Programação Multithread com GPIO do Raspberry Pi

Este artigo irá abordar uma forma de controlar as portas de entrada e saída da placa de desenvolvimento Raspberry Pi através de programação multithread em linguagem C. O experimento proposto consiste em criar uma aplicação com múltiplas threads independentes para ler entradas e controlar saídas. Uma região de memória compartilhada irá armazenar o estado dos pinos de entrada e, com base nesses valores, as saídas serão atualizadas. Um mutex será utilizado para sincronizar as threads e controlar o acesso ao espaço de memória compartilhado.

O Raspberry Pi é um SBC (Single-board Computer) do tamanho de um cartão de crédito que, na sua versão 2 modelo B, vem munido de um processador quad-core de 900MHz ARM Cortex-A7 e 1GB de memória RAM, capaz de rodar sistemas operacionais baseados em GNU/Linux. A vantagem de criar aplicações sobre a plataforma de um sistema operacional Linux é que podemos explorar a sua capacidade de multiprocessamento e pseudo-paralelismo. Isto quer dizer que o sistema operacional é capaz de executar dezenas de tarefas simultaneamente, mesmo que escalonando-as no tempo. Assim, é possível desmembrar aplicações mais complexas em diversas linhas de execução que executam tarefas bem definidas.

É aconselhável que o leitor já esteja familiarizado com programação em linguagem C para ambiente Linux. Caso haja dúvidas, pode-se consultar os artigos publicados anteriormente aqui no site, especialmente os artigos que tratam sobre a Criação e Manipulação de Threads POSIX e sobre Sincronismo de Threads Utilizando Mutex.

Material Utilizado

Para a montagem do circuito e testes foram utilizados os seguintes materiais e componentes eletrônicos:

  • Raspberry Pi 2 model B rodando Raspbian Jessie
  • Monitor + Teclado + Mouse + Fonte de alimentação (para o Raspberry Pi)
  • Protoboard 830 pontos modelo MB-102
  • Placa breakout T-Cobbler + Cabo flat 40 vias
  • Fios jumper macho-macho
  • 4x LEDs difusos azuis 5mm
  • 4x Push buttons (chave momentânea)
  • 4x Resistores de 220Ω e 1/4W
  • 4x Resistores de 1kΩ e 1/4W
  • 4x Resistores de 10kΩ e 1/4W

Se você tem um Raspberry Pi ainda sem sistema operacional ou deseja apenas atualizar a sua versão do Raspbian, leia meu último artigo sobre como instalar o Raspbian Jessie no cartão de memória.

GPIO do Raspberry PI

O Raspberry Pi, além de possuir portas USB e Ethernet, bem como saídas HDMI, de áudio e vídeo composto, também possui um conector de 40 pinos (conector P1) que disponibiliza 17 pinos de GPIO (General Purpose Input/Output), uma interface serial UART, uma interface I²C, uma interface SPI, uma saída PWM, além de alimentação. Esta é uma característica poderosa desse pequeno computador, pois possibilita controlar e receber informações de vários dispositivos externos e periféricos, tais como LEDs, relés, sensores, motores, conversores A/D e displays gráficos de LCD. Melhor ainda: por ter uma interface Ethernet pronta para usar, é possível colocar todos estes dispositivos externos na rede e até mesmo na Internet. É por esse motivo que muitas soluções que necessitam de Linux embarcado têm empregado o Raspberry Pi como plataforma base de desenvolvimento.

Os pinos de alimentação do conector P1 fornecem 5V, 3.3V e GND. A alimentação 3.3V está limitada a apenas 50mA. Já a alimentação de 5V drena corrente diretamente do conector da fonte microUSB e pode utilizar o quanto sobrar do consumo da placa. Dessa maneira, uma fonte de alimentação microUSB de 1A consegue fornecer 300mA no conector P1, uma vez que a placa consome em média 700mA.

Os pinos de GPIO drenam 50mA de corrente com segurança. Mas atenção! São 50mA distribuídos entre todos os pinos! Um pino individual de GPIO pode drenar com segurança apenas 16mA. Por este motivo, se deseja acionar dispositivos de maior porte, utilize um circuito driver de potência e, sempre que possível, utilize fotoacoplamento para proteger seu Pi.

A figura 1 mostra a disposição dos pinos do conector P1 e suas respectivas funções. Este padrão de pinos é o mesmo para as versões 1A+, 1B+, 2B e 3B.

Pinagem do Conector P1 do Raspberry Pi

Figura 1. Pinagem do Conector P1 do Raspberry Pi

Na figura 1, as funções de cada pino também estão identificadas por cores, estando os pinos de GPIO representados pela cor verde. Esses pinos podem ser utilizados tanto como entrada, tanto como saída, desde que sejam devidamente configurados. Cada pino também possui, internamente, resistores de pull up/down que podem ser habilitados quando o pino está configurado como entrada.

Além das interfaces citadas anteriormente, temos também uma interface mais sofisticada que é a ID EEPROM. Esta interface é do tipo I²C e, durante o boot, irá procurar por uma memória EEPROM que identifique uma placa adicional que esteja conectada ao RPi. Essa função pode configurar automaticamente o GPIO (e opcionalmente, drivers do Linux) para suportar a respectiva placa.

A Biblioteca bcm2835

A biblioteca bcm2835 foi desenvolvida para o Raspberry Pi e dá acesso ao GPIO e outras funções de entrada e saída do chip Broadcom BCM2835 que estão disponíveis no conector P1 da placa de desenvolvimento. Além de dar acesso aos pinos físicos  do RPi, a biblioteca permite acesso aos timers do sistema. Interrupções ainda não são suportadas pela biblioteca e, por este motivo, eventos só podem ser detectados através de testes contínuos. Além disso, a biblioteca é compatível com a linguagem C++ e se instala como um arquivo header e uma biblioteca não compartilhada (non-shared library).

O download da biblioteca pode ser feito acessando o site aqui. No momento deste post, a biblioteca encontra-se na versão 1.50. No site, que é dedicado ao desenvolvimento da biblioteca, você encontra informações, histórico de aprimoramento, manual de referência das funções e exemplos. O manual de referência é bastante completo e consiste em uma ótima fonte de consulta para a programação, pois explana os argumentos de cada função e os valores de retorno. Já os exemplos não se aprofundam tanto, porém são suficientes para nortear aplicações mais requintadas.

Para instalar a biblioteca, baixe o arquivo .tar.gz em uma pasta conveniente do seu Raspberry Pi e digite os seguintes comandos no terminal:

Terminada a instalação, já é possível incluir o arquivo header em seus códigos C e utilizar as funções disponíveis. Abaixo, estão listadas as seis funções que foram utilizadas para a programação do estudo de caso deste artigo. Lembrando que, para a compilação com o gcc, é necessário utilizar o argumento -lbcm2835 na linha de comando.

A função bcm2835_init() inicializa a biblioteca abrindo /dev/mem (se você está logado como root) ou /dev/gpiomem (caso você não seja root) e adquirindo ponteiros para os registradores da memória interna do chip BCM2835. É necessário chamar essa função antes de utilizar qualquer outra função da biblioteca. Em caso de falha, a função retornará 0 (zero) e, após o erro, chamar qualquer outra função da biblioteca pode resultar em comportamento anômalo. Diante dessa situação, recomenda-se imprimir mensagens de erro no arquivo padrão stderr. Em caso de sucesso, a função retornará 1 e se o usuário não estiver logado como root, apenas operações com o GPIO são permitidas.

A função bcm2835_gpio_write() impõe o nível lógico especificado pelo parâmetro on (HIGH ou LOW) no pino especificado pelo parâmetro pin.

A função bcm2835_gpio_lev() lê o valor lógico de um pino especificado pelo parâmetro pin, retornando HIGH ou LOW. Ela funciona tanto com pinos configurados como entrada, tanto como saída.

A função bcm2835_gpio_fsel(), basicamente, configura o pino especificado pelo parâmetro pin como entrada ou saída.

Já a função bcm2835_delay() provoca um atraso em milissegundos. Ela faz uso da função nanosleep() e, portanto, não utiliza a CPU até que o tempo especificado tenha transcorrido. No entanto, a função nanosleep() pode fazer arredondamentos para cima no valor de tempo especificado. Além disso, após terminar a rotina de atraso, ainda pode haver um delay até que a CPU fique livre novamente.

Por fim, a função bcm2835_close() fecha a biblioteca liberando qualquer memória que tenha sido alocada e fechando /dev/mem ou /dev/gpiomem.

A biblioteca bcm2835 é recheada de funções interessantes, além de dar suporte para PWM, SPI e I²C. O manual completo de funções pode ser consultado aqui.

Utilizando o GPIO – Prática

Para exemplificar o uso do GPIO do Raspberry Pi com a biblioteca bcm2835, será proposto um circuito utilizando LEDs e chaves momentâneas (tipo push button). Cada chave será responsável por acionar um LED correspondente e, sempre que determinada chave estiver pressionada, o LED correlato deve acender. O circuito, então, será formado por 4 LEDs e 4 push buttons.

-> Hardware – Montagem do Circuito

A figura 2 mostra o esquemático do circuito proposto para este experimento. As vias de sinal na cor azul são provenientes de pinos configurados como saída e as vias na cor amarela são de pinos configurados como entrada. A via vermelha é de alimentação 5V e a preta representa o GND.

LEDs e Botoes - Raspberry Pi - Esquematico

Figura 2. Esquemático do circuito proposto para testes

Sabendo que o GPIO do RPi trabalha com uma tensão 3.3V, adotou-se um resistor de 220Ω para limitar a corrente em cada LED, o que proporciona uma corrente de aproximadamente 7,5mA. É o suficiente para que o LED ligue de forma bastante visível e assegurar que não haverá sobrecarga em cada pino do GPIO. Nos pinos de entrada, adotou-se resistores de 10kΩ como pull-down. Isso quer dizer que, quando um botão não estiver pressionado, o pino de entrada estará em nível baixo. Os resistores de 1kΩ servem para limitar a corrente de entrada quando um dos botões for pressionado. Observe que os botões estão ligados em 5V. Se essa tensão fosse aplicada diretamente às portas de entrada, estas seriam danificadas.

A figura 3 mostra um esquema de montagem mais simplificado para a protoboard feito com a ferramenta Fritzing. Este modelo de montagem não utiliza o breakout T-Cobbler.

LEDs e Botoes - Raspberry Pi - Protoboard

Figura 3. Esquema de montagem do circuito na protoboard

E a figura 4 mostra a montagem final na protoboard. Esta foi a versão utilizada nos testes que utiliza o breakout T-Cobbler. Essa plaquinha facilita muito a montagem por dar acesso direto a todos os pinos do GPIO na protoboard.

LEDs e Botões - Raspberry Pi - Montagem Final

Figura 4. Montagem final na protoboard

A seguir, passaremos à análise do estudo de caso e à explicação do código desenvolvido para controlar o hardware apresentado.

-> Programação – Análise de Pré-Requisitos

Antes de analisar o código propriamente dito, devemos nos preocupar em idealizar a estrutura da aplicação e definir quais recursos do sistema serão utilizados. Duas perguntas que devem ser feitas são: 1) Devo criar uma única linha de execução para cuidar de todas as entradas e saídas ou devo distribui-las? e 2) Devo utilizar threads ou processos? Essas duas perguntas são de fundamental importância e suas respostas estão baseadas na análise prévia feita do estudo de caso.

O desenvolvimento de aplicações para sistemas embarcados, especialmente os que rodam em plataformas de tempo-real (RTOS – Real Time Operating System), baseia-se, principalmente, na independência entre as tarefas que são executadas pelo sistema. Isso não só acarreta na distribuição do código e das linhas de execução, como também explora ao máximo o paralelismo no processamento. Trazendo esse pensamento para o estudo de caso, podemos notar que a leitura das entradas (botões) independe do controle das saídas (LEDs) e vice-versa. São tarefas totalmente independentes que não necessitam diretamente umas das outras, logo podem ser separadas em linhas de execução distintas.

As saídas necessitam se basear em algum valor para atualizarem o seu estado, mas esse valor pode vir de diversos lugares diferentes, não necessariamente dos botões de entrada. O mesmo ocorre com as entradas: elas devem enviar o seu status para algum lugar, mas não necessariamente para os LEDs. E, aprofundando-se nessa linha de raciocínio, podemos ainda notar que cada botão independe dos outros. Eles são uma entrada única e independente do sistema. O mesmo para os LEDs, que são independentes entre si. Com essas observações, podemos chegar a uma conclusão de que o mais adequado será distribuir as tarefas de leitura das entradas e controle das saídas em linhas de execução separadas. Assim, haveriam quatro linhas de execução para leitura das entradas e quatro linhas de execução para controlar as saídas.

E agora que já respondemos a primeira pergunta, devemos analisar com qual recurso faremos essa divisão das linhas de execução. Sabemos que as tarefas que cuidam das entradas deverão se comunicar com as tarefas que cuidam das saídas. Então, deverá haver um meio de comunicação entre elas. Se utilizarmos processos, primeiramente, teremos que criá-los, o que já é sabido que demanda tempo, memória e processamento. Além disso, a comunicação entre processos (IPC – Inter-Process Comunication) é mais lenta, pois necessita de outros mecanismos do kernel, tais como sinais ou named pipes. Sem contar que o sincronismo entre processos é também mais complicado.

Já as threads são de rápida criação, gastam menos recursos de memória e possuem espaço de memória compartilhado dentro do escopo do processo que as criou. Também já vimos que o sincronismo de threads é facilitado pelo uso de mutex que também evita condições de corrida. Assim sendo, não resta dúvidas de que para este estudo de caso as threads são mais indicadas.

-> Programação – Código Multithread em C

Tendo definida a estrutura do código e os recursos a serem utilizados, pode-se partir para o desenvolvimento do programa. Apesar de utilizar recursos mais avançados, como threads e mutex, o código ficou extremamente simples e enxuto. E é esse o intuito de utilizar recursos mais avançados do sistema: facilitar; não complicar. O desenvolvimento em ambiente Linux é rico em recursos poderosos e devemos dominá-los para otimizar ao máximo nossas aplicações.

No código, como variáveis globais, temos o mutex que será utilizado para o sincronismo das threads, um vetor de quatro posições que armazenará o estado de cada pino de entrada (botões) e mais dois vetores que armazenam macros para indicar quais pinos serão utilizados como entrada e saída.

Na função principal, primeiramente, o mutex é inicializado e, depois, a biblioteca bcm2835 também é inicializada. Feito isso, os pinos de entrada e saída são selecionados, desabilitando os resistores de pull up/down nos pinos de entrada. Logo em seguida, oito threads são criadas e são unidas à função principal. Por fim, a biblioteca bcm2835 é fechada e o mutex é destruído.

Dentre as threads, quatro delas fazem a leitura das entradas através da função inputRead() e as outras quatro controlam as saídas através da função controlLED(). E dentro do laço for que cria as threads, a cada iteração, é criada uma thread de entrada e outra de saída (um par casado). Cada par de threads recebe um número (de 1 a 4) que é repassado como parâmetro da função pthread_create(). Esse número irá relacionar uma thread com a outra informando que aquela thread de entrada será responsável por fornecer informações para aquela thread de saída.

Importante também notar que existe um laço for para unir as threads com pthread_join(). No entanto, esse laço nunca irá se completar, a menos que algo anormal aconteça. Isso se deve ao fato da função pthread_join() ser bloqueante e as threads estarem programadas para nunca terminarem. Assim, logo na primeira iteração, a execução ficará bloqueada na primeira chamada dessa função.

Nas funções das threads, inputRead() e controlLED(), não há mistérios. Elas rodam dentro de um laço while infinito e não devem terminar sua execução chamando pthread_exit() a menos que algo anormal aconteça. O único detalhe em ambas as funções é que o mutex deve ser travado antes de acessar a variável led_state e, logo em seguida, deve ser liberado. Essa é a variável compartilhada da memória e deve ser protegida pelo mutex para evitar condições de corrida. Observe também que há um delay de 50ms ao final de cada laço while. Esse delay serve para que o escalonador da CPU dê lugar a outra thread para ser executada, pois, como explicado anteriormente, a função de bcm2835_delay() não faz uso da CPU. Isso também contribui para economizar poder de processamento, pois dessa maneira as threads não ficam sendo executadas freneticamente. Esse é um intervalo razoável para testar botões que serão pressionados pelo usuário e nenhum atraso será percebido.

Veja o código completo abaixo:

Salve este código com o nome gpio_rpi.c e compile-o no seu RPi. Para compilar o código, use a seguinte linha de comando no terminal, digitando-a a partir da pasta onde encontra-se o arquivo do código:

Antes de executar o código, tenha absoluta certeza de que o circuito está montado de maneira correta e conectado de forma adequada ao conector P1 do seu Raspberry Pi. Para finalizar a execução do programa, pressione as teclas Ctrl+C no terminal.

Se tudo funcionou adequadamente, meus parabéns! Agora proponho um pequeno teste: enquanto o código estiver rodando no terminal, observe no canto superior direito da sua tela o uso do processamento do RPi. O meu fica oscilando entre 0% e 8%. Mesmo que todos os botões sejam pressionados ao mesmo tempo, não há picos de uso da CPU. Feito isso, comente as duas chamadas da função bcm2835_delay(), recompile o código e rode novamente o programa. Observe agora o uso da CPU. Aumentou? Saberia explicar o motivo? Bom, a resposta já foi dada. =)

-> Executando a Aplicação Automaticamente no Boot

E que tal ter essa aplicação rodando automaticamente em background toda vez que você inicializar o seu Raspberry Pi? Para que isso seja possível, basta executar três simples passos:

  1. Copie o arquivo executável que você compilou para a pasta /usr/bin (com permissão de super usuário).
  2. Abra o arquivo /etc/rc.local com o editor de textos nano e adicione o texto gpio_rpi & antes da diretiva exit 0. Salve as alterações pressionando Ctrl+o e depois feche o arquivo pressionando Ctrl+x.
  3. Reinicie o seu Raspberry Pi e confira se funcionou.

O arquivo rc.local deve ficar assim:

Arquivo rc.local ModificadoImagem 5. Arquivo /etc/rc.local modificado

O arquivo rc.local é um shell script que é sempre executado na inicialização do sistema. Assim, é possível fazê-lo executar a nossa aplicação toda vez que o sistema for inicializado. As aplicações que são chamadas diretamente do Shell devem estar contidas em alguma das pastas apontadas pela variável de ambiente PATH. Por isso, foi necessário mover o arquivo executável para uma dessas pastas primeiro. Outra alternativa é adicionar a pasta do projeto à variável PATH, assim não é preciso mover o arquivo. O “&” adicionado na frente do nome da aplicação faz com que o programa rode em background e não bloqueie a execução do script.

A aplicação ficará rodando em background continuamente sem nunca terminar. Posteriormente, caso você deseje terminar a aplicação, é possível enviar um sinal para terminar o processo. Há várias formas de fazer isso e existem dois comandos bastante utilizados para enviar sinais a um processo pelo terminal, são eles: kill pkill. O primeiro envia um sinal para o processo indicado através do seu PID e o segundo envia um sinal para um processo indicado através do seu nome ou de parte dele.

Como o comando pkill já envia por default o sinal SIGTERM e não necessita do PID do processo, vamos utilizá-lo:

Caso você não se lembre do nome completo da sua aplicação, pode consultá-la com o comando pgrep. Esse comando procura na lista de processos que estão sendo executados pelo padrão de texto informado como argumento.  Supondo que você lembre apenas da parte “gpio” do nome:

Neste caso, o valor retornado pelo comando compreende o PID do processo e o nome dele. Se houverem  vários processos que contenham “gpio”no nome, eles serão listados neste comando.

Conclusão

Com este pequeno experimento, foi possível colocar em prática boa parte dos conhecimentos de programação fornecidos nos artigos anteriores. A minha intenção, a priori, foi de fornecer a base de programação em Linux para depois explorar esses recursos com práticas interessantes. Nesse artigo, pudemos explorar um pouco de threads e mutex.

Além de aplicar as técnicas de programação, a novidade aqui foi unir programação e a parte de hardware e eletrônica, acessando os pinos de GPIO do Raspberry Pi através da linguagem C. Agora, eletrônica e programação começam a criar laços de uma forma deslumbrante e quase que inseparável. Se podemos ler uma entrada, podemos ler dados de sensores; e se podemos acender um LED, podemos acender uma lâmpada ou mesmo acionar um grande motor. As possibilidades só vão aumentando e a gama de aplicações também.

Aqueles que já escreveram um firmware para microcontroladores, com certeza notaram a diferença de programar sobre a plataforma do Linux. É mais potente e mais robusto! Logicamente, certas aplicações não necessitam de um sistema operacional e se resumem a poucas linhas de códigos. Contudo, aplicações mais complexas, multitarefas, irão necessitar de recursos de  gerenciamento que só um sistema operacional pode fornecer.

Gostou do artigo? Deixe um comentário, curta e compartilhe com os amigos!
Caso haja dúvidas, fique à vontade para perguntar.
Abraços e até a próxima!

Sobre

Jair Junior é Bacharel em Engenharia Eletrônica pela Universidade de Brasília [2014] com ênfase em microeletrônica. Suas especialidades na área são microcontroladores, sistemas embarcados e projeto de hardware. Também possui conhecimentos aprofundados em aplicações web e processamento digital de imagens. Atualmente, é aluno de pós-graduação lato sensu da PUC Minas no curso de Desenvolvimento de Aplicações Web. Ademais, tem como hobbies viajar, praticar esportes na natureza, apreciar cervejas artesanais e escutar um bom e velho rock 'n roll. Para mais detalhes, acesso a página sobre o autor.

Ver todos os posts de

2 thoughts on “Programação Multithread com GPIO do Raspberry Pi

  1. Excelente post, material muito interessante para quem deseja desbravar e ir mais além das funcionalidades básicas que essa placa pode proporcionar.
    Parabéns.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *