Controle de GPIO da Raspberry Pi com linguagem C - Embarcados
Controle de GPIO da Raspberry Pi utilizando a linguagem C
Confira todos os pontos em uma configuração dos pinos de GPIO da Raspberry Pi e como o Sistema Operacional se porta perante estas configurações.
Então pessoal, quando comecei a desenvolver soluções envolvendo sistemas embarcados, enfrentei muitas dificuldades para utilizar os recursos que a Raspberry Pi possui, trabalhando com códigos de baixo nível que permitem um maior controle sobre suas funcionalidades computacionais, como por exemplo a linguagem C.
Dessa forma, pensei em utilizar este aspecto como motivação para produzir este artigo, detalhando todos os pontos em uma configuração dos pinos de GPIO da Raspberry Pi, como o Sistema Operacional se porta perante estas configurações, para que seu desenvolvimento tenha um salto de qualidade e que essas dificuldades impostas ao se trabalhar com um sistema embarcado utilizando linguagens de baixo nível sejam solucionadas.
Relato também que este tema já foi abordado nos artigos Raspberry PI – GPIO input com Python e Raspberry PI – GPIO output com Python, mas a linguagem utilizada em ambos foi Python. Portanto, a construção de um material contendo este assunto é muito importante, visto que podemos efetuar um comparativo entre os benefícios que o desenvolvimento de aplicações envolvendo Python ou C podem proporcionar respeitando as características de projeto, mas este assunto fica para um próximo artigo.
Antes de começarmos a desenvolver os códigos, primeiramente precisamos instalar a imagem do S.O operacional desejado. Para executar este procedimento, disponibilizamos em nosso banco de artigos o material Como instalar o Raspbian na Raspberry? que pode ser utilizado como consulta visto que o S.O presente neste projeto foi o Raspbian.
(Nota: Encaminhamos também para consulta o material postado nesta página que ilustra o procedimento de como o cartão deverá ser gravado).
Caso não conheça a Raspberry Pi, recomendamos ler o artigo Raspberry Pi. Se já possui um conhecimento desta plataforma mas não é muito familiarizado com Linux, o artigo Raspberry Pi e o Linux irá proporcionar uma excelente base sobre os dois assuntos.
Distribuição: Raspbian GNU/Linux – Jessie (2016-09-23)
Kernel 4.4.26-v7+
GPIO (General Purpose Input/Output) é o termo utilizado para o controle de todo pino que possui as funcionalidades gerais de entrada e saída e não possui atribuições dedicadas, ou seja, todo pino que possibilita a configuração como entrada ou saída pode ser classificado como pino de GPIO desde que não possua características funcionais dedicadas.
Estes pinos podem ser configurados como saída (estado lógico alto – 1 ou baixo – 0) ou como entrada utilizado na leitura de push buttons por exemplo.
Controlando os pinos de GPIO
Primeiramente, para controlar os pinos de GPIO de qualquer microcontrolador, precisamos necessariamente saber qual é o seu impacto quando tentamos controlar o mesmo na presença de um Sistema Operacional portado no processador do dispositivo.
Em processadores de alto desempenho, que possuem um sistema operacional, esta ação de controle é submetida ao kernel, ou seja, o kernel que se comunica diretamente com o hardware e os softwares do espaço de usuário possuem apenas um subconjunto de instruções de máquina. Dessa forma, para que um programa que está no espaço de usuário poder ter acesso ao hardware, é necessário que o kernel permita essas operações.
Quando estamos tratando das configurações dos pinos de GPIO da Raspberry Pi, onde o programa do espaço de usuário necessita da permissão de controle dos pinos de GPIO, existem várias bibliotecas que permitem esse controle. Neste documento iremos utilizar a sysfs, que proporciona um controle maior relacionado à manipulação dos arquivos presentes na estrutura de diretórios do S.O.
O Sysfs possui a capacidade de permitir ao código do kernel a exportação das informações necessárias para o controle dos periféricos do espaço de núcleo ao espaço do usuário em um sistema de arquivos em memória, que nos possibilita a manipulação do hardware no espaço do usuário mesmo com a presença do sistema operacional. Esta ação simplesmente nos proporciona uma interatividade entre nossa aplicação e o hardware, trabalhando apenas com arquivos, links simbólicos e diretórios.
Dessa forma, este recurso torna o processo de controle amigável ao desenvolvedor, visto que para o controle dos periféricos é necessário trabalhar apenas com arquivos e suas derivações em conjunto com uma linguagem que possua essas propriedades de manipulação de arquivos.
Estrutura de diretórios do SYSFS
Sysfs é uma simples coleção de diretórios, arquivos e links simbólicos, que permitem a navegação e manipulação utilizando simples comandos shell.
A Figura 1 ilustra a estrutura dos subdiretórios presentes no sysfs:
Figura 1 – Estrutura de diretórios sysfs
Adicionalmente, encaminhamos abaixo, a critério introdutório, os diretórios em conjunto com suas especificações técnicas. Como nosso foco está direcionado apenas com as funcionalidades ligadas ao diretório /sys/class/gpio, qualquer dúvida a respeito deverá ser consultada nesta página.
block: Este diretório possui subdiretórios para cada dispositivo do sistema. Em cada diretório, são descritos os atributos que descrevem as características dos dispositivos.
bus: Possui um layout com os vários tipos de barramento do kernel presentes no processador. Cada subdiretório que representa a forma de barramento utilizada, possui dois subdiretórios:
Conjunto de links simbólicos para cada device encontrado no sistema.
Possui os drivers para cada dispositivo presente no barramento utilizado.
class: Diretório que contém as representações de cada dispositivo conhecidas pelo kernel.
devices: Contém um sistema de arquivos que representa uma árvore de dispositivos.
firmware: Possui as interfaces de visualização e manipulação de atributos e objetos específicos do firmware.
module: É o diretório que contém os subdiretórios para cada módulo do kernel.
Diretório /sys/class/gpio/
Este diretório possui três tipos de entrada:
Interfaces de controle usadas para permitir ao espaço de usuário o controle do pino de GPIO;
Instâncias de Controle do GPIO.
Visando ilustrar como é disposta a estrutura de diretório após a exportação do controle do GPIO para o espaço de usuário, considere a Figura 2.
Figura 2 – Estrutura do diretório /sys/class/gpio/
Inicialmente, para que possamos controlar um pino de GPIO, necessariamente precisamos fazer com que este controle feito no espaço de núcleo seja exportado para o espaço de usuário. Esta interface de controle irá permitir que um programa situado no espaço de usuário solicite ao kernel o controle do GPIO e de suas propriedades:
export: O programa no espaço de usuário solicita ao kernel o controle do GPIO no espaço de usuário.
unexport: Reverte as ações efetuadas pelo export.
Após a exportação do pino, precisamos definir como o mesmo deverá trabalhar, se será como OUTPUT ou INPUT através do arquivo direction. Através do arquivo value, é possível realizar a leitura ou escrita no pino de acordo com as configurações definidas no método direction. O arquivo edge nos permite verificar se a tensão sofreu uma borda de subida, descida ou ambas, já o método active_low, permite a inversão dos níveis lógicos de leitura e/ou gravação.
Como mencionado anteriormente, todo o código foi desenvolvido em C e devido à sua extensão, ficou inviável a disponibilização do mesmo no corpo deste artigo.
Sendo assim, iremos citar os principais pontos necessários para configuração de um pino GPIO utilizando a biblioteca sysfs, desde o export do pino até o unexport, onde o controle do pino é liberado do espaço de usuário.
Por fim, disponibilizamos os arquivos fonte no github caso queiram utilizar o código desenvolvido para efetuar testes.
Exportando o pino
Antes mesmo de realizar as configurações dos pinos como entrada/saída, é necessário solicitar ao kernel a permissão de controle do pino desejado a nível de usuário, ou seja, é exportado o controle do pino para o espaço de usuário do sistema.
Após a exportação ser concluída, os pinos estarão disponíveis para serem utilizados e configurados.
Segue abaixo o exemplo de código desenvolvido em C que realiza a exportação do pino:
bool export_gpio(int pin) { arquive = open ("/sys/class/gpio/export", O_WRONLY); if (arquive==-1) { printf("Arquivo abriu incorretamente\n"); return false; } snprintf(buffer, 3, "%d", pin); if(write(arquive, buffer, 3) == -1) { close(arquive); return false; } close(arquive); return true; }
bool export_gpio(int pin)
arquive = open ("/sys/class/gpio/export", O_WRONLY);
if (arquive==-1)
printf("Arquivo abriu incorretamente\n");
snprintf(buffer, 3, "%d", pin);
if(write(arquive, buffer, 3) == -1)
close(arquive);
Configurando o pino como INPUT ou OUTPUT
Após a exportação do pino para o controle no espaço do usuário, o pino está pronto para ser configurado. Dessa forma, é necessário informar que tipo de configuração o pino deverá trabalhar, ou seja, se sua direção deverá ser configurada como INPUT ou OUTPUT.
Para ilustrar este processo, o código abaixo detalha a configuração da direção do pino:
bool direction_gpio(int pin, int direction) { arquive=0; snprintf(path, 35, "/sys/class/gpio/gpio%d/direction", pin); arquive = open (path, O_WRONLY); if (arquive==-1) { return false; } snprintf(buffer, 3, "%d", pin); if (write( arquive, ((direction == INPUT)?"in":"out"), 3 )==-1) { close(arquive); return false; } close(arquive); return true; }
bool direction_gpio(int pin, int direction)
arquive=0;
snprintf(path, 35, "/sys/class/gpio/gpio%d/direction", pin);
arquive = open (path, O_WRONLY);
if (write( arquive, ((direction == INPUT)?"in":"out"), 3 )==-1)
Efetuando a leitura
Quando o pino é configurado como INPUT, é possível realizar a leitura de valores 0V – baixo e 3,3V – alto de acordo com a configuração utilizada. Neste artigo utilizamos resistores de Pull-Up na entrada da porta, ou seja, sempre quando o botão estiver pressionado, o sinal baixo será injetado no pino, caso contrário permanecerá em estado alto.
Este processo de leitura está ilustrado no código abaixo:
int value_in_gpio(int pin, int value) { arquive=0; char retorno[3]; snprintf(path, 35, "/sys/class/gpio/gpio%d/value", pin); arquive = open(path, O_RDONLY); //printf("Descritor do arquivo: %d \n", arquive); if (arquive == -1) { return false; } if (read(arquive, retorno, 3) == -1) { close(arquive); return false; } close(arquive); printf("Valor do pino: %c \n", retorno[0]); return atoi(retorno); }
int value_in_gpio(int pin, int value)
char retorno[3];
snprintf(path, 35, "/sys/class/gpio/gpio%d/value", pin);
arquive = open(path, O_RDONLY);
//printf("Descritor do arquivo: %d \n", arquive);
if (arquive == -1)
if (read(arquive, retorno, 3) == -1)
printf("Valor do pino: %c \n", retorno[0]);
return atoi(retorno);
Escrita no pino
Quando o pino está configurado como OUTPUT, é possível efetuar a escrita dos valores zero e um na porta de acordo com a funcionalidade desejada, ou a atuação sobre determinada aquisição de sinal.
Este processo de escrita no pino, está ilustrado no código abaixo:
bool value_gpio(int pin, int value) { arquive=0; snprintf(path, 35, "/sys/class/gpio/gpio%d/value", pin); arquive = open(path, O_WRONLY); if (arquive == -1) { return false; } if (write (arquive, ((value == HIGH)?"1":"0"), 1) == -1) { close(arquive); return false; } close(arquive); return true; }
bool value_gpio(int pin, int value)
arquive = open(path, O_WRONLY);
if (write (arquive, ((value == HIGH)?"1":"0"), 1) == -1)
Unexport do pino
Por fim, após a desutilização do pino exportado, visando evitar que o mesmo esteja consumindo recursos da placa e do S.O, sempre é necessário que um unexport seja aplicado.
Para ilustrar esta ação, encaminhamos o código abaixo:
bool unexport_gpio(int pin) { arquive = open ("/sys/class/gpio/unexport", O_WRONLY); if (arquive==-1) { printf("Arquivo abriu incorretamente\n"); return false; } if(write(arquive, buffer, 3) == -1) { close(arquive); return false; } return true; }
bool unexport_gpio(int pin)
arquive = open ("/sys/class/gpio/unexport", O_WRONLY);
Neste tópico, será descrito os esquemas de ligação dos circuitos utilizados para simular o código desenvolvido e serão divididos em duas partes, a parte que representa a configuração do pino de GPIO como OUTPUT e a outra como INPUT.
Ligação do pino de GPIO24 como OUTPUT:
Neste cenário, foi utilizado um resistor de 680 Ohms, com o objetivo de limitar a corrente em aproximadamente 20 mA para proteção do led.
Figura 3 – GPIO24 como output
Ligação do pino de GPIO24 como INPUT:
Neste cenário, foi adotado um resistor de PULL-UP de 560 Ohms com o objetivo de limitar a corrente no pino em no máximo 6mA.
Com a utilização desta configuração, quando o botão estiver pressionado, o GPIO24 receberá um sinal comum do GND (0 V – baixo), caso contrário (botão não pressionado), o pino receberá o sinal do VCC (3,3 V – alto).
Figura 4 – Resistor de pull up configurando o GPIO24 como input
Operação equivalente no terminal
Com o objetivo de ilustrar todas as operações descritas anteriormente de uma forma mais palpável, desde o export do pino até o seu unexport, a Figura 5 exemplifica todos as ações realizadas através de comandos shell:
Figura 5 – Comando no terminal
O GPIO24 é exportado para o controle no espaço de usuário.
A direção do GPIO24 é configurada como saída.
É escrito no pino de GPIO24 os valores alto e baixo exemplificando um led alternando seu status de aceso para apagado.
Listagem da estrutura de diretórios presentes no controle de GPIO, com o intuito de ilustrar que o pino GPIO24 ainda encontra-se disponível para uso.
O GPIO24 é liberado após a utilização do pino, ou seja, o unexport é aplicado ao mesmo.
Após a liberação do pino, são listados os arquivos visando exemplificar que o pino de GPIO24 não está mais disponível para uso.
Código disponível no github
Informamos que os códigos completos para a configuração dos pinos de GPIO como INPUT/OUTPUT estão disponíveis em leal-freitas para utilização.
Relatamos também que este código foi homologado na Raspberry Pi 3 model B, portanto as macros de definição dos pinos de GPIO precisam ser configuradas de acordo com as características do modelo da placa utilizado.
Com o desenvolvimento deste artigo, ficou claro o quão poderosa é a biblioteca sysfs, que nos permite exportar informações do espaço de núcleo ao espaço do usuário aliada a qualquer linguagem que possuem as propriedades de manipulação dos arquivos, permitindo assim a interação entre o software/firmware presente no espaço do usuário e o hardware de uma forma amigável visto que trabalhar com arquivos, links simbólicos e diretórios são bem acessíveis para os desenvolvedores.
Também ficou evidente com este trabalho que ao utilizar linguagens de baixo nível, as mesmas nos permitem e proporcionam um maior controle sobre os periféricos evitando assim, dependências de funções terceiras.
Sou Engenheiro Eletricista formado pela PUCPR e atualmente aluno de especialização em Sistemas Embarcados na Universidade Positivo, tenho como paixão o desenvolvimento de soluções voltadas a automação residencial utilizando sistemas embarcados. Possuo interesse em aplicar medidas embarcadas em ambientes residências envolvendo o conceito de IoE.