Interrupção de GPIO no Linux

Uma aplicação em sistemas embarcados frequentemente entra em contato com o mundo externo para realizar medidas, aferir dados, e, então, para processá-las e retorná-las como uma informação relevante ou controlar outros dispositivos. Essas aplicações estão nas mais variadas áreas, como nas ciências biológicas através de equipamentos médicos, para controle industrial, na agricultura de precisão, robótica e muitas outras.

Para realizar esta interação com o mundo, uma das interfaces mais utilizadas é a GPIO, General Purpose I/O, que trata-se de um I/O programável digital que utiliza 2 níveis lógicos (0 ou 1). Nos pinos de GPIO pode-se conectar LEDs, botões, sensores, válvulas e mais dispositivos para interfacear a aplicação com o mundo externo.

Para estabelecer esse periférico como entrada da aplicação é preciso configurá-lo para a leitura, que precisa ser orientada aos eventos da interface, isto é, mudanças ocorridas no exterior que são transmitidas no pino de GPIO.

Para lidar com estas ocorrências de entrada (input) que, a priori, não sabemos quando elas vão ocorrer, são possíveis duas abordagens: a leitura a uma frequência dada (técnica de polling) ou o uso de interrupções.

Neste artigo iremos realizar um estudo de caso com Linux Embarcado no Computador em Módulo Colibri i.MX6 DL IT e a placa base Aster para demonstrar o uso das GPIOs com interrupções e o porquê dessa escolha.

Um pouco sobre o computador em módulo e a placa base

O computador em módulo Colibri i.MX6 DL IT possui um processador NXP/Freescale i.MX6 DL Cortex A-9 com frequência de operação de 800MHz/1GHz e até 154 pinos de GPIO configuráveis.

Computador em módulo com destaque aos principais componentes - GPIO no Linux
Figura 1: Computador em módulo com destaque aos principais componentes

O conector de 200 pinos SODIMM (X1) externaliza as interfaces do processador, em que cada pino do processador pode assumir diversas funções, dentre elas GPIO. Para garantir a pino-compatibilidade entre os módulos da família Colibri (iMX6, iMX7, iMX6ULL, etc) da Toradex, é recomendável utilizar a configuração Colibri padrão dos pinos que é fornecida na imagem padrão. Você pode utilizar a Pinout Design Tool para descobrir as funções auxiliares de cada pino e configurar a pinagem para a sua placa base, se estiver utilizando Windows CE.

Existem pinos do i.MX6 que são pareados e dividem o mesmo pino SODIMM.

Por exemplo, o pino 59 do conector X1 do módulo é compartilhado entre os pinos SD4_DATA1 (Secure Digital Memory Card, relacionado ao SDHC, MMC, CE_ATA, eMMC) e EIM_ADDR22 (External Interface Module ou External Memory Bus). Essa informação será muito valiosa na escolha da GPIO, futuramente.

A placa base Toradex Aster possui suporte a até 39 pinos programáveis de GPIO e até 4 interfaces de PWM localizadas no conector X20, ilustrado na imagem abaixo.

Placa base Aster da Toradex com as principais I/Os indicadas
Figura 2: Placa base Aster da Toradex com as principais I/Os indicadas

Na aplicação a seguir, vamos utilizar um pino de GPIO como frequencímetro. A medida de frequência será realizada através de um dos pinos de PWMs do próprio módulo, uma vez que sua frequência é ajustável e precisa.

Os pinos que iremos utilizar estão no conector X20 indicado na figura acima. Esse conector possui 40 pinos de diferentes funções. Um detalhe interessante é que essa pinagem presente na placa base Aster é compatível com os headers da Raspberry Pi tipo A+, B+ 2B e 3B, possibilitando o uso de hats e headers. A placa base também inclui header pino-compatível com Arduíno nos conectores X15, X16, X17 e X18, onde é possível acoplar shields. A equivalência de pinos permite a adição de funcionalidades de forma prática e rápida.

Vamos ver abaixo as funções de cada um dos 40 pinos do conector X20, com destaque para os mais relevantes no caso da nossa aplicação: PWM destacados em vermelho e GPIOs destacadas em verde.

Os pinos do conector X20 da placa base comunicam-se com o SODIMM do módulo, então, é necessário escolher com cautela o pino da GPIO utilizado, de modo a certificar-se que a função primária dele na device tree corresponde com a funcionalidade desejada.  

Conector X20 detalhado com pinos de interesse (GPIOs, PWM)
Figura 3: Conector X20 detalhado com pinos de interesse (GPIOs, PWM)

Agora, para começar a acessar em software estes pinos, precisamos localizá-los no sistema de arquivos do Linux.

Exportando GPIO

Vamos escolher qual GPIO queremos usar para o frequencímetro.

A GPIO utilizada foi a CIF_D_1 (pino 16) pois é associada ao pino SODIMM 98 que possui como função primária a GPIO.

Conexão da CIF_D_1 e PWM_B para uso de GPIO no Linux
Figura 4: Conexão da CIF_D_1 e PWM_B

Se houverem dúvidas na contagem dos pinos, é recomendado virar a placa e observar a solda em formato de quadrado. Este deve ser o pino 1.

Demonstração dos pinos 1 e 2 do conector X20
Figura 5: Demonstração dos pinos 1 e 2 do conector X20

Agora precisamos localizar, no sistema de arquivos do Linux, a CIF_D_1 para exportá-la. É possível obter mais informações sobre o uso de GPIO neste artigo.

Para obter o número da GPIO no sistema de arquivo do Linux, precisamos descobrir qual é o pino SODIMM do módulo Colibri i.MX6 que está conectado ao CIF_D_1 e realizar um cálculo simples com a GPIO, ou consultar a tabela de correlação deste link. Podemos obter essa informação nos datasheets da placa base e módulo.

Estas informações estão simplificadas na tabela abaixo.

Você pode descobrir outros pinos também pelo mesmo caminho! Atente-se para a configuração primária dos pinos SODIMM do módulo, isto é, existem alguns pinos que precisarão de alterações na device tree para funcionar propriamente como GPIO.

Vamos exportar a GPIO15 no módulo:

Configurando o PWM

A fim de configurar o PWM no módulo Colibri i.MX6 DL, precisamos exportar a PWM desejada. No exemplo, vamos usar a PWM_B. Mais informações sobre o uso de PWM podem ser encontradas neste link.

Exportando PWM_B:

Configurando com frequência de 10 kHz com duty-cycle de 50% (período e duty-cycle em nanossegundos):  

Verifique o valor da GPIO via linha de comando e veja o valor oscilando entre 1 e 0 lendo o arquivo repetidamente:

A aplicação

No mundo real, os eventos são, em geral, irregulares, isto é, não temos certeza de quando vão ocorrer, o que favorece a implementação de interrupção para vigiar as GPIOs de modo a garantir elegância e eficiência no código. No entanto, existe uma outra estratégia de implementação que envolve a leitura da IO a uma dada frequência, a chamada técnica de polling.

Vamos utilizar uma implementação em C utilizando a função poll() para efetuar a interrupção.

A biblioteca poll.h

A funcionalidade de poll(), em linhas gerais, é de vigiar um descritor de arquivo na espera da prontidão para leitura/escrita. Até ficar, de fato pronto, o processo que realizou a chamada permanece bloqueado.

A biblioteca de poll.h  fornece a struct pollfd com o descritor do arquivo que se deseja vigiar, os eventos de interesse (events) e eventos retornados (revents).

A chamada de poll() ocorre com os parâmetros struct pollfd do descritor arquivo a ser vigiado, número de arquivos a serem vigiados e por fim o timeout, que é o tempo em milissegundos que poll() deve bloquear na espera do arquivo. O bloqueio ocorre até que haja um evento em ação.

Além disso, poll suporta os seguintes tipos de eventos para serem aguardados, configurados pelas flags:

  • POLLPRI: dados de alta prioridade podem ser lidos sem bloqueio;
  • POLLHUP: desligado (apenas retornada em revents, ignorada em events). Usado em programação com sockets, indica que um dos lados do canal foi encerrado. As próximas leituras resultam em 0 (end of file) depois de todos os dados do canal serem consumidos;
  • POLLRDHUP: usado em programação em sockets. Indica que a conexão foi encerrada ou terminou a conexão com escrita interrompida. É necessário definir a macro _GNU_SOURCE antes de qualquer outra definição;
  • POLLERR: condição de erro (apenas retornada em revents, ignorada em events);
  • POLLNVAL: requisição inválida (apenas retornada em revents, ignorada em events);
  • POLLIN: existem dados para ler;
  • POLLOUT: a escrita está disponível;
  • POLLRDNORM: equivale a POLLIN;
  • POLLRDBAND: dados da banda de alta prioridade podem ser lidos;
  • POLLWRNORM: equivalente a POLLOUT;
  • POLLWRBAND: dados da banda de alta prioridade podem ser lidos.

Vamos ver um pequeno exemplo de uso de poll, que vigia a entrada padrão com timeout de 5 segundos, então ao compilar e executar:

Se você digitar na linha de comando, o programa envia uma mensagem avisando que os dados estão disponíveis.

Para compilar os exemplos para a placa, é necessário configurar uma toolchain. Os passos para tal estão listados neste artigo.

Os comandos para compilar, copiar e executar na placa após execução dos passos de configuração da toolchain são:

No seu computador host:

Na sua placa:

Talk is cheap, show me the code

Nesta aplicação queremos vigiar o valor da GPIO e para os eventos, iremos configurar a flag POLLPRI, que indica que dados de alta prioridade podem ser lidos sem bloqueio.

Com o uso de poll(), o cálculo da frequência se dá pelo inverso do período. Este, por sua vez, é obtido através da diferença do tempo entre mudanças no valor do GPIO em um número de amostras.

O código fonte pode ser consultado no Github.

Execução

Captura de tela da execução do programa na placa Colibri IMX6 DL para demonstrar Interrupção no Linux Embarcado
Figura 6: Captura de tela da execução do programa na placa Colibri IMX6 DL

A execução do programa com PWM configurado para período de 1 ms e frequência de 1kHz com duty-cycle de 25% é mostrada na figura acima.

Para testar o programa, você pode alterar a configuração do período do pino de PWM.

Consumo de CPU: o porquê da interrupção

A motivação central da escolha de interrupções para a implementação do frequencímetro no presente estudo de caso é o consumo de CPU.

Na técnica de polling, até com o sinal PWM desativado, o consumo de CPU será máximo, uma vez que essa estratégia é composta basicamente por um laço infinito, de leitura constante, avaliando as alterações na GPIO.

Já a interrupção, possui consumo quase zero de CPU sem acionar o PWM, uma vez que o processo que chamou a função poll() fica bloqueado no aguardo da interrupção.

Lembre-se que esse programa pode estar rodando simultaneamente a outros, que aplicam essa frequência, logo, é fundamental que a thread de frequencímetro seja o mais leve possível, de modo a não comprometer o produto final.

Conclusão

A resolução do cálculo do período, e consequentemente da frequência , é definido pela resolução a qual adquirimos os tempos inicial e final e o número de amostras de medição da subida do pulso.

Com o parâmetro atual de 1.000 adquirindo o tempo em nanossegundos, podemos calcular frequências na ordem de grandeza de até 33 kHz (período de 30µs) com a implementação de interrupções por poll() para as configurações de hardware, software e SO utilizadas nos testes executados neste artigo.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Linux Embarcado » Interrupção de GPIO no Linux
Comentários:
Notificações
Notificar
guest
0 Comentários
Inline Feedbacks
View all comments
Talvez você goste:

Séries

Menu