Le moyen standard (peu coûteux) de programmer les microcontrôleurs ARM consiste à utiliser Eclipse avec une chaîne d’outils complexe branchée. Eclipse a définitivement ses mérites, mais j'aimerais me sentir indépendant de cet IDE. J'aimerais découvrir ce qui se passe dans les coulisses lorsque je construis (compile-link-flash) mon logiciel et lorsque je lance une session de débogage. Pour obtenir une compréhension plus approfondie, il serait merveilleux d’exécuter toute la procédure à partir de la ligne de commande.
Remarque: J'utilise Windows 10 64 bits. Mais la plupart des choses expliquées ici s'appliquent également aux systèmes Linux. Veuillez ouvrir tous les terminaux de commande avec des droits d'administrateur. Cela peut vous épargner beaucoup de problèmes.
1. Construction du logiciel
La première "mission" est accomplie. Je suis maintenant en mesure de compiler et de lier mon logiciel en un binaire .bin
et une image .elf
via la ligne de commande. La clé du succès était de savoir où Eclipse mettait ses fichiers de création pour un projet spécifique. Une fois que vous savez où ils se trouvent, tout ce que vous avez à faire est d’ouvrir un terminal de commande et de taper la commande GNU make
.
Vous n'avez plus besoin d'Eclipse pour ça! Surtout si vous pouvez lire (et comprendre) le makefile et le modifier selon vos besoins lorsque votre projet avance.
Notez que j'ai trouvé les outils GNU (compilateur, éditeur de liens, utilitaire de création, GDB, ...) dans le dossier suivant, après l'installation de SW4STM32 (System Workbench for STM32):
C:\Ac6\SystemWorkbench\plugins\fr.ac6.mcu.externaltools.arm-none.win32_1.7.0.201602121829\tools\compiler\
Ensuite, j'ai créé un nouveau dossier sur mon disque dur et y ai copié tous ces GNU:
C:\Apps\AC6GCC
|-> arm-none-eabi
|-> bin
'-> lib
Et j'ajoute ces entrées à la "variable de chemin d'environnement":
- C:\Apps\AC6GCC\bin
- C:\Apps\AC6GCC\lib\gcc\arm-none-eabi\5.2.1
Huray, maintenant, je dispose de tous les GNU outils opérationnels sur mon système! Je mets le fichier build.bat
suivant dans le même dossier que le makefile
:
@echo off
echo.
echo."--------------------------------"
echo."- BUILD -"
echo."--------------------------------"
echo.
make -j8 -f makefile all
echo.
Exécuter ce bat-fichier devrait faire le travail! Si tout se passe bien, vous obtenez un .bin
et un .elf
fichier binaire à la suite de la compilation.
2. Clignotant et déboguant le firmware
L'étape suivante naturelle consiste à faire clignoter le micrologiciel sur la puce et à démarrer une session de débogage. Dans Eclipse, il suffit d'un clic sur un bouton, du moins si Eclipse est configuré correctement pour votre microcontrôleur. Mais que se passe-t-il dans les coulisses? J'ai lu (partiellement) le mémoire de thèse de Dominic Rath - le développeur d'OpenOCD. Vous pouvez le trouver ici: http://openocd.net/ . C'est ce que j'ai appris:
Eclipse lance le logiciel OpenOCD lorsque vous cliquez sur l’icône de «débogage». Eclipse fournit également certains fichiers de configuration à OpenOCD - de telle sorte qu'OpenOCD sache comment se connecter à votre microcontrôleur. "Comment se connecter" n'est pas une mince affaire. OpenOCD doit trouver le pilote USB approprié pour se connecter à l'adaptateur JTAG (par exemple, STLink). L’adaptateur JTAG et son pilote USB sont généralement fournis par le fabricant de votre puce (par exemple, STMicroelectronics). Eclipse transmet également à OpenOCD un fichier de configuration décrivant les spécifications du microcontrôleur. Une fois que OpenOCD est au courant de toutes ces choses, il peut établir une connexion JTAG fiable avec le périphérique cible.
OpenOCD démarre deux serveurs. Le premier est un serveur Telnet sur TCP port 4444. Il donne accès à OpenOCD CLI (interface de ligne de commande). Un client Telnet peut se connecter et envoyer des commandes à OpenOCD. Ces commandes peuvent être un simple "stop", "run", "set breakpoint", ...
De telles commandes pourraient suffire à déboguer votre microcontrôleur, mais de nombreuses personnes connaissaient déjà le débogueur Gnu (GDB). C'est pourquoi OpenOCD démarre également un serveur GDB sur TCP port 3333. Un client GDB peut se connecter à ce port et lancer le débogage du microcontrôleur!
Le débogueur Gnu est un logiciel en ligne de commande. Beaucoup de gens préfèrent une interface visuelle. C'est exactement ce que fait Eclipse. Eclipse démarre un client GDB qui se connecte à OpenOCD - mais tout cela est caché par l'utilisateur. Eclipse fournit une interface graphique qui interagit avec le client GDB en coulisse.
J'ai fait une figure pour expliquer toutes ces choses:
>> Démarrer OpenOCD
J'ai réussi à démarrer OpenOCD à partir de la ligne de commande. Je vais expliquer comment.
Ouvrez un terminal de commande et démarrez OpenOCD. Vous devrez fournir à OpenOCD quelques fichiers de configuration, de manière à ce qu’il sache où chercher votre microcontrôleur. Généralement, vous devez fournir un fichier de configuration décrivant le programmeur JTAG et un fichier de configuration définissant votre microcontrôleur. Transmettez ces fichiers à OpenOCD avec l’argument -f
dans la ligne de commande. Vous devrez également donner à OpenOCD l’accès au dossier scripts
en le transmettant avec l’argument -s
. Voici comment je lance OpenOCD sur mon ordinateur avec la ligne de commande:
> "C:\Apps\OpenOCD-0.9.0-Win32\bin\openocd" -f "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts\interface\stlink-v2.cfg" -f "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts\target\stm32f7x.cfg" -s "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts"
Si vous avez démarré OpenOCD correctement (avec les arguments corrects), il démarrera avec le message suivant:
Open On-Chip Debugger 0.9.0 (2015-08-15-12:41)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.231496
Info : stm32f7x.cpu: hardware has 8 breakpoints, 4 watchpoints
Info : accepting 'gdb' connection on tcp/3333
Info : flash size probed value 1024
Notez que la fenêtre de votre terminal est maintenant bloquée. Vous ne pouvez plus taper de commandes. Mais c'est normal. OpenOCD s'exécute en arrière-plan et bloque le terminal. Maintenant, vous avez deux options pour interagir avec OpenOCD: vous démarrez une session Telnet dans un autre terminal et vous vous connectez au TCP port localhost:4444
pour pouvoir donner des commandes à OpenOCD et recevoir des commentaires. Ou vous démarrez une session client GDB et connectez-la à TCP port localhost:3333
.
>> Démarrer une session Telnet pour interagir avec OpenOCD
Voici comment démarrer une session Telnet pour interagir avec le programme OpenOCD en cours d’exécution:
> dism /online /Enable-Feature /FeatureName:TelnetClient
> telnet 127.0.0.1 4444
Si cela fonctionne bien, vous recevrez le message suivant sur votre terminal:
Open On-Chip Debugger
> ..
Et vous êtes prêt à envoyer des commandes à OpenOCD! Mais je vais maintenant passer à la session GDB, car c’est le moyen le plus pratique d’interagir avec OpenOCD.
>> Démarrer une session client GDB pour interagir avec OpenOCD
Ouvrez encore une autre fenêtre de terminal et tapez la commande suivante:
> "C:\Apps\AC6GCC\bin\arm-none-eabi-gdb.exe"
Cette commande démarre simplement le client GDB arm-none-eabi-gdb.exe
. Si tout se passe bien, GDB démarre avec le message suivant:
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20151217-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--Host=i686-w64-mingw32 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos Word" to search for commands related to "Word".
(gdb)..
Maintenant connectez ce client GDB au serveur GDB dans OpenOCD:
(gdb) target remote localhost:3333
Maintenant vous êtes connecté à OpenOCD! Bon à savoir: si vous souhaitez utiliser une commande OpenOCD native (comme vous le feriez dans une session Telnet), il suffit de faire précéder la commande du mot clé monitor
. De cette manière, le serveur GDB dans OpenOCD ne gérera pas la commande lui-même, mais le transmettra au démon OpenOCD natif.
Il est donc temps de réinitialiser la puce, de l’effacer et de l’arrêter:
(gdb) monitor reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0xfffffffe msp: 0xfffffffc
(gdb) monitor halt
(gdb) monitor flash erase_address 0x08000000 0x00100000
erased address 0x08000000 (length 1048576) in 8.899024s (115.069 KiB/s)
(gdb) monitor reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0xfffffffe msp: 0xfffffffc
(gdb) monitor halt
La puce est maintenant prête à recevoir des instructions de notre part. Tout d'abord, nous allons dire à la puce que ses sections flash 0 à 7 (c'est-à-dire toutes les sections flash de ma puce 1Mb) ne doivent pas être protégées:
(gdb) monitor flash protect 0 0 7 off
(gdb) monitor flash info 0
#0 : stm32f7x at 0x08000000, size 0x00100000, buswidth 0, chipwidth 0
# 0: 0x00000000 (0x8000 32kB) not protected
# 1: 0x00008000 (0x8000 32kB) not protected
# 2: 0x00010000 (0x8000 32kB) not protected
# 3: 0x00018000 (0x8000 32kB) not protected
# 4: 0x00020000 (0x20000 128kB) not protected
# 5: 0x00040000 (0x40000 256kB) not protected
# 6: 0x00080000 (0x40000 256kB) not protected
# 7: 0x000c0000 (0x40000 256kB) not protected
Ensuite, j'arrête à nouveau la puce. Juste pour être sûr..
(gdb) monitor halt
Enfin, je remets le fichier binaire .elf
à GDB:
(gdb) file C:\\..\\myProgram.elf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from C:\..\myProgram.elf ...done.
C'est maintenant le moment de vérité. Je demande à GDB de charger ce binaire dans la puce. Doigts croisés:
(gdb) load
Loading section .isr_vector, size 0x1c8 lma 0x8000000
Loading section .text, size 0x39e0 lma 0x80001c8
Loading section .rodata, size 0x34 lma 0x8003ba8
Loading section .init_array, size 0x4 lma 0x8003bdc
Loading section .fini_array, size 0x4 lma 0x8003be0
Loading section .data, size 0x38 lma 0x8003be4
Error finishing flash operation
Malheureusement, cela n'a pas réussi. Je reçois le message suivant dans OpenOCD:
Error: error waiting for target flash write algorithm
Error: error writing to flash at address 0x08000000 at offset 0x00000000
EDIT: problème matériel résolu.
Apparemment, c'était un problème matériel. Je n'avais jamais pensé que ma puce serait défectueuse, car charger le fichier binaire sur la puce avec l'outil STLink Utility fonctionnait sans problème. Seul OpenOCD se plaignait et donnait des erreurs. Alors naturellement, j'ai blâmé OpenOCD - et non la puce elle-même. Voir ma réponse ci-dessous pour plus de détails.
EDIT: Autre manière élégante de flasher la puce - en utilisant makefile!
Comme le problème a été résolu, je vais maintenant me concentrer sur un autre moyen d’exécuter le flash et le débogage de la puce. Je pense que c'est vraiment intéressant pour la communauté!
Vous avez peut-être remarqué que j'ai utilisé les commandes Windows cmd pour exécuter toutes les étapes nécessaires. Cela peut être automatisé dans un fichier de commandes. Mais il existe un moyen plus élégant: tout automatiser dans un fichier makefile! M./Mess. Othane a suggéré le fichier Make suivant pour son Cortex-M? puce. Je suppose que la procédure pour une puce Cortex-M7 est très similaire:
#################################################
# MAKEFILE FOR BUILDING THE BINARY #
# AND EVEN FLASHING THE CHIP! #
# Author: Othane #
#################################################
# setup compiler and flags for stm32f373 build
SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
CROSS_COMPILE ?= arm-none-eabi-
export CC = $(CROSS_COMPILE)gcc
export AS = $(CROSS_COMPILE)gcc -x assembler-with-cpp
export AR = $(CROSS_COMPILE)ar
export LD = $(CROSS_COMPILE)ld
export OD = $(CROSS_COMPILE)objdump
export BIN = $(CROSS_COMPILE)objcopy -O ihex
export SIZE = $(CROSS_COMPILE)size
export GDB = $(CROSS_COMPILE)gdb
MCU = cortex-m4
FPU = -mfloat-abi=hard -mfpu=fpv4-sp-d16 -D__FPU_USED=1 -D__FPU_PRESENT=1 -DARM_MATH_CM4
DEFS = -DUSE_STDPERIPH_DRIVER -DSTM32F37X -DRUN_FROM_FLASH=1 -DHSE_VALUE=8000000
OPT ?= -O0
MCFLAGS = -mthumb -mcpu=$(MCU) $(FPU)
export ASFLAGS = $(MCFLAGS) $(OPT) -g -gdwarf-2 $(ADEFS)
CPFLAGS += $(MCFLAGS) $(OPT) -gdwarf-2 -Wall -Wno-attributes -fverbose-asm
CPFLAGS += -ffunction-sections -fdata-sections $(DEFS)
export CPFLAGS
export CFLAGS += $(CPFLAGS)
export LDFLAGS = $(MCFLAGS) -nostartfiles -Wl,--cref,--gc-sections,--no-warn-mismatch $(LIBDIR)
HINCDIR += ./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/CMSIS/Include/ \
./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/CMSIS/Device/ST/STM32F37x/Include/ \
./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/STM32F37x_StdPeriph_Driver/inc/ \
./
export INCDIR = $(patsubst %,$(SELF_DIR)%,$(HINCDIR))
# openocd variables and targets
OPENOCD_PATH ?= /usr/local/share/openocd/
export OPENOCD_BIN = openocd
export OPENOCD_INTERFACE = $(OPENOCD_PATH)/scripts/interface/stlink-v2.cfg
export OPENOCD_TARGET = $(OPENOCD_PATH)/scripts/target/stm32f3x_stlink.cfg
OPENOCD_FLASH_CMDS = ''
OPENOCD_FLASH_CMDS += -c 'reset halt'
OPENOCD_FLASH_CMDS += -c 'sleep 10'
OPENOCD_FLASH_CMDS += -c 'stm32f1x unlock 0'
OPENOCD_FLASH_CMDS += -c 'flash write_image erase $(PRJ_FULL) 0 ihex'
OPENOCD_FLASH_CMDS += -c shutdown
export OPENOCD_FLASH_CMDS
OPENOCD_ERASE_CMDS = ''
OPENOCD_ERASE_CMDS += -c 'reset halt'
OPENOCD_ERASE_CMDS += -c 'sleep 10'
OPENOCD_ERASE_CMDS += -c 'sleep 10'
OPENOCD_ERASE_CMDS += -c 'stm32f1x mass_erase 0'
OPENOCD_ERASE_CMDS += -c shutdown
export OPENOCD_ERASE_CMDS
OPENOCD_RUN_CMDS = ''
OPENOCD_RUN_CMDS += -c 'reset halt'
OPENOCD_RUN_CMDS += -c 'sleep 10'
OPENOCD_RUN_CMDS += -c 'reset run'
OPENOCD_RUN_CMDS += -c 'sleep 10'
OPENOCD_RUN_CMDS += -c shutdown
export OPENOCD_RUN_CMDS
OPENOCD_DEBUG_CMDS = ''
OPENOCD_DEBUG_CMDS += -c 'halt'
OPENOCD_DEBUG_CMDS += -c 'sleep 10'
.flash:
$(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_FLASH_CMDS)
.erase:
$(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_ERASE_CMDS)
.run:
$(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_RUN_CMDS)
.debug:
$(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_DEBUG_CMDS)
Cher Monsieur/Mess. Othane, pourriez-vous expliquer comment utiliser ce makefile pour les étapes suivantes:
Je connais quelques bases sur les makefiles, mais votre makefile est vraiment très profond. Vous semblez utiliser certaines fonctionnalités de l'utilitaire GNU make. S'il vous plaît donnez-nous plus d'explications, et je vous accorderai le bonus ;-)
Si je me souviens bien, j’ai eu quelques problèmes avec la commande straight load aussi, j’ai donc basculé sur "flash write_image erase my_project.hex 0 ihex" .. évidemment, j’utilisais des fichiers hex, mais il semble que les fichiers elf devraient fonctionner, voir http://openocd.org/doc/html/Flash-Commands.html ... la bonne chose à propos de cette commande est qu'elle efface également uniquement les sections flash écrites, ce qui est vraiment pratique et ce n'est pas le cas. besoin d'un effacement
Avant d'exécuter la commande ci-dessus, vous devez exécuter "stm32f1x unlock 0" pour vous assurer que la puce est déverrouillée et que vous êtes autorisé à effectuer le câblage sur la mémoire flash. Voir la fiche technique correspondante.
De plus, pour commencer, la commande "stm32f1x mass_erase 0" effacera la puce complètement et rapidement, il est donc préférable de démarrer dans un état connu.
Je sais que certaines de ces commandes disent qu'ils sont pour les F1, mais croyez-moi, ils travaillent pour la série F4
Btw, ce fichier contient la plupart des commandes que j’utilise pour flasher mon f4, c’est donc une bonne référence https://github.com/othane/mos/blob/master/hal/stm32f373/stm32f373.mk
J'espère que ça vous décoiffe
Ceci est un peu bref et pas très bon style stackoverflow mais je vous indiquerais mon code où je l’ai configuré pour ma bibliothèque "mos" pour les STM32F4 et STM32F1 ( https://github.com/othane/mos ) ... C’est un sujet important à répondre, alors je pense qu’un exemple serait mieux
En bref, mon projet est un arbre de Makefiles, puisque vous avez votre code compilant le principal d’intérêt qui vous intéresse se trouve ici https://github.com/othane/mos/blob/master/hal/stm32f373/stm32f373 .mk ... en gros, vous avez besoin d’openocd, puis j’ai une série de commandes permettant d’effacer la puce, ou de flasher et de déboguer le nouveau code, etc. en tapant simplement make .erase ou make .flash ou make .debug
enfin si vous regardez dans mes tests unitaires (ce sont essentiellement des exemples de programmes), vous trouverez le Makefile pour le construire + un fichier gdbinit comme celui-ci https://github.com/othane/mos/blob/master/utest/ gpio/debug_gpio_utest.gdbinit ... alors vous faites simplement "make && make .flash && make .debug" dans un terminal et appelez votre compilateur de croix gdb comme ceci "arm-none-eabi-gdb -x ./debug_gpio_utest .gdbinit "dans un autre ... cela démarrera gdb après le flashage du code et vous pouvez utiliser les commandes normales break et list de gdb, etc. pour interagir avec le code (remarquez comment j'ai défini une commande de réinitialisation dans le fichier .gdbinit, consultez le aide pour la commande mon ... fondamentalement, il vous permettra d’envoyer des commandes directement à opendd via gdb (c’est vraiment utile)
Désolé, la réponse est assez brève et contient beaucoup de liens, mais j'espère que cela vous aidera.
À première vue, la distribution sur gnutoolchains.com devrait être suffisante. Il existe un certain nombre de scripts de compilation pour créer votre propre version. J'ai le mien d'inclure l'ARM7TDMI. Cela fonctionne bien sous Linux et FreeBSD mais MinGW a échoué la dernière fois que je l’ai essayée :-(
En ce qui concerne OpenOCD, je vous recommande de le démarrer dans le même répertoire que votre instance GDB, afin que le téléchargement binaire semble transparent si vous l'appelez depuis GDB (le moyen le plus simple). Vous avez également la possibilité de créer un script qui lance OpenOCD et charge le code, mais vous devrez le redémarrer après chaque compilation.
Vous venez maintenant d'appeler "gdb" et de le connecter au "serveur distant" (localhost si le serveur et gdb s'exécutent sur le même ordinateur). Configurez GDB de sorte qu'il connaisse l'emplacement du code source et l'emplacement du fichier ELF. Il y a une tonne de sites Web qui passent par l'utilisation de base de GDB.
Il semble y avoir un GDB pour Windows ( http://www.equation.com/servlet/equation.cmd?fa=gdb )
Les commandes suivantes dans GDB devraient vous aider à démarrer:
cible localhost distant: 3333
répertoire/chemin/vers/projet
fichier de symboles /path/to/project.elf
Apparemment, c'était un problème matériel. Je n'avais jamais pensé que ma puce serait défectueuse, car charger le fichier binaire sur la puce avec l'outil STLink Utility fonctionnait sans problème. Seul OpenOCD se plaignait et donnait des erreurs. Alors naturellement, j'ai blâmé OpenOCD - et non la puce elle-même.
Aujourd'hui, j'ai essayé la même procédure avec une nouvelle puce sur le tableau, et maintenant cela fonctionne!
J'obtiens la sortie suivante en GDB lors de l'exécution de la commande load
:
(gdb) load
Loading section .isr_vector, size 0x1c8 lma 0x8000000
Loading section .text, size 0x39e0 lma 0x80001c8
Loading section .rodata, size 0x34 lma 0x8003ba8
Loading section .init_array, size 0x4 lma 0x8003bdc
Loading section .fini_array, size 0x4 lma 0x8003be0
Loading section .data, size 0x38 lma 0x8003be4
Start address 0x8003450, load size 15388
Transfer rate: 21 KB/sec, 2564 bytes/write.
(gdb)
Merci à tous ceux qui ont fait de leur mieux pour m'aider :-)