Lendo um pino de I/O por interrupção no Linux

- por Sergio Prado

Categorias: Linguagem C, Linux Tags: , ,

O acesso a pinos de I/O (GPIO) no Linux é feito através de arquivos exportados no diretório /sys/class/gpio/. Para acessar um pino de I/O, o primeiro passo é exportá-lo escrevendo o número do pino de I/O no arquivo export. Depois é necessário configurar a direção do pino de I/O no arquivo direction e por fim ler ou alterar o estado do pino de I/O no arquivo value.

Por exemplo, para acionar o GPIO 53:

# echo 53 > /sys/class/gpio/export
# echo out > /sys/class/gpio/gpio53/direction
# echo 1 > /sys/class/gpio/gpio53/value

Da mesma forma, podemos ler o estado do GPIO 25 com os comandos abaixo:

# echo 25 > /sys/class/gpio/export
# echo in > /sys/class/gpio/gpio25/direction
# cat /sys/class/gpio/gpio25/value
0

Podemos também escrever um programa em linguagem C para ler o GPIO, usando as funções de acesso a arquivos da biblioteca do sistema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(int argc, const char *argv[])
{
    char buf[16];
    int fd, qtd;
 
    fd = open("/sys/class/gpio/export", O_WRONLY);
    write(fd, "25", 2);
    close(fd);
 
    fd = open("/sys/class/gpio/gpio25/direction", O_WRONLY);
    write(fd, "in", 2);
    close(fd);
 
    fd = open("/sys/class/gpio/gpio25/value", O_RDONLY);
    qtd = read(fd, buf, sizeof(buf) - 1);
    close(fd);
 
    buf[qtd] = '\0';
    printf("GPIO status is %s\n", buf);
 
    return 0;
}

E se quisermos monitorar o estado de um botão conectado a um GPIO para tomar alguma decisão quando o botão for pressionado? Podemos colocar a leitura do pino de I/O em um loop infinito, conforme exemplo abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(int argc, const char *argv[])
{
    char buf[16];
    int fd, qtd;
 
    fd = open("/sys/class/gpio/export", O_WRONLY);
    write(fd, "25", 2);
    close(fd);
 
    fd = open("/sys/class/gpio/gpio25/direction", O_WRONLY);
    write(fd, "in", 2);
    close(fd);
 
    for (;;) {
 
        fd = open("/sys/class/gpio/gpio25/value", O_RDONLY);
        qtd = read(fd, buf, sizeof(buf) - 1);
        close(fd);
 
        buf[qtd] = '\0';
 
        if (!strcmp(buf, '1'))
            printf("Botão pressionado!\n");
    }
 
    return 0;
}

O grande problema é que o código vai rodar em um loop infinito, lendo o pino de I/O indefinidamente e consumindo 100% de CPU!

Uma forma de minimizar este problema é ler o botão periodicamente, por exemplo a cada 100ms, utilizando as rotinas de espera da biblioteca do sistema (sleep(), usleep(), etc). Mas mesmo assim iremos desperdiçar um pouco de CPU a cada 100ms, além da possibilidade de perder algum evento se ele acontecer mais rápido que a periodicidade definida.

A melhor forma de resolver este problema é trabalhando por interrupção.

Para isso, precisamos habilitar a interrupção do pino de I/O no arquivo edge, que permite configurar como a interrupção será gerada. São suportados os valores “none” (interrupção desabilitada), “rising” (interrupção na transição para nível lógico alto), “falling” (interrupção na transição para nível lógico baixo) ou “both” (interrupção na transição para os níveis lógicos alto ou baixo).

Com o arquivo edge configurado, as interrupções no pino de I/O podem ser capturadas com a chamada de sistema poll(). Veja o exemplo completo abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int main(int argc, const char *argv[])
{
    struct pollfd fds[1];
    char aux[16];
    int fd;
 
    fd = open("/sys/class/gpio/export", O_WRONLY);
    write(fd, "25", 2);
    close(fd);
 
    fd = open("/sys/class/gpio/gpio25/direction", O_WRONLY);
    write(fd, "in", 2);
    close(fd);
 
    fd = open("/sys/class/gpio/gpio25/edge", O_WRONLY);
    write(fd, "rising", 6);
    close(fd);
 
    for (;;) {
 
        fds[0].fd = open("/sys/class/gpio/gpio25/value", O_RDONLY);
        fds[0].events = POLLPRI | POLLERR;
 
        /* need to read before polling */
        read(fds[0].fd, aux, sizeof(aux));
 
        /* block waiting for GPIO interrupt */
        poll(fds, 1, -1);
 
        if (fds[0].revents & POLLPRI)
            printf("Botão pressionado!\n");
 
        close(fds[0].fd);
    }
 
    return 0;
}

Não espere uma resposta determinística neste exemplo, afinal estamos trabalhando na camada de usuário com muitas chamadas de sistema e trocas de contexto envolvidas. De qualquer forma, é um código com baixa carga de processamento e 100% baseado em eventos, como todo código elegante deve ser. :-)

Happy coding!

Sergio Prado

Faça um Comentário

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.