Introdução
Em artigos anteriores no Embarcados: Zephyr no ESP32 e Zephyr no Ubuntu 16.04, foram mostradas formas de configurar a máquina para gerar um binário do RTOS Zephyr em placas de diferentes fabricantes.
Este artigo tem por objetivo mostrar como interagir com as GPIO’s de uma placa por meio do RTOS Zephyr (versão: v3.4.0, west-tool versão: v1.1.0). A placa utilizada no artigo é a STM32 Bluepill (microcontrolador STM32F103C8T6), no entanto, o artigo irá expor uma ideia de como realizar a mesma implementação em placas de diferentes fabricantes (seção “Descrição do hardware: Device tree overlay”).
Diagrama do hardware do projeto
O projeto apresentado no artigo tem por objetivo controlar um LED por meio de um botão do tipo “push button”. O LED é conectado a um pino da placa e declarado como OUTPUT, o botão conectado a outro pino da placa e declarado como INPUT.
O diagrama do circuito pode ser visto abaixo:
O pino B0 é o responsável por ligar/desligar o LED, enquanto o B1 é o responsável por fazer a leitura do estado do botão.
Configuração da aplicação
Inicialmente criamos um diretório (“button_sample”) e adicionamos a ele um arquivo na raiz, o “CMakeLists.txt”. É neste arquivo que indicamos ao Zephyr onde o arquivo-fonte vai estar localizado.
O conteúdo nele é:
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(button_sample)
target_sources(app PRIVATE src/main.c)
Ainda na raiz, adicionamos um arquivo “prj.conf”, que não será utilizado neste projeto. A finalidade deste arquivo é habilitar determinadas features a serem utilizadas pela aplicação.
Para fins de organização, criamos dentro do “button_sample”, mais dois diretórios: “src” e “boards”. “src” é onde o código-fonte da aplicação se encontra, “boards” é onde fica o arquivo .overlay da placa.
A estrutura do projeto nesse momento deve ser assim:
Descrição do hardware: Device tree overlay
No zephyr, o hardware utilizado é descrito por meio de uma estrutura de dados conhecida por “devicetree”. Para placas cujo suporte ao Zephyr já é existente, podemos criar um arquivo do tipo “.overlay” e declarar nossas modificações ao hardware. Este arquivo então será mesclado ao devicetree da placa durante a build, permitindo que a aplicação seja capaz de acessar os recursos de hardware declarados.
O devicetree da placa é obtido por meio do parâmetro “-b <NOME_DA_PLACA>” durante a build. Podemos determinar o devicetree da placa por meio do seu nome no Zephyr. Para tanto, no site do projeto, existe uma listagem de todas as placas suportadas. Neste artigo, estamos interessados no “STM32 Minimum Development Board”, visto que esta é referente à placa STM32 bluepill. Na página dessa placa, temos o nome da placa utilizado durante a build: “stm32_min_dev_blue”.
Com isso, o comando para compilar o binário é:
west build -p always -b stm32_min_dev_blue -- -DDTC_OVERLAY_FILE=boards/custom.overlay
Quando o comando acima for executado, veremos que o compilador de device tree irá mesclar o overlay junto ao .dts da placa alvo. O output da build contém essas informações, como pode ser visto abaixo:
No custom.overlay, estamos interessados em declarar os pinos B0 e B1, bem como atribuir a eles um “alias” para que estes pinos sejam facilmente acessados na aplicação.
O conteúdo do custom.overlay é:
/ {
leds {
compatible = "gpio-leds";
led_1: led_1 {
gpios = <&gpiob 0 GPIO_ACTIVE_HIGH>;
};
};
buttons {
compatible = "gpio-keys";
button_0: button_0 {
gpios = <&gpiob 1 GPIO_ACTIVE_HIGH>;
};
};
aliases {
led1 = &led_1;
button0 = &button_0;
};
};
Um ponto de atenção aqui é a forma com que cada GPIO é declarado, a propriedade “gpios” é declarada como um trio de termos, por exemplo: “<&gpiob 0 GPIO_ACTIVE_HIGH>”. O primeiro elemento (gpiob) é um “node label” que se refere à porta B, o segundo argumento (0 e 1) o pino em questão. O “&” prefixado é a forma para fazer referência ao nó referente à porta B. Para saber o “node label” da porta na placa, é preciso verificar o devicetree da placa.
De acordo com os logs da Figura 3 acima, o devicetree da placa é o arquivo boards/arm/stm32_min_dev/stm32_min_dev_blue.dts. Partindo desse arquivo, faremos sua análise a fim de encontrar o node label dos pinos. Esse arquivo inclui o “stm32_min_dev.dtsi”, que por sua vez inclui o “stm32_min_dev.dtsi”, que inclui o “stm32f103X8.dtsi” que inclui o “stm32f1.dtsi”. Este último arquivo é onde estão declaradas as GPIO’s, e ele é um arquivo comum a todas as placas da série STM32F1. Nele, podemos ver o node label atribuído ao endereço 0x40010c00 referente à porta B, que precisamos usar como o primeiro argumento no nosso overlay:
/ {
[...]
gpiob: gpio@40010c00 {
compatible = "st,stm32-gpio";
gpio-controller;
#gpio-cells = <2>;
reg = <0x40010c00 0x400>;
clocks = <&rcc STM32_CLOCK_BUS_APB2 0x00000008>;
};
[...]
};
Procedimento semelhante a este pode ser feito em placas de outras fabricantes: busca-se o nome da placa no site do projeto para a etapa de compilação. Em seguida, analisam-se os logs de compilação para encontrar o path do .dts da placa. Depois basta procurar no arquivo .dts e em seus includes o local em que os pinos são declarados.
Implementação da aplicação
Na versão utilizada neste artigo (v3.4.0), podemos utilizar uma macro (GPIO_DT_SPEC_GET) que irá fazer referência aos alias do overlay no código. Utilizamos-a deste modo:
#define LED0_NODE DT_ALIAS(led0)
#define LED1_NODE DT_ALIAS(led1)
#define BUTTON_NODE DT_ALIAS(button0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
static const struct gpio_dt_spec led_proto = GPIO_DT_SPEC_GET(LED1_NODE, gpios);
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(BUTTON_NODE, gpios);
Notar que “led0” é o alias dado ao LED nativo da placa (declarado em “stm32_min_dev.dtsi”), enquanto que “led1” e “button0” são aliases definidos no overlay. Cada gpio é declarado como sendo do tipo “struct gpio_dt_spec”. Uma vantagem de declará-los assim é a possibilidade em utilizar os métodos terminados em _dt da API de GPIO. Essa struct encapsula um ponteiro para uma “struct device”, o que torna as chamadas de métodos da API mais enxutas.
O objetivo da aplicação é alterar o acionamento do LED baseado em cada aperto do botão. É preciso então:
- Garantir que as GPIO estão prontas para uso
- Configurar o pino 1 (do botão) para ser do tipo INPUT
- Configurar os pinos 0 e 12 (LED na protoboard e LED da placa) para serem do tipo OUTPUT, inicialmente desligados
- Garantir que uma mudança de estado no pino 1 (botão) mude o estado dos LEDs.
Os pontos 1, 2 e 3 são obtidos por meio de chamadas aos métodos “gpio_is_ready_dt” e “gpio_pin_configure_dt”. O ponto 4 pode ser feito no Zephyr por meio de declaração de callback: toda vez que houver uma mudança no estado do pino 1, uma interrupção ocorrerá e a callback será executada. Isso pode ser feito por meio dos métodos “gpio_pin_interrupt_configure_dt”, “gpio_init_callback” e “gpio_add_callback_dt”.
Na callback, utilizamos o método “gpio_pin_toggle_dt” para alternar os estados dos LED’s. Um ponto a ser notado é que o circuito utilizado aqui é suscetível ao efeito de debounce, o que implica que a callback pode ser chamada mais de uma vez para um único aperto no botão.
No site do Zephyr existe um artigo dedicado a formas de tratar o debounce por meio de software, mas para fins de simplificação, neste caso utilizamos a técnica de validar um toque apenas em caso de chamadas superiores a um determinado delay pré-estabelecido:
static void button_handler(const struct device *port,
struct gpio_callback *cb,
gpio_port_pins_t pins) {
printk("Button Handler\n");
if (gpio_pin_get_dt(&button)) {
if (k_uptime_get() - last_read >= DEBOUNCE_DELAY) {
printk("Pressed\n");
gpio_pin_toggle_dt(&led);
gpio_pin_toggle_dt(&led_proto);
}
last_read = k_uptime_get();
}
}
No trecho acima, os LEDs alternam de estado apenas caso o período da última leitura for superior a um determinado valor em milissegundos (DEBOUNCE_DELAY), definido arbitrariamente em 50ms.
Compilado e gravado na placa, podemos ver o código em execução conforme visto abaixo:

O código completo para referência se encontra no repositório do github.
Conclusão
Neste artigo foram expostos alguns dos principais métodos da (atual) API de GPIO do Zephyr. Mostrou-se também uma forma para determinar os labels dos pinos de uma placa de desenvolvimento qualquer, para declaração no arquivo de overlay. Outras informações / formas de configurar as GPIO’s podem ser vistas por meio da documentação oficial do Zephyr.
Saiba Mais
Core Dump: Uma Ferramenta Poderosa para Debugar Programas no Zephyr com o ESP32
Zephyr RTOS – Debugando uma aplicação no ESP32 com o ESP-Prog









