ÍNDICE DE CONTEÚDO
O Zephyr OS é um RTOS de baixo footprint, com suporte para diversas arquiteturas, com foco em dispositivos embarcados e com baixos recursos. Ele é mantido pela Linux Foundation e possui adesão de grandes fabricantes, como Intel, Nordic, Google, Meta, Qualcomm, etc.
Para uma lista completa dos membros ativos do projeto, visite o site oficial.
Você pode encontrar mais detalhes sobre o RTOS na página da documentação do Zephyr OS.
A Espressif vem trabalhando para ampliar o suporte aos seus SoCs no Zephyr OS. Além do famoso ESP32, também existe suporte atualmente ao ESP32-S2 e ESP32-C3. Para entender como está o suporte atual para os SoCs da Espressif, recomendo o seguinte artigo (em inglês).
Neste artigo usarei a placa ESP32 DevKitC e o ESP-Prog da Espressif. Vamos ver como configurar seu ambiente de desenvolvimento, compilar uma aplicação que irá utilizar o PWM do ESP32 para piscar um LED, gravar sua placa de desenvolvimento e depurar ela utilizando o JTAG através do OpenOCD.
OpenOCD no Zephyr
O Open On-Chip Debugger (OpenOCD) é uma aplicação open source que implementa um servidor GDB remoto para uma variedade bem grande de dispositivos embarcados. Os módulos ESP32 precisam de alguns patches que não estão disponíveis na upstream do projeto. Por essa razão a Espressif mantém uma versão própria do repositório.
ESP-PROG
O ESP-Prog é uma ferramenta de desenvolvimento e depuração da Espressif que possui as funcionalidades de download automático do firmware, comunicação serial e depuração JTAG.
O ESP-Prog se conecta ao PC por apenas um cabo USB. O PC reconhece duas portas distintas, uma para download do firmware e outra para depuração JTAG.
Conhecendo a ESP-Prog
USB Bridge
O ESP-Prog usa o FT2232HL, fornecido pela FTDI, como a ponte controladora USB. A placa pode ser configurada para converter o USB 2.0 para interfaces seriais e paralelas que são padrão de industria.
Interface de Comunicação
O ESP-Prog pode se conectar com a ESP32 utilizando tanto a interface de programação, quanto a JTAG.
Interface de Programação
A interface possui 6 pinos, incluindo a UART (TX e RX), pino de seleção do modo de boot (ESPIO0) e pino de reset (ESPEN).
Interface JTAG
A interface JTAG possui a seguinte configuração de pinos:
Seleção de Tensão de Operação
Os jumpers localizados ao lado das interfaces, tanto a de programação, quanto a de JTAG fazem a seleção da tensão de operação, podendo ser selecionado 3.3V ou 5V.
Preparando o Hardware
Neste artigo vamos usar o ESP-Prog somente para fazer a depuração através do JTAG de uma aplicação PWM. Portanto, será necessário conectar o ESP-Prog ao ESP32 através da interface JTAG e um LED na nossa placa. Será necessário a utilização de dois cabos USB. Um para o ESP-Prog e outro para alimentar e gravar o ESP32.
ESP-Prog
Conecte os pinos da interface na DevKitC seguindo a tabela abaixo:
Tabela 1 – Conexão entre JTAG e DevKitC
ESP32 | Conector JTAG | Sinal JTAG |
GND | 3 | Terra Digital |
IO14 | 2 | Test Mode State |
IO13 | 4 | JTAG return Test Clock |
IO15 | 6 | Test Data Out |
IO12 | 8 | Test Data In |
Selecione a tensão de operação para 3.3V no jumper localizado ao lado do conector JTAG.
LED
Se a sua placa já vem com um LED montado, muito provavelmente ele estará conectado internamente ao pino 2, portanto esse passo não é necessário.
A DevKitC não possui um LED montado de fábrica, portanto devemos conectar um resistor (100 Ω a 1 kΩ) no pino 2 (IO2) da placa. O outro polo do resistor deve ir ao ânodo do Led (polo positivo), enquanto o cátodo (polo negativo) deve ser conectado ao GND da placa.
Obtendo o Zephyr OS
Caso você não tenha o Zephyr OS baixado em sua máquina, é só seguir os passos descritos na página oficial (Getting Started Guide). Seguindo esses passos você vai obter o código do Zephyr OS, a ferramenta WEST e o SDK do Zephyr. Após completar o guia, podemos dar continuidade instalando as ferramentas da Espressif.
Instalando as ferramentas da espressif
O HAL da Espressif no Zephyr OS precisa de alguns binários e outros utilitários para funcionar corretamente, como a versão da Espressif do OpenOCD. O comando a seguir executa todos os passos necessários para obtenção dessas ferramentas:
1 |
cd ~/zephyrproject<br>west espressif install |
Por padrão, o OpenOCD com suporte para as placas da espressif fica localizado em $HOME/.espressif/tools/zephyr.
Firmware
Neste artigo vamos utilizar a sample blinky_pwm, que já vem incluída na árvore do Zephyr como um dos exemplos. A pasta do projeto está localizada em samples/basic/blinky_pwm/
Arquivo de configuração
Antes de compilar nosso exemplo e gravar na nossa placa, vamos inserir uma opção do Zephyr de build otimizado para debug. No arquivo *samples/basic/blinky_pwm/prj.conf*, insira a linha:
1 |
CONFIG_DEBUG_OPTIMIZATIONS=y |
Build, Flash e Monitor
Agora podemos compilar nosso exemplo passando algumas informações sobre o OpenOCD. Vá para a pasta raiz do projeto (~/zephyrproject/zephyr) execute o comando de build:
1 |
cd ~/zephyrproject/zephyr<br>west build -b esp32 samples/basic/blinky_pwm -- -DOPENOCD="~/.espressif/tools/zephyr/openocd-esp32/bin/openocd" DOPENOCD_DEFAULT_PATH="~/.espressif/tools/zephyr/openocd-esp32/share" |
O Zephyr, por padrão, usa sua própria versão do OpenOCD. Precisamos portanto especificar para ele qual OpenOCD usar. Neste caso, queremos aquela com suporte às placas da Espressif.
Se a compilação for bem sucedida, você verá algo assim no terminal:
1 |
[194/194] Linking C executable zephyr/zephyr.elf<br>Memory region Used Size Region Size %age Used<br>mcuboot_hdr: 32 B 32 B 100.00%<br>metadata: 28 B 32 B 87.50%<br>ROM: 50116 B 4194240 B 1.19%<br>iram0_0_seg: 19488 B 128 KB 14.87%<br>irom0_0_seg: 22044 B 3342304 B 0.66%<br>dram0_0_seg: 3896 B 180736 B 2.16%<br>dram0_1_seg: 21 KB 110032 B 19.54%<br>drom0_0_seg: 5334 B 4194240 B 0.13%<br>rtc_iram_seg: 0 GB 8 KB 0.00%<br>rtc_slow_seg: 0 GB 4 KB 0.00%<br>IDT_LIST: 0 GB 8 KB 0.00%<br>esptool.py v3.3<br>Creating esp32 image…<br>Merged 8 ELF sections<br>Successfully created esp32 image. |
Para gravar o binário na placa, basta executar o comando:
1 |
west flash |
A mensagem no terminal será algo como:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Auto-detected Flash size: 4MB Flash will be erased from 0x00001000 to 0x00007fff... Flash will be erased from 0x00008000 to 0x00008fff... Flash will be erased from 0x00010000 to 0x00035fff... Flash params set to 0x0220 Wrote 32768 bytes at 0x00001000 in 0.5 seconds (507.1 kbit/s)... Hash of data verified. Wrote 16384 bytes at 0x00008000 in 0.2 seconds (597.5 kbit/s)... Hash of data verified. Wrote 163840 bytes at 0x00010000 in 1.9 seconds (686.0 kbit/s)... Hash of data verified. Leaving... Hard resetting via RTS pin... |
Para acessar a serial da sua ESP32 o seguinte comando pode ser usado:
1 |
west espressif monitor |
Resultando na seguinte mensagem no terminal em caso de sucesso:
1 |
*** Booting Zephyr OS build zephyr-v3.1.0-2181-ge4d11fe17775 ***<br>PWM-based blinky<br>Calibrating for channel 0…<br>Done calibrating; maximum/minimum periods 1000000000/7812500 usec |
Você verá seu LED piscando em frequências que irão aumentar a cada 4 segundos. Ele começa a piscar lentamente e vai aumentando sua frequência gradativamente até chegar em algum limite e voltar a diminuir a frequência de forma cíclica. Você pode perceber que nenhuma informação sobre o período configurado no PWM é indicado no console. Vamos descobrir quais valores estão sendo configurados através de uma sessão GDB utilizando o ESP-Prog.
Debugando a placa
Agora vamos debugar nossa placa utilizando o ESP-Prog. Para sair do monitor, pressiona as teclas ctrl + ] e execute o comando:
1 |
west debug |
Várias mensagens irão aparecer. Se necessário, pressione ENTER até chegar em algo como:
1 |
esp32.cpu0: Target halted, PC=0x40000400, debug_reason=00000000<br>Hardware assisted breakpoint 1 at 0x400d03d0: file /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c, line 23. |
O Zephyr automaticamente coloca um breakpoint na função main(). Os breakpoints são lugares (linhas ou funções) onde o programa para a execução e espera por um comando seu (para imprimir variáveis, para executar uma linha por vez, etc). Tal como a shell de Linux, o GDB é um programa interativo. A tabela abaixo mostra os comandos mais comuns:
Comando | Descrição |
b main | Coloca um breakpoint no início da aplicação |
b | Coloca um breakpoint na linha atual |
b N | Coloca um breakpoint na linha N |
b +N | Coloca um breakpoint N linhas abaixo da linha atual |
b fn | Coloca um breakpoint no início da função “fn” |
d N | Deleta o breakpoint “N” |
info break | Lista os breakpoints |
r | Executa a aplicação até um breakpoint ou erro |
c | Continua a execução da aplicação até um breakpoint ou erro |
f | Executa até o fim da função atual |
s | Executa a próxima linha (mas entra dentro de funções) |
s N | Executa as próximas N linhas |
n | Executa a próxima linha (não entra dentro de funções) |
p var | Mostra o valor atual da variável “var” |
display var | Mostra o valor da variável “var” sempre que parar |
q | Sai do GDB |
De volta para o exemplo, insira o comando continue na interface do GDB:
1 2 3 4 5 6 7 |
(gdb) c Continuing. Info : esp32.cpu0: Target halted, PC=0x400D03D0, debug_reason=00000001 esp32.cpu0: Target halted, PC=0x400D03D0, debug_reason=00000001 Temporary breakpoint 1, main () at /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c:23 23 { |
A partir desse ponto podemos executar nossos próprios comandos do GDB.
Vamos colocar um breakpoint na linha 61 do arquivo main.c da sample blinky_led. Nessa linha que é setado o período do sinal PWM e período do que compreende o pulso positivo:
1 |
ret = pwm_set_dt(&pwm_led0, period, period / 2U); |
Para isso, execute o seguinte comando:
1 2 |
(gdb) b 61 Breakpoint 2 at 0x400d04b0: file /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c, line 61. |
Vamos continuar a execução do programa até ele parar no breakpoint que colocamos:
1 2 3 4 5 6 7 8 |
(gdb) c Continuing. Note: automatically using hardware breakpoints for read-only addresses. Info : esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 Breakpoint 2, main () at /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c:61 61 ret = pwm_set_dt(&pwm_led0, period, period / 2U); |
Nesse ponto podemos ver qual o valor da variável period:
1 |
(gdb) p period<br>$1 = 1000000000 |
A API de PWM do Zephyr recebe os valores de período em nanosegundos. Um período de 1.000.000.000 ns equivale a 1 Hz. O terceiro argumento da função pwm_set_dt() recebe o tempo do pulso positivo do PWM, que neste caso é metade do período, indicando portanto um PWM com duty cycle de 50%.
Para que o valor dela seja mostrado toda vez que pararmos em algum breakpoint podemos utilizar o comando display:
1 |
(gdb) display period<br>1: period = 1000000000 |
Continuando a execução do programa por alguns ciclos:
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 38 39 40 |
(gdb) c Continuing. Info : esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 Breakpoint 2, main () at /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c:61 61 ret = pwm_set_dt(&pwm_led0, period, period / 2U); 2: period = 500000000 (gdb) c Continuing. Info : esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 Breakpoint 2, main () at /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c:61 61 ret = pwm_set_dt(&pwm_led0, period, period / 2U); 2: period = 250000000 (gdb) c Continuing. Info : esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 Breakpoint 2, main () at /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c:61 61 ret = pwm_set_dt(&pwm_led0, period, period / 2U); 2: period = 125000000 (gdb) c Continuing. Info : esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 Breakpoint 2, main () at /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c:61 61 ret = pwm_set_dt(&pwm_led0, period, period / 2U); 2: period = 62500000 (gdb) c Continuing. Info : esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 esp32.cpu0: Target halted, PC=0x400D04B0, debug_reason=00000001 Breakpoint 2, main () at /home/tambor/zephyrproject/samples/basic/blinky_pwm/src/main.c:61 61 ret = pwm_set_dt(&pwm_led0, period, period / 2U); 2: period = 31250000 |
Podemos verificar através do LED e da leitura da variável que a cada iteração da aplicação o período do PWM é diminuído pela metade, dobrando a frequência com que o LED pisca.
Para sair da sessão GDB basta inserir o comando quit:
1 2 3 4 5 6 7 8 9 10 |
(gdb) quit A debugging session is active. Inferior 1 [Remote target] will be detached. Quit anyway? (y or n) y Detaching from program: /home/tambor/zephyrproject/build/zephyr/zephyr.elf, Remote target [Inferior 1 (Remote target) detached] Info : dropped 'gdb' connection shutdown command invoked |
Conclusão
Vimos como obter o Zephyr OS, compilar uma aplicação, acessar a porta serial e realizar a depuração para o ESP32. Experimentamos como a infraestrutura que o Zephyr OS nos fornece simplifica esses processos através de simples comandos, bastando ter o hardware adequado. O suporte ao ESP32 e aos demais SoCs da Espressif no Zephyr OS, e em outros projetos de código aberto, vem aumentando consistentemente permitindo flexibilidade ao desenvolvedor de escolher a plataforma que mais se adequa às necessidades de seu projeto. Espero que este artigo seja instrutivo e lhe incentive a explorar mais profundamente as possibilidades que o Zephyr OS em conjunto com o ESP32 tem a oferecer.
Saiba Mais
Zephyr RTOS no ESP32 – Primeiros Passos
Blink LED no ESP32 e Zephyr RTOS
Zephyr RTOS – Instalando o Ambiente de Desenvolvimento
Referências
https://docs.zephyrproject.org/latest/boards/xtensa/esp32/doc/index.html
https://www.espressif.com/en/news/Zephyr_updates
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/jtag-debugging/index.html