2023-12-10
Faire clignoter une LED avec openocd
J’utilise openocd pour communiquer avec mes cartes stm32. N’utilisant ni tcl ni gdb (désolé pour les afficionados valencians j’ai ajouté ces lignes dans mon fichier de configuration:
gdb_port disabled
tcl_port disabled
Il ne me reste donc plus que le telnet en écoute sur le port 4444 pour dialoguer avec mes bestioles, ce que je fais à l’aide de nc:
$ echo 'reset halt' | nc -N 127.0.0.1 4444
$ echo "flash write_image erase ${PWD}/main.bin 0x8000000" | nc -N 127.0.0.1 4444
$ echo 'reset run' | nc -N 127.0.0.1 4444
Pris d’une inspiration aussi divine que soudaine j’ai dernièrement tenté un:
$ echo help | nc -N 127.0.0.1 4444
et quelle ne fût pas ma surprise devant cette avalanche de commandes ! Je suis loin d’en avoir fait le tour mais les suivantes ont attiré mon attention:
mdb ['phys'] address [count]
display memory bytes
mdd ['phys'] address [count]
display memory double-words
mdh ['phys'] address [count]
display memory half-words
mdw ['phys'] address [count]
display memory words
mwb ['phys'] address value [count]
write memory byte
mwd ['phys'] address value [count]
write memory double-word
mwh ['phys'] address value [count]
write memory half-word
mww ['phys'] address value [count]
write memory word
reset [run|halt|init]
Le manuel de référence indique, page 172, que la valeur initiale du Port configuration register high vaut 0x4444 4444. Du fait qu’elle soit facilement reconnaissable, c’est cette valeur que je vais tenter d’afficher. Autre information sur cette page, le décalage à appliquer depuis l’adresse de base du GPIO:
Address offset: 0x04
Page 51, on peut lire que l’adresse de base du GPIO Port C se trouve en 0x4001 1000, le registre de configuration se trouve donc en 0x4001 1004. Je vérifie:
$ echo 'reset halt' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> reset halt
target halted due to debug-request, current mode: Thread
xPSR: 00000000 pc: 00000000 msp: 00000000
$ echo 'reset init' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> reset init
target halted due to debug-request, current mode: Thread
xPSR: 00000000 pc: 00000000 msp: 00000000
$ echo 'mdw 0x40011004' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0x40011004
0x40011004: 44444444
Comme on dit du côté de Valence: Rodriguez ! Autre valeur particulière, page 1087, le DBGMCU_IDCODE à l’adresse 0xE004 2000:
$ echo 'mdw 0xE0042000' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0xE0042000
0xe0042000: 20036410
Il s’agit d’une révision 3 (2003) d’un “medium-density device” (0x410). La taille de la mémoire flash (page 1076) sur 16 bits (half-words):
$ echo 'mdh 0x1FFFF7E0' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdh 0x1FFFF7E0
0x1ffff7e0: 0080
Et bien sur l’identifiant de la carte, page (1077-1078), 2 x 16 bits + 2 * 32 bits:
$ echo 'mdh 0x1FFFF7E8 2' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdh 0x1FFFF7E8 2
0x1ffff7e8: 0048 0024
$ echo 'mdw 0x1FFFF7EC 2' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0x1FFFF7EC 2
0x1ffff7ec: 31333911 41373931
La LED PC13
Pour contrôler la PIN 13 du GPIO C je dois d’abord activer ce dernier. Page 106, dans le “APB2 peripheral reset register”, on peut voir que le GPIO C correspond au bit 4. Ce registre se situe à 0x18 de l’adresse de base de RCC (Reset and Clock Control) qui se trouve en 0x4002 1000 (page 50). Je commence par vérifier la valeur initiale puis j’active le GPIO:
$ echo 'mdw 0x40021018' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0x40021018
0x40021018: 00000000
$ echo 'mww 0x40021018 0x00000010' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mww 0x40021018 0x00000010
$ echo 'mdw 0x40021018' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0x40021018
0x40021018: 00000010
Maintenant que le GPIO est activé, je peux passer la PIN 13 en sortie (Output) depuis le registre de configuration (0x4001 1004, vu plus haut). Pour se faire, les bits 21 et 20 (le “mode”) devront avoir pour valeur respective 1 et 0:
$ echo 'mww 0x40011004 0x44644444' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mww 0x40011004 0x44644444
A ce moment la LED devrait être allumée. Pour changer l’état de la PIN j’utilise les bits 13 et 29 du registre “Port bit set/reset register”, page 173, dont le décalage vaut 0x10:
# bit 13 à 1, état haut, LED éteinte
$ echo 'mww 0x40011010 8192' | nc -N 127.0.0.1 4444
# bit 29 à 1, état bas, LED allumée
$ echo 'mww 0x40011010 536870912' | nc -N 127.0.0.1 4444
Pour conclure
Manipuler les bits et les adresses d’un micro-contrôleur reste pour moi une source d’émerveillement dont je doute me lasser un jour. Il existe un lien naturel entre ce monde et celui de l’informatique “générale” qu’illustre parfaitement cette (célèbre ?) sentence:
Fonctions, variables ? Mensonges ! Tout n'est qu'adresse -- iMil
Il ne me reste plus qu’à lire tranquillement la documentation officielle, la partie concernant Tcl me faisant de l’oeil …
Commentaires: https://github.com/bsdsx/blog_posts/issues/19
2022-11-02
FreeBSD et bluepill, corrections
J’ai raconté plusieurs conneries dans mon billet FreeBSD et bluepill qu’il me faut corriger.
openocd et mon adaptateur
Quand openocd affiche le message suivant:
$ openocd -f openocd.cfg -c "init"
Open On-Chip Debugger 0.11.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
Info : clock speed 1000 kHz
Info : STLINK V2J17S4 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.137032
Warn : UNEXPECTED idcode: 0x2ba01477
Error: expected 1 of 1: 0x1ba01477
Ce n’est pas l’adaptateur qui pose problème mais bien la bluepill comme on peut le lire sur hackaday.com:
[ snip bluepill version Chang ]
...
A major difference one will quickly encounter with this chip is when programming it and getting the message "UNEXPECTED idcode: 0x2ba01477". The reason for this is that the STM32F103 MCU reports the ID 0x1ba01477, confusing the programmer.
....
core.S
Toujours depuis ce fichier assembleur, avec une nouvelle modification en provenance directe du jardin magique (merki miod@ et semarie@): utiliser le préprocesseur pour définir la valeur du registre r7:
$ fetch -o - https://raw.githubusercontent.com/WRansohoff/STM32F0_minimal/master/core.S | sed -e 's/cortex-m0/cortex-m3/' -e 's/reset_handler/Reset_Handler/g' -e 's/_estack/__end_stack/' -e 's/0xDEADBEEF/R7_REGISTER/' > core.S
On compile avec l’option -x assembler-with-cpp et la valeur:
$ clang --target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -x assembler-with-cpp -DR7_REGISTER=0xDEADBEEF -c core.S -o core.o
On génère le binaire avec le linker script :
$ fetch -o stm32-base.zip https://github.com/STM32-base/STM32-base/archive/refs/heads/master.zip
$ tar xf stm32-base.zip STM32-base-master/linker
$ clang --target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -nostdlib -L STM32-base-master/linker -T STM32-base-master/linker/STM32F1xx/STM32F103xB.ld core.o -o core.elf
Pour vérifier la prise en compte de la valeur:
$ readelf -x .text main.elf
Hex dump of section '.text':
0x08000000 00500020 09000008 02488546 024f0020 .P. .....H.F.O.
0x08000010 401cfde7 00500020 efbeadde @....P. ....
Où efbeadde correspond bien à la version petit boutiste de DEADBEEF.
objcopy
Partie la plus sensible qui va me jouer des tours pendant plusieurs semaines, la conversion du .elf en .bin . On peut lire un peu partout cette commande:
$ objcopy -O binary main.elf main.bin
Mais sur FreeBSD on trouve 2 objcopy:
$ ls -l /usr/bin/*objcopy
-r-xr-xr-x 2 root wheel 4025248 May 21 09:32 /usr/bin/llvm-objcopy
-r-xr-xr-x 2 root wheel 129560 May 21 09:32 /usr/bin/objcopy
$ file /usr/bin/*objcopy
/usr/bin/llvm-objcopy: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, FreeBSD-style, stripped
/usr/bin/objcopy: ELF 64-bit LSB pie executable, x86-64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 13.1, FreeBSD-style, stripped
En partant d’un fichier source aussi simple que celui-ci:
void __libc_init_array(void) {} // startup_common.s
int integer_in_data_section = 1; // la section .data ne doit pas être vide
int
main(void) {
int i = 0;
while (1) {
i += integer_in_data_section;
}
return i;
}
Je commence par compiler un fichier assembleur correspondant à la carte:
$ tar xf stm32-base.zip STM32-base-master/startup
$ clang --target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -I STM32-base-master/startup -c STM32-base-master/startup/STM32F1xx/STM32F103xB.s -o startup.o
STM32-base-master/startup/startup_common.s:24:1: warning: Reset_Handler changed binding to STB_WEAK
.weak Reset_Handler
puis mon fichier source:
$ clang --target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -c main.c -o main.o
$ clang --target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -nostdlib -L STM32-base-master/linker -T STM32-base-master/linker/STM32F1xx/STM32F103xB.ld main.o startup.o -o main.elf
$ size main.elf
text data bss dec hex filename
416 4 1536 1956 0x7a4 main.elf
que je dois convertir en .bin :
$ objcopy -O binary main.elf main.bin
$ ls -l main.bin
-rwxr-xr-x 1 dsx wheel 402653188 2 nov. 19:39 main.bin
400 Mo pour incrémenter une variable, on dépasse un peu les bornes des limites. Avec le “bon” objcopy:
$ llvm-objcopy -O binary main.elf main.bin
$ ls -l main.bin
-rwxr-xr-x 1 dsx wheel 420 2 nov. 19:41 main.bin
A moi (enfin !) les leds qui clignottent !
Commentaires: https://github.com/bsdsx/blog_posts/issues/16
2022-10-09
FreeBSD et bluepill
A force de suivre les vidéos de Sieur Rancune j’ai fini par craquer pour une poignée de bluepill. Parce qu’il est conseillé sur tous les interweb d’utiliser arm-none-eabi-gcc pour programmer ces charmantes bestioles j’ai décidé d’utiliser clang/llvm livré de base.
Si allumer une led constitue le ‘Hello World!’ de l’électronique, j’ai découvert le niveau -1 en lisant cette série de billets: placer une valeur particulière dans un registre.
Les sources
Le fichier core.S requiert 3 modifications mineures:
adapter le type de cpu
modifier la casse de la fonction reset_handler
renommer une variable
$ fetch -o - https://raw.githubusercontent.com/WRansohoff/STM32F0_minimal/master/core.S | sed -e ‘s/cortex-m0/cortex-m3/’ -e ‘s/reset_handler/Reset_Handler/g’ -e ‘s/_estack/__end_stack/’ > core.S $ clang –target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -c core.S -o core.o
Le binaire
Pour que mon binaire final soit adapté à ma carte, je dois récupérer un ‘linker script’:
$ fetch https://github.com/STM32-base/STM32-base/archive/refs/heads/master.zip
$ tar xf master.zip STM32-base-master/linker
$ clang --target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -nostdlib -L STM32-base-master/linker -T STM32-base-master/linker/STM32F1xx/STM32F103xB.ld core.o -o main.elf
C’est pour se conformer à ce script qu’il faut modifier le fichier core.S.
Les soucis commencent
Avant d’envoyer le fichier sur la carte, il faut le convertir:
$ objcopy -O binary main.elf main.bin
$ du -Ah main.bin
384M main.bin
Là je crois qu’on dépasse les bornes des limites: ça fait beaucoup de méga pour placer une valeur dans un registre. Si je regarde mon fichier elf:
$ readelf -l main.elf | grep 0x
Entry point 0x8000009
LOAD 0x010000 0x08000000 0x08000000 0x0001c 0x0001c R E 0x10000
LOAD 0x020000 0x20000000 0x20000000 0x00600 0x00600 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0
ARM_EXIDX 0x01001c 0x0800001c 0x0800001c 0x00000 0x00000 R 0x4
Je peux me passer de GNU_STACK avec l’option -z nognustack:
$ clang --target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -z nognustack -nostdlib -L STM32-base-master/linker -T STM32-base-master/linker/STM32F1xx/STM32F103xB.ld core.o -o main.elf
$ readelf -l main.elf | grep 0x
Entry point 0x8000009
LOAD 0x010000 0x08000000 0x08000000 0x0001c 0x0001c R E 0x10000
LOAD 0x020000 0x20000000 0x20000000 0x00600 0x00600 RW 0x10000
ARM_EXIDX 0x01001c 0x0800001c 0x0800001c 0x00000 0x00000 R 0x4
Mais cela ne corrige pas le problème. J’ai fini par comprendre qu’une section n’avait pas le bon type:
$ readelf -S main.elf.OK | grep ._user_heap_stack
[ 6] ._user_heap_stack NOBITS 20000000 020000 000600 00 WA 0 0 1
$ readelf -S main.elf.KO | grep ._user_heap_stack
[ 7] ._user_heap_stack PROGBITS 20000000 020000 000600 00 WA 0 0 1
La correction la plus simple que j’ai trouvé est de passer cette section en ‘NOLOAD’:
$ grep ._user_heap_stack STM32-base-master/linker/common.ld
._user_heap_stack (NOLOAD) : {
$ clang --target=arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -z nognustack -nostdlib -L STM32-base-master/linker -T STM32-base-master/linker/STM32F1xx/STM32F103xB.ld core.o -o main.elf
$ readelf -S main.elf | grep ._user_heap_stack
[ 8] ._user_heap_stack NOBITS 20000000 020000 000600 00 WA 0 0 1
$ objcopy -O binary main.elf main.bin
$ du -Ah main.bin
512B main.bin
C’est déjà plus raisonnable.
Les soucis continuent
Il est temps de programmer la carte. J’utilise donc un adaptateur usb st link v2 et devel/openocd avec la configuration suivante:
$ cat openocd.cfg
source [find interface/stlink.cfg]
transport select hla_swd
source [ find target/stm32f1x.cfg]
reset_config none separate
Je connecte la bluepill à l’adaptateur, je branche l’adaptateur et paf:
$ openocd -f openocd.cfg -c "init"
Open On-Chip Debugger 0.11.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
Info : clock speed 1000 kHz
Info : STLINK V2J17S4 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.137032
Warn : UNEXPECTED idcode: 0x2ba01477
Error: expected 1 of 1: 0x1ba01477
Pour qu’openocd daigne utiliser mon adaptateur je dois lui dire qu’il est “de confiance” (kofkof):
$ cat openocd.cfg
set CPUTAPID 0x2ba01477
source [find interface/stlink.cfg]
transport select hla_swd
source [ find target/stm32f1x.cfg]
reset_config none separate
$ openocd -f openocd.cfg -c "init"
Open On-Chip Debugger 0.11.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
Info : clock speed 1000 kHz
Info : STLINK V2J17S4 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.138581
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f1x.cpu on 3333
Info : Listening on port 3333 for gdb connections
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Et quand y’en a plus y’en a encore
On va facilement trouver des exemples d’utilisation conjointe d’openocd et de gdb. Pas de bol pour moi, le debugger llvm n’est pas compatible, d’après https://stackoverflow.com/questions/36287351/how-to-setup-lldb-with-openocd-and-jtag-board:
"from what we were researching, it is not possible to debug remote (bare-metal!) targets with lldb without writing extra code."
Et autant dire que l’extra code en python, je passe.
Le bout du tunnel
Je vais donc utiliser l’interface “telnet” d’openocd:
$ echo 'reset halt' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> reset halt
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x1fffe61c msp: 0x20000180
$ echo 'stm32f1x mass_erase 0' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> stm32f1x mass_erase 0
device id = 0x20036410
flash size = 25616kbytes
stm32x mass erase complete
$ md5 main.bin
MD5 (main.bin) = 09c6d874e631d40370b70a7bcc120f41
$ echo 'flash write_image erase main.bin 0x8000000' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> flash write_image erase main.bin 0x8000000
auto erase enabled
wrote 1024 bytes from file main.bin in 0.087373s (11.445 KiB/s)
$ echo 'reset run' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> reset run
$ echo 'halt' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> halt
target halted due to debug-request, current mode: Handler HardFault
xPSR: 0x00000003 pc: 0x012fff1e msp: 0xe59efff8
$ echo 'reg' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> reg
===== arm v7m registers
(0) r0 (/32): 0x008fec14
(1) r1 (/32): 0x00000000
(2) r2 (/32): 0x00000000
(3) r3 (/32): 0x00000000
(4) r4 (/32): 0x00000000
(5) r5 (/32): 0x00000000
(6) r6 (/32): 0x00000000
(7) r7 (/32): 0xdeadbeef
(8) r8 (/32): 0x00000000
(9) r9 (/32): 0x00000000
(10) r10 (/32): 0x00000000
(11) r11 (/32): 0x00000000
(12) r12 (/32): 0x00000000
(13) sp (/32): 0x20005000
(14) lr (/32): 0xffffffff
(15) pc (/32): 0x08000012
(16) xPSR (/32): 0x01000000
(17) msp (/32): 0x20005000
(18) psp (/32): 0x00000000
(20) primask (/1): 0x00
(21) basepri (/8): 0x00
(22) faultmask (/1): 0x00
(23) control (/3): 0x00
===== Cortex-M DWT registers
Pour être sûr de mon coup, j’essaie avec les valeurs suivantes:
- OxCAFECAFE: MD5 (main.bin) = 45e3b36afd8ebd902675c26e804c21b5
- OxBABEBABE: MD5 (main.bin) = 4f03cd6b4a29ca80dc28fc49401cb1c5
- Ox12345678: MD5 (main.bin) = 908fc5196f85ade8321d1d951d8035f7
A moi les leds qui clignottent !
Commentaires: https://github.com/bsdsx/blog_posts/issues/15