2 de dezembro de 2021

Do bit Ao Byte

Embarcados, Linux e programação

Laboratório Maker 11: SWD debugger – Debug da Raspberry

Debug da Raspberry

Apesar de ser um artigo do Laboratório Maker, ele está diretamente relacionado à série sobre Debug da Raspberry que venho escrevendo. Nesse artigo vamos fazer uma ferramenta; um SWD debugger para debug da Raspberry e gravação de nossos programas, partindo de uma Raspberry Pi Pico para outra!

O legal de criar essa ferramenta é que poderemos depurar uma Raspberry Pi Pico de qualquer computador, não só do Raspberry Pi 400, como mostrado nos artigos anteriores. Claro, não tem como não recomendar a Raspberry Pi 400 da Saravati, que está com um preço ótimo. Além disso, eles tem também a Raspberry Pi Pico e com isso você já pode se presentear (ou presentear alguém) nesse natal (ou aniversário etc) com algo bem legal; ou até um kit com ambas!

Nos artigos anteriores mostrei como preparar o SDK na Raspberry Pi 400, mas como agora vamos depurar uma Raspberry Pi Pico utilizando outra Raspberry Pi Pico, não faz sentido que o processo seja feito a partir de uma Raspberry Pi 400, 4, 3, 2, 1, 0. Nesse caso, vamos ver primeiramente como preparar o SDK da Raspberry Pi Pico para computadores tradicionais.

Instalando o Pico SDK no Ubuntu

Esse procedimento serve para qualquer distribuição baseada em Debian (como o próprio Ubuntu o é). Vou descrever o procedimento de instalação em outras plataformas, pule para a sua caso não use Linux. Se bem que no Windows você pode usar WSL e ter um Linux instalado dentro do Windows, sem consumo excessivo de recursos e funcionando tudo nativamente. Vale experimentar.

Baixar o SDK e os exemplos

Vamos fazer no estilo “receita de bolo” para o artigo não ficar muito maior do que o necessário. Confie e proceda como descrito.

Crie a base:

cd ~/
mkdir pico
cd pico

Clone os repositórios:

sudo apt-get update && sudo apt-get install -y git
git clone -b master https://github.com/raspberrypi/pico-sdk.git
cd pico-sdk
git submodule update --init
cd ..
git clone -b master https://github.com/raspberrypi/pico-examples.git

Se desejar, temos alguns repositórios a mais; o pico-extras e o pico-playground. Mas sugiro que não se apegue a esses repositórios, já que vamos programar usando o VS Code com PlatformIO à posteriori. Utilizando PlatformIO, estaremos usando a API do Arduino e programaremos da forma tradicional, além de desfrutar das bibliotecas já disponíveis.

Instalar a toolchain

Quando criamos binários para uma plataform diferente daquela em que estamos programando, é chamado “cross-compiling” – ou em português, “compilação cruzada”. Não depende exclusivamente da plataforma, mas não vou entrar em detalhes. Aqui no blog tem diversos artigos específicos sobre toolchains, arquiteturas, descompilação de firmwares etc. Divirta-se!

Para a pico vamos usar o compilador gcc-arm-none-eabi. Embedded Application Binary Interface para eabi. Quando contém o none, não tem fornecedor nem sistema operacional alvo e está em conformidade com ARM. Quando não contém o none, então a compilação referenciará a libc do Linux, que implica em chamadas do kernel, como IOCTL, para alocação de páginas de memória para seu processo (porque todo o programa que roda no sistema operacional é um processo com identificação e tudo o mais referente à plataforma).

Me desculpe por ter escrito demais para passar essas duas linhas:

sudo apt update
$ sudo apt install cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential libstdc++-arm-none-eabi-newlib

Quando precisar atualizar o SDK

Essa dica é extra. Quando sair uma nova versão e precisar atualizar seu SDK, entre no diretório pico-sdk novamente e siga com os comandos para baixar e atualizar os submódulos:

cd pico-sdk
git pull
git submodule update

Testando o SDK da Pico

Hora do teste.

export PICO_SDK_PATH=~/pico/pico-sdk
cd ~/pico/pico-examples
mkdir build && cd build
cmake ..
Pico SDK

Sem erros? Tudo bem até aqui? Então apenas compile o blink:

cd blink
make

Ao final, um diretório contendo (entre outros) os arquivos blink.uf2 e blink.elf. Já expliquei em outros artigos, mas se por acaso é seu primeiro contato com essa Raspberry Pi Pico: O arquivo uf2 é para carregar pelo BOOTSEL. O elf é para depuração, subindo o firmware por SWD, como propósito desse artigo.

Pico blink

Se chegou até aqui sem erro, passemos para a fase seguinte. Mas antes, se quiser ter o path do SDK disponível sempre que abrir um terminal, adicione a linha export PICO_SDK_PATH=~/pico/pico-sdk ao arquivo ~/.bashrc. Simplesmente edite esse arquivo e adicione essa linha ao final. Depois, feche o terminal e abra novamente, ou então faça:

source ~/.bashrc

Se não sabe como editar o arquivo, deixe que o sistema decida por você:

xdg-open ~/.bashrc
Pico SDK Path

A última linha é a que importa e que você deve copiar.

Picotool

A picotool é uma ferramenta para inspeção de binários para RP2040. Ela também interage com essa MCU (que é a MCU do Raspberry Pi Pico) quando em modo BOOTSEL. A partir da versão 1.1 também é possível interagir com a RP2040 mesmo que não esteja no modo BOOTSEL, desde que usando o suporte ao USB stdio, usando a flag -f da picotool. Essa ferramenta é bem documentada.

Antes de vermos isso nesse ou em outro artigo, instale também as dependências. Em linux baseado em Debian (Ubuntu, por exemplo), faça:

sudo apt install build-essential pkg-config libusb-1.0-0-dev
cd ~/pico
git clone https://github.com/raspberrypi/picotool.git

Para Windows, instale a libusb daqui: https://libusb.info/

Agora a compilação:

cd picotool
mkdir build && cd build
cmake ..
make

Ainda no Windows, será necessário ajustar a variável de ambiente LIBUSB_ROOT. Se estiver usando MinGW/WSL:

mkdir build
cd build
cmake -G "NMake Makefiles" ..
nmake

A compilação é bem rápida e deve ter uma saída limpa e clara:

picotool - Debug da Raspberry

Gravar o PicoProbe na RPi Pico

Para transformar uma RPi Pico em um SWD debugger devemos clonar esse repositório e compilar o firmware. Mais uma vez, volte ao diretório base e proceda com os comandos abaixo:

cd ~/pico
git clone https://github.com/raspberrypi/picoprobe.git 
cd picoprobe 
mkdir build 
cd build 
cmake -G “Unix Makefiles” .. 
make

O parâmetro de -G é para Linux e MacOS. Se for usar Windows com MinGW, troque por “MinGW Makefiles”.

Agora basta copiar o firmware picoprobe.uf2 para a RPi Pico. O resultado da compilação deve ser limpo como:

picoprobe

A RPi Pico que se tornará um SWD debugger deve ser gravada da maneira tradicional, usando o BOOTSEL. Se não conhece o processo, é mais simples que qualquer placa. Tendo o firmware uf2, faça o seguinte:

  • Aperte o botão BOOTSEL da placa.
  • Com o botão apertado, conecte a placa à porta USB do computador.
  • Solte o botão.
  • Copie o firmware para o dispositivo de armazenamento que aparece

Ao arrastar o arquivo de firmware para o dispositivo que aparecerá como se fosse um pendrive, ele reiniciará automaticamente, pronto para uso.

Download do picoprobe.uf2

Se não quiser ter o trabalho de compilar (não sei qual seria a razão, mas…) baixe o firmware picoprobe compilado oficialmente. Estará no último tópico do subtítulo “Debugging using another Raspberry Pi Pico”. Para gravá-lo, não haverá diferença; proceda como supracitado.

Wiring do PicoProbe

picoprobe wiring

Dei um Ctrl+Chups do google images (ou foi da documentação? não lembro) e maquiei a imagem em um “pseudo Photoshop online grátis“. Essa imagem está horrível, mas há alguns meses venho fazendo todas as capas nesse programa, recomendo bastante. E é “for free”. Grátis mesmo.

Minha recomendação maior é (parafraseando a documentação): Faça o wiring diretamente na placa. Não use protoboard para não ter problema de sinal.

Me parece óbvio, mas acho melhor citar que à esquerda está a RPi Pico que gravamos o Picoprobe e à direita temos o Raspberry Pi Pico que será depurado, através dos pinos SWD.

OpenOCD com suporte a multidrop

Infelizmente o suporte à Raspberry Pi Pico ainda não está implementado oficialmente no OpenOCD. Faz um bom tempo já que saiu a Pico e, considerando a demora, não sei se veremos isso em um pacote nativo ou um fork no Linux. De qualquer modo, a compilação não é um trabalho complexo então arregace as mangas e bora fazer.

Antes que você coloque o carro na frente dos bois, não instale pelo apt porque é necessário compilar com suporte a multidrop. Vale sempre salientar, por mais que pareça repetitivo. Eu me repito, mas você não se frustra.

Para acessar a porta SWD de qualquer ARM que suporte SWD, será necessário ter um programa na máquina host chamado “debug translator”, que entende o protocolo SWD e save como controlar o processador. No caso da RP2040, ela é dual core, mas não se espante com esse controle. O ESP32 também é dual core e já mostrei como fazer debug usando o ESP-PROG e VS Code. E tem vídeo sobre a ESP-PROG no canal também. Só que para a RPi Pico tem esse trabalhinho extra.

Seguindo o padrão de construção, tudo dentro do diretório ~/pico:

sudo apt install automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev
cd ~/pico
git clone https://github.com/raspberrypi/openocd.git --recursive --branch rp2040 --depth=1
cd openocd
./bootstrap
./configure --enable-ftdi --enable-sysfsgpio --enable-bcm2835gpio
make
sudo make install

A saída do comando sudo make install deve ser limpa como essa:

Openocd

Instalando o GDB para fazer o debug da Raspberry

Esse é o debugger que iremos utilizar. No caso, a versão multi-arquitetura. A instalação é padrão do sistema, então é o processo mais simples desse tutorial.

sudo apt-get install gdb-multiarch

Debug da Raspberry Pi Pico no shell

Nada melhor que o teste nativo primeiro, porque quanto mais camadas de abstração, mais dependente de terceiros ficamos. Após gravar o firmware picoprobe, constatei pelo shell sua identificação, observando o arquivo de logs do sistema (no caso, Linux). Depois iniciei o OpenOCD por linha de comando.

sudo openocd -f interface/picoprobe.cfg -f target/rp2040.cfg

Os resultados:

Agora vamos conectar o GDB a essa ponte. Eu compilei o mesmo blink do vídeo sobre o debug na Raspberry Pi 400. Entrei no diretório em que estava o firmware (firmware.elf) e procedi com os seguintes comandos:

gdb-multiarch firmware.elf
target remote localhost:3333
load

Após digitar load e apertar [Enter], começará uma paginação gigantesca. Aperte c e [Enter] para continuar sem paginar. O firmware será carregado, daí podemos começar a enviar comandos. O código do blink que usei está mais abaixo. Coloquei breakpoints nas linhas 14 e 15:

b 14
b 15
monitor reset init
continue
n
n
n
n
n
n

Cada n faz ele avançar para o próximo (next). Foram necessários 6 passos desde o início do programa até a escrita no pino. Quando ele mudou o estado para HIGH, o LED permaneceu ligado, pois estava parado no breakpoint. Abaixo está o print desde o breakpoint e minha afobação dando “next” antes de iniciar o processo. Mas é legal de ver a verbosidade em caso de erro do usuário. Verbosidade é tudo!

Debug da Raspberry

Para monitorar uma variável, use watch variavel_desejada. Fiz alguns steps mais monitorando a função digitalWrite para ver o valor mudando de 0 para -1. Com -1 o LED se acende, assim como com 1.

A saída é tão verbosa que vemos claramente a chamada e os valores. Repare:

Debug da Raspberry

Fantástico ou não? Mas por que -1 invés de 1? Agora vamos ver o código desse blink para facilitar o entendimento.

Debug da Raspberry com o blink

#include <Arduino.h>

struct biting {
    int state :1;
} one_bit;

void setup() {
    pinMode(LED_BUILTIN,OUTPUT); //pin 25
    one_bit.state = 0;
}

void loop() {
  sleep_ms(200);
  one_bit.state += 1;
  digitalWrite(LED_BUILTIN, one_bit.state);
}

Repare na função loop() primeiro. Perceba que só há incremento da variável dentro da struct. Só há 1 delay. Só há 1 digitalWrite aplicando o valor do incremento. Como isso pode ser um blink? – Simples!

Na struct a variável continua sendo um inteiro, ocupando o tamanho de um inteiro, conforme a plataforma (2 Bytes, 4 Bytes etc). Porém, usando :1 limitei o uso da reserva de memória dessa variável a 1 bit. Um único bit comporta 0 ou 1, certo? Se a variável for 1 e adicionarmos mais 1, acontece o estouro de base. para ficar claro, pense em nossa base decimal. Temos dez números: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Se somarmos 9+1, teremos o estouro de base decimal. Nesse caso, simplesmente voltamos o valor a 0 e adicionamos o primeiro dígito à direita: 10. Só que reservamos um bit em nossa variável e não havia memória para alocar a soma, então estouramos a memória da variável e ela sempre vai para -1, por ser apenas 1 bit. Mas ao somarmos novamente, ele volta para 0. E assim temos o nosso blink funcional, como se nenhum erro estivesse acontecendo (mas está acontecendo de forma controlada).

Debug da Raspberry Pi Pico no VSCode

Já vimos como fazê-lo usando o Raspberry Pi 400. Esse vídeo tem quase 5 minutos. Já adianto; é um vídeo chato, mas informativo. E é complementar aos tutoriais relacionados ao debug da Raspberry Pi Pico.

Fiz o que pude para o debug acontecer, mas tudo resultou em erro. Abaixo, troquei picotool (já explicado anteriormente) por picoprobe, que é a o dispositivo usado para upload. Tentei usar ambos também em debug_tool, mas sem esperança em relação a isso, porque a própria documentação do PlatformIO não mostra um parâmetro para essa placa (o link está mais abaixo).

[env:pico]
platform        = raspberrypi
board           = pico
;adicione a linha abaixo:
upload_protocol = picotool

Pois é. Não tem um parâmetro de debug. Na documentação oficial do PlatformIO só tem alguns parâmetros; platform, board, board_build.mcu, board_build.f_cpu. Em upload_protocol temos picotool, mas não tem opção para debug_tool. Será que é “1-click”? – Não era. Só deu problema de diversos tipos, pesquisei bastante e não encontrei nada. Se alguém tiver solução para o problema e quiser compartilhar essa informação, posta em nossa página no facebook ou em um dos grupos. Ou ainda, comenta lá no canal dobitaobytebrasil no Youtube.

Debug pela IDE é muito melhor, sem dúvidas. Vou continuar em busca da solução, acompanhe aqui e no Youtube!