samedi 30 mars 2019

STM32 : environnements de développement




STM32 : environnements de développement


Cet article décrit les différentes possibilités de travailler avec un STM32. Il décrit brièvement les environnements existants sur le marché mais pas seulement.

Quand on travaille sur un STM32 plusieurs possibilités s'offrent à nous :
  • environnement graphique intégré
  • Makefile
  • IDE ARDUINO
Cet article fait suite à ceux-ci :
https://riton-duino.blogspot.com/2018/03/stm32f103c8t6-et-arduino.html
https://riton-duino.blogspot.com/2019/03/stm32-duino-deboguer.html

https://riton-duino.blogspot.com/2019/03/stm32-boitier-st-link.html

1. Les librairies

Les principales librairies (SDK) avec lesquelles on peut travailler sont peu nombreuses.

1.1. STM32Cube

Il s'agit des librairies ST Microlectronics.

Elles sont assez bas niveau, et leur utilisation demande beaucoup de travail.
Elles sont téléchargeables ici :

https://www.st.com/en/embedded-software/stm32cube-mcu-mpu-packages.html

Il y a une librairie par famille de processeur : F0, F1, F3, L0, L4, etc.

Ces librairies sont utilisées par la plupart de environnements du marché.

1.2. MBED

MBED vous offrira un ensemble de librairies permettant de gérer des afficheurs, des modules de communication et des composants divers.

https://os.mbed.com/handbook/mbed-SDK

Ce SDK est supporté nativement par PlatformIO, mais peut aussi être utilisé avec KEIL et IAR.

2. L'environnement intégré

Si vous travaillez avec un environnement évolué comme KEIL, SW4STM32, ATOLLIC ou IAR tout le travail de choix des options et de compilation se passe de manière plutôt transparente pour vous, et tant mieux.Vous avez simplement à faire quelques choix dans l'interface :
  • modèle de processeur
  • version debug ou non 
  • choix du boîtier JTAG
  • etc.
Ensuite vous n'avez plus qu'à cliquer sur un bouton pour compiler et charger dans la cible. Quelques autres boutons vous permettront de déboguer.

Une liste des environnements est fournie ici :

https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools.html

Normalement, certaines librairies ARDUINO devraient fonctionner avec ces environnements, par contre il faudra les intégrer vous-même à vos projets, et gérer les dépendances (certaines librairies ont besoin d'autres librairies). L'opération ne sera pas aussi facile qu'avec l'IDE ARDUINO et STM32DUINO.

Par contre vous aurez un debugger intégré, ce qui est quand même un gros avantage.

Vous pouvez essayer ces environnements de développement et leur debugger et si votre choix se porte sur l'un d'eux.

2.1. KEIL µVision

C'est un environnement simple et rapide à maîtriser. Il utilise le compilateur ARM.

Malheureusement il est seulement disponible sous Windows.

KEIL est gratuit si vos projets ont une taille inférieure à 32K de code (binaire bien entendu).

2.2. SW4STM32 et ATOLLIC

SW4STM32 et Atollic sont les environnements de ST Microlectronics. Ils sont basés sur Eclipse.

SW4STM32 et ATOLLIC sont gratuits. Il fonctionnent aussi sous LINUX.

2.3. IAR

IAR est payant.

2.4. PlatformIO

PlatformIO est basé sur Visual SourceCode, un clone d'Atom.

Le gros intérêt est qu'il n'est pas limité au STM32. Il permet de travailler avec différents processeurs :
  • ARDUINO
  • ESP8266, ESP32
  • MSP430
Il permettra de travailler avec diffrents frameworks ou SDK :
  • STM32DUINO
  • MBED
  • CMSIS
  • SPL
  • STM32Cube 
Il est de loin mon choix préféré, y compris en tant que simple éditeur.

3. Le Makefile

Certains développeurs travaillent avec un éditeur de leur choix (Visual Studio, Eclipse, etc.) et compilent leur code en ligne de commande. Ils écrivent des fichiers de script appelés Makefiles qui se chargent de la compilation et de l'édition de liens.
Le Makefile peut également charger le binaire dans la cible.

Dans certaines grosses équipe un développeur spécialisé est chargé d'écrire les Makefiles.

Contrairement à ce que l'on peut croire ce n'est pas une manière arriérée de travailler. C'est au contraire la seule manière correcte de travailler en équipe, car en général un serveur d'intégration continue se charge de recompiler (build) le travail de toute l'équipe quand le code est modifié.
De plus le serveur se charge en général d'exécuter des tests automatisés sur le code produit.

Les membres de l'équipe sont avertis en temps réel par mail si le build échoue.

4. L'IDE ARDUINO

L'IDE ARDUINO est un peu à part. Il est plutôt destiné aux amateurs et décharge ceux-ci de pas mal de connaissances liées aux environnements habituels(build automatique, intégration de librairies, etc.).

Que fait l'IDE ARDUINO pour vous, et que ne fait-il pas ?
  • il choisit les options pour vous
  • certaines options sont modifiables grâce au menu "Outils"
  • il compile et fait l'édition de liens
  • il charge le binaire dans la cible
  • c'est tout
Avec son package STM32DUINO il est capable de compiler du code STM32.

Mais il peut aussi vous permettre de travailler sur des microcontrôleurs Espressif ESP8266 et ESP32.

Il ne possède aucune possibilité de déboguer, mais on peut utiliser un débugger séparé comme gdb ou gdbgui.

5. Liens utiles

https://riton-duino.blogspot.com/2018/03/stm32f103c8t6-et-arduino.html
https://riton-duino.blogspot.com/2019/03/stm32-duino-deboguer.html

https://riton-duino.blogspot.com/2019/03/stm32-boitier-st-link.html


Cordialement
Henri

vendredi 29 mars 2019

STM32 : boîtier ST-LINK





STM32 : boîtier ST-LINK


Afin de ne  ne pas répéter ces informations dans 3 documents, j'ai réuni dans cet article quelques petits paragraphes concernant les boîtiers ST-LINK.

Cet article fait suite à ceux-ci :
https://riton-duino.blogspot.com/2019/03/stm32-environnements-de-developpement.html
https://riton-duino.blogspot.com/2018/03/stm32f103c8t6-et-arduino.html
https://riton-duino.blogspot.com/2019/03/stm32-duino-deboguer.html

1. Les boîtiers ST-LINK

Il existe sur le marché différents boîtier JTAG ST-LINK pour STM32 :

ST-LING V2 ST MicroElectronics
ST-LINK V2 Clone chinois

2. Les branchements

Quand on veut charger une BLUE PILL, toutes les cartes ne sont pas sérigraphiées. Ceci aide :




Voici les branchements côté boîtier :

Connecteur ST-LINK V2 ST MicroElectronics

Il y a 4 fils à relier avec le boîtier ST-LINK "offficiel" :
  • GND : 20
  • 3.3V : 19
  • DCLK : 9
  • DIO : 7
ST-LINK V2 Clone chinois
Et 4 aussi avec les ST-LINK chinois :
  • GND : GND
  • 3.3V : 3.3V
  • DCLK :SWCLK
  • DIO :SWDIO
Si vous n'avez jamais utilisé ce genre de boîtier, il est très probable que sous LINUX vous ayez à créer un petit fichier pour autoriser l'accès au device ST-LINK.

Si vous lancez st-flash ou st-util et que vous voyez ceci, c'est que vous n'avez pas les privilèges :

2019-03-29T19:12:31 WARN usb.c: Couldn't find any ST-Link/V2 devices

Vous pouvez lancer la même commande avec sudo, mais avouez que ce n'est pas une solution.

Si vous utilisez STM32DUINO ce fichier se trouve ici :

.arduino15/packages/STM32/tools/STM32Tools/1.2.1/tools/linux64/49-stlinkv2-1.rules

Sinon on peut le trouver ici :

https://github.com/texane/stlink/blob/master/etc/udev/rules.d/49-stlinkv2-1.rules

Copiez ce fichier sous /etc/udev/rules.d.
Ce fichier permet à l'utilisateur non root d'accéder au device

Modifiez le fichier /etc/udev/rules.d/49-stlinkv2-1.rules, pour accepter les ST-LINK clones chinois stlinkV2 :

# stm32 nucleo boards, with onboard st/linkv2-1
# ie, STM32F0, STM32F4.
# STM32VL has st/linkv1, which is quite different

SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", \
    MODE:="0666", \
    SYMLINK+="stlinkv2-1_%n"

SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", \
    MODE:="0666", \
    SYMLINK+="stlinkv2_%n"

# If you share your linux system with other users, or just don't like the
# idea of write permission for everybody, you can replace MODE:="0666" with
# OWNER:="yourusername" to create the device owned by you, or with
# GROUP:="somegroupname" and mange access using standard unix groups.


D'où viennent ces identifiants 0483 et 3748 ? simple, on les obtient avec lsusb :

riton@alpha:~/.arduino15$ lsusb
Bus 002 Device 013: ID 0bda:0307 Realtek Semiconductor Corp.
Bus 002 Device 011: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 002 Device 008: ID 0451:8043 Texas Instruments, Inc.
Bus 002 Device 005: ID 0451:8043 Texas Instruments, Inc.
Bus 002 Device 004: ID 046d:c31d Logitech, Inc. Media Keyboard K200
Bus 002 Device 018: ID 0483:3748 STMicroelectronics ST-LINK/V2Bus 002 Device 003: ID 05e3:0610 Genesys Logic, Inc. 4-port hub
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 006 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 005 Device 002: ID 11b0:6148 ATECH FLASH TECHNOLOGY
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub


Quand le fichier 49-stlinkv2-1.rules est prêt redémarrez udev :

udevadm trigger

3. La contrefaçon

Méfiez-vous, certains vendeurs vendent des boîtiers soit disant officiels :

Clone chinois déguisé en vrai ST MicroElectronics
ICI : https://fr.aliexpress.com/item/ST-LINK-V2-CN-ST-LINK-STLINK-STM8-STM32-emulator-official-version/32230072665.html?spm=a2g0s.9042311.0.0.27426c37uV9sOM

Le résultat n'est pas à la hauteur :

riton@alpha:/mnt/sdc1/riton$ st-flash write /tmp/arduino_build_579822/Blink.ino.bin 0x8000000
2019-03-29T20:36:26 INFO src/stlink-common.c: Loading device parameters....
2019-03-29T20:36:26 WARN src/stlink-common.c: unknown chip id! 0
riton@alpha:/mnt/sdc1/riton$
 
Par contre il fonctionne bien avec une Black VET6 et son connecteur 20 points :

Préférez ces petits boîtiers :
Clone chinois non déguisé
ICI : https://fr.aliexpress.com/item/ST-Link-V2-Programming-Unit-mini-STM8-STM32-Emulator-Downloader-M89-New/32631496848.html?spm=a2g0s.9042311.0.0.27426c37Knvivh

Le résultat est tout autre :

riton@alpha:/mnt/sdc1/riton$ st-flash write /tmp/arduino_build_579822/Blink.ino.bin 0x8000000
2019-03-29T21:17:02 INFO src/stlink-common.c: Loading device parameters....
2019-03-29T21:17:02 INFO src/stlink-common.c: Device connected is: F1 Medium-density device, id 0x20036410
2019-03-29T21:17:02 INFO src/stlink-common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x10000 bytes (64 KiB) in pages of 1024 bytes
2019-03-29T21:17:02 INFO src/stlink-common.c: Attempting to write 12844 (0x322c) bytes to stm32 address: 134217728 (0x8000000)
Flash page at addr: 0x08003000 erased
2019-03-29T21:17:02 INFO src/stlink-common.c: Finished erasing 13 pages of 1024 (0x400) bytes
2019-03-29T21:17:02 INFO src/stlink-common.c: Starting Flash write for VL/F0/F3 core id
2019-03-29T21:17:02 INFO src/stlink-common.c: Successfully loaded flash loader in sram
 12/12 pages written
2019-03-29T21:17:03 INFO src/stlink-common.c: Starting verification of write complete
2019-03-29T21:17:03 INFO src/stlink-common.c: Flash written and verified! jolly good!
riton@alpha:/mnt/sdc1/riton$ 

4. Liens utiles
https://riton-duino.blogspot.com/2019/03/stm32-environnements-de-developpement.html
https://riton-duino.blogspot.com/2018/03/stm32f103c8t6-et-arduino.html
https://riton-duino.blogspot.com/2019/03/stm32-duino-deboguer.html


Cordialement
Henri

jeudi 28 mars 2019

STM32-DUINO : déboguer




STM32-DUINO : déboguer


Dans cet article je vous présente plusieurs solutions pour déboguer un logiciel développé sur STM32, particulièrement si celui-ci est développé avec l'IDE ARDUINO et le package STM32DUINO, ou compilé en ligne de commande avec un Makefile.

Cet article fait suite à celui-ci : https://riton-duino.blogspot.com/2018/03/stm32f103c8t6-et-arduino.html

Nous allons commencer par expliquer le principe d'un débogueur (ou debugger en anglais).

1. Les bases

Le debugger est un logiciel permettant de chercher (et éventuellement de trouver et corriger) les bugs d'un logiciel en cours de développement ou d'un logiciel déjà opérationnel mais sur lequel a été détecté un dysfonctionnement.

Il permet différentes opérations comme :
  • lancer l'application
  • interrompre l'application
  • examiner les variables globales, statiques ou locales
  • examiner la pile (stack in english)
  • modifier une variable
  • mettre en place des points d'arrêt
  • faire un saut de ligne dans l'application
  • etc.
Un debugger peut être un logiciel en ligne de commande comme gdb par exemple, ou un logiciel avec une interface graphique.

2. La chaîne de développement

J'ai réuni les informations concernant les différents environnements de développement ici :

https://riton-duino.blogspot.com/2019/03/stm32-environnements-de-developpement.html

2.1. Le boîtier JTAG

2.1.1. Le boîtier ST-LINK

Vous utilisez peut-être une carte avec un connecteur SWD approprié, comme une BLUE PILL par exemple, auquel cas il vous faut aussi un boîtier ST-LINK pour charger et déboguer votre application.

Voir ici pour l'installation : https://riton-duino.blogspot.com/2019/03/stm32-boitier-st-link.html

2.1.2. Le boîtier J-LINK

Il existe bien d'autres boîtiers JTAG en particulier le J-LINK, supporté par beaucoup d'environnements de développement.

2.1.3. La carte d'évaluation

Vous travaillez peut-être sur une carte avec un connecteur USB, comme une ST NUCLEO ou DISCOVERY, auquel cas l'électronique du ST-LINK est déjà intégrée à la carte :

Vous trouverez des informations sur ces cartes ici : https://riton-duino.blogspot.com/2018/03/stm32f103c8t6-et-arduino.html

Avec tout ce beau matériel, quand vous vous retrouvez confronté à un bug difficile à trouver, il vous manque simplement une chose : le debugger.


3. Notions générales

Avec un STM32, pour faire fonctionner le debugger, il faut deux logiciels :
  • st-util
  • gdb 
Ces deux logiciels sont complémentaires et dialoguent ensemble.

Les explications qui suivent sont basées sur l'installation sur un système UBUNTU, mais normalement cela ne devrait pas poser de problème sous Windows.
Je ferai un essai quand j'aurai terminé. Promis.

3.1. Le logiciel à déboguer

Le logiciel sur lequel nous allons travailler doit être préparé spécialement pour être utilisé. En effet il doit contenir des informations spéciales qui vont permettre au debugger d'assurer son rôle :
  • des noms de fichier
  • des numéros de ligne
  • pas d'optimisation
Ces informations, dans un logiciel compilé classiquement ne sont pas incluses dans le binaire car elles occupent de la place.

Il faut donc utiliser une option -g pour la compilation :

gcc -Wall -g prog.c -o prog

Avec un STM32 nous utilisons arm-none-eabi-gcc :

arm-none-eabi-gcc -mcpu=cortex-m4 -c -g -Og source.c -o source.o

A l'édition de liens il faut aussi préciser que nous voulons une version debug :

arm-none-eabi-gcc -mcpu=cortex-m4 -g -Og -o prog.elf source.o main.o
arm-none-eabi-objcopy" -O binary  prog.elf prog.bin

Les lignes de commandes sont simplifiées bien sûr, car dans la réalité il y a beaucoup plus d'options.

On peut remarquer deux lignes de commande pour l'édition de liens : l'éditeur de lien produit un fichier .elf contenant d'une part le code binaire de l'application et d'autre part toutes les informations de debug qui nous intéressent.
Ce fichier est ensuite converti en fichier binaire pur afin d'être chargé dans la FLASH du STM32.

3.1.1. La compilation

Ici nous allons parler uniquement de la génération de l'exécutable à partir de l'IDE ARDUINO et PlatformIO. Je pars du principe que celui qui utilise un Makefile maîtrise parfaitement la chose et n'a pas besoin d'explications.

3.1.1.1. IDE ARDUINO

Choisissez tout d'abord le type de carte avec le menu "Outils / Type de carte" : Nucleo-64 par exemple.
Choisissez le modèle de carte avec le menu "Outils / Board part Number" : Nucleo F401-RE par exemple.
Choisissez l'option "Outils / Optimize" : Debug (-g)
Choisissez l'option "Outils / Upload Method" : STLink

Ouvrez l'exemple BlinWithoutDelay à l'aide du menu "Fichier / Exemples / 02.Digital".
Modifiez-le pour ajouter quelques lignes pour afficher des messages sur la console :

const int ledPin =  LED_BUILTIN;// the number of the LED pin

// Variables will change:
int ledState = LOW;             // ledState used to set the LED

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;        // will store last time LED was updated

// constants won't change:
const long interval = 1000;           // interval at which to blink (milliseconds)

void setup() {
  // set the digital pin as output:
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // here is where you'd put code that needs to be running all the time.

  // check to see if it's time to blink the LED; that is, if the difference
  // between the current time and last time you blinked the LED is bigger than
  // the interval at which you want to blink the LED.
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    Serial.print("currentMillis="); delay(100);
    Serial.println(currentMillis); delay(100);
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}


Branchez votre carte, 
Compilez et chargez l'application. Les messages suivants s'affichent pendant le chargement :

/home/riton/.arduino15/packages/STM32/tools/STM32Tools/1.2.1/tools/linux/stlink_upload ttyACM0 {upload.altID} {upload.usbID} /tmp/arduino_build_575652/BlinkWithoutDelay.ino.bin
 2019-03-28T18:26:48 INFO common.c: Loading device parameters....
2019-03-28T18:26:48 INFO common.c: Device connected is: F4 device (Dynamic Efficency), id 0x10006433
2019-03-28T18:26:48 INFO common.c: SRAM size: 0x18000 bytes (96 KiB), Flash: 0x80000 bytes (512 KiB) in pages of 16384 bytes
2019-03-28T18:26:48 INFO common.c: Attempting to write 35780 (0x8bc4) bytes to stm32 address: 134217728 (0x8000000)
EraseFlash - Sector:0x0 Size:0x4000 st-flash 1.5.1

Flash page at addr: 0x08000000 erasedEraseFlash - Sector:0x1 Size:0x4000
Flash page at addr: 0x08004000 erasedEraseFlash - Sector:0x2 Size:0x4000 2019-03-28T18:26:49 INFO common.c: Finished erasing 3 pages of 16384 (0x4000) bytes
2019-03-28T18:26:49 INFO common.c: Starting Flash write for F2/F4/L4
2019-03-28T18:26:49 INFO flash_loader.c: Successfully loaded flash loader in sram

Flash page at addr: 0x08008000 erased2019-03-28T18:26:49 INFO common.c: Starting verification of write complete
2019-03-28T18:26:50 INFO common.c: Flash written and verified! jolly good!

enabling 32-bit flash writes
size: 32768
size: 3012


Le nom de l'applicaion binaire générée est en gras. Sous LINUX le binaire est généré dans le répertoire /tmp, sous Windows c'est ici : C:\Users\Local Settings\Temp.

Après chargement dans la cible, tout se passe bien. La LED verte clignotte sur la carte.

3.1.1.2. PlatformIO
 
Sous PlatformIO c'est différent. Il faut créer un projet, choisir une carte parmi les nomreuses plateformes proposées.

Pour activer l'option de debug, il faut modifier le fichier platformio.ini :

[env:black_f407ve]
platform = ststm32
board = black_f407ve
framework = arduino
monitor_speed = 115200
build_flags = -g3 

upload_protocol = stlink
debug_tool = stlink


J'ai ajouté également les options indiquent comment le chargement devra s'opérer : stlink.

Je n'ai pas encore eu la possibilité de consacrer du temps à la mise oeuvre du débogeur sous PlatformIO. Mais cette possibilité est intégrée.

Lors du chargement l'IDE indique également l'emplacement du fichier exécutable :

Uploading .pioenvs/black_f407ve/firmware.elf

3.2. st-util

st-util est le logiciel serveur qui va communiquer avec le boîtier ST-LINK ou votre carte NUCLEO ou DISCOVERY par le câble USB.

Si vous n'utilisez pas STM32DUINO il faut le télécharger :

https://www.st.com/en/development-tools/st-link-v2.html#tools-software

Sinon, le nécessaire se trouve ici dans le répertoire .arduino15 de votre répertoire personnel :
Il y a une librairie libstlink.so.1 à copier sous /usr/lib :

Si la machine est en 64bits :
sudo cp .arduino15/packages/STM32/tools/STM32Tools/1.2.1/tools/linux64/stlink/lib/libstlink.so.1 /usr/lib
Si la machine est en 32bits :
sudo cp .arduino15/packages/STM32/tools/STM32Tools/1.2.1/tools/linux/stlink/lib/libstlink.so.1
/usr/lib

Vous pouvez soit modifier votre variable d'environnement PATH pour y ajouter le répertoire où se trouve st-util, soit copier st-util dans /usr/local/bin :
Si la machine est en 64bits :
sudo cp .arduino15/packages/STM32/tools/STM32Tools/1.2.1/tools/linux/stlink/st-util /usr/local/bin
Si la machine est en 32bits :
sudo cp .arduino15/packages/STM32/tools/STM32Tools/1.2.1/tools/linux64/stlink/st-util
/usr/local/bin

Ensuite il suffit de brancher le boîtier ST-LINK ou la carte NUCLEO ou DISCOVERY et de lancer st-util dans un terminal :

st-util
 

st-util 1.5.1
2019-03-28T17:57:56 INFO common.c: Loading device parameters....
2019-03-28T17:57:56 INFO common.c: Device connected is: F4 device (Dynamic Efficency), id 0x10006433
2019-03-28T17:57:56 INFO common.c: SRAM size: 0x18000 bytes (96 KiB), Flash: 0x80000 bytes (512 KiB) in pages of 16384 bytes
2019-03-28T17:57:56 INFO gdb-server.c: Chip ID is 00000433, Core ID is  2ba01477.
2019-03-28T17:57:56 INFO gdb-server.c: Listening at *:4242...


Ici je viens de le lancer avec une carte NUCLEO F401RE branchée sur un port USB. Elle est parfaitement reconnue.

Cette fenêtre doit rester ouverte et st-util doit tourner pendant toute la session de debug.
A chaque fois que vous voulez recompiler et recharger l'application, il faut tuer st-util (CTRL_C), et le relancer après avoir compilé et rechargé le code dans la cible.

Il est très probable que sous LINUX vous ayez à créer un petit fichier pour autoriser l'accès au device ST-LINK.

Si vous lancez st-util et que vous voyez ceci :

2019-03-29T19:12:31 WARN usb.c: Couldn't find any ST-Link/V2 devices

Créez un fichier /etc/udev/rules.d/49-stlinkv2.1.rules :

# stm32 nucleo boards, with onboard st/linkv2-1
# ie, STM32F0, STM32F4.
# STM32VL has st/linkv1, which is quite different

SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", \
    MODE:="0666", \
    SYMLINK+="stlinkv2-1_%n"

SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", \
    MODE:="0666", \
    SYMLINK+="stlinkv2_%n"

# If you share your linux system with other users, or just don't like the
# idea of write permission for everybody, you can replace MODE:="0666" with
# OWNER:="yourusername" to create the device owned by you, or with
# GROUP:="somegroupname" and mange access using standard unix groups.


Ce fichier permet à l'utilisateur non root d'accéder au device, qu'il soit un ST-LINK ST MicroElectronics ou un ST-LINK chinois.

3.3. gdb

Le deuxième logiciel est le debugger lui-même. Il doit bien sûr comprendre le langage machine STM32, donc ce ne sera pas le gdb installé normalement sur un système LINUX, qui ne connait que le langage machine INTEL.

Que l'on utilise gdb en ligne de commande ou une interface graphique, gdb est nécessaire.

Ici encore, tout dépend si l'on utilise STM32DUINO ou pas.

Si vous n'utilisez pas STM32DUINO il faut télécharger la chaîne de compilation :

https://developer.arm.com/tools-and-software/open-source-software/gnu-toolchain/gnu-rm/downloads

Sinon, le nécessaire se trouve dans le répertoire .arduino15 de votre répertoire personnel. Vous pouvez soit modifier votre variable d'environnement PATH pour y ajouter le répertoire où se trouve arm-none-eabi-gdb, soit copier arm-none-eabi-gdb dans /usr/local/bin :

sudo cp .arduino15/packages/STM32/tools/arm-none-eabi-gcc/6-2017-q2-update/bin/arm-none-eabi-gdb /usr/local/bin

4. gdb

gdb est un debugger en mode ligne de commande. Je sens que certains vont passer directement au chapitre suivant.

Le lancement :

Il suffit de lancer arm-none-eabi-gdb suivi du nom de l'appplication.

Nous avons vu plus haut que lors de la compilation (voir   3.1.1. La compilation) les messages de chargement de l'IDE ARDUINO indiquent le nom de l'application :

/tmp/arduino_build_575652/BlinkWithoutDelay.ino.bin

Copiez ce nom complet et remplacez .bin par .elf

Idem pour PlatformIO, fauf que le nom de fichier est déjà prêt :

.pioenvs/black_f407ve/firmware.elf

gdb se lance aussi dans un terminal depuis le répertoire où se situe vos fichiers source :
arm-none-eabi-gdb /tmp/arduino_build_575652/BlinkWithoutDelay.ino.elf

Ou :

arm-none-eabi-gdb .pioenvs/black_f407ve/firmware.elf

gdb se lance :

GNU gdb (GNU Tools for ARM Embedded Processors 6-2017-q2-update) 7.12.1.20170417-git
Copyright (C) 2017 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=x86_64-linux-gnu --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"...
Reading symbols from /tmp/arduino_build_575652/BlinkWithoutDelay.ino.elf...done.
(gdb)


Remarque :

Reading symbols from /tmp/arduino_build_575652/BlinkWithoutDelay.ino.elf...done.

Ce message indique que gdb a bien trouvé l'application et qu'il a chargé la table des symboles.
Si vous avez oublié de choisir l'option "Debug (-g)" dans "Outils / Optimize", vous aurez un message différent :

Reading symbols from /tmp/arduino_build_575652/BlinkWithoutDelay.ino.elf...(no debugging symbols found)...done.

L'invite de commande (gdb) apparaît. 
Si vous n'avez pas pas lancé st-util, faites-le (voir 3.1. st-util).
Connectons-nous au serveur st-util.

(gdb) target extended-remote :4242
Remote debugging using :4242
0x080010ec in Reset_Handler ()
(gdb) 


Dans ce qui suit l'invite de commande et la commande à taper est en gras. J'indique les commandes à taper et leur abbréviation.

Voyons où nous en sommes (commande "list" ou "l") :

(gdb) list 
41    #endif
42    #ifndef D_CACHE_DISABLED
43      SCB_EnableDCache();
44    #endif
45    #endif
46   
47      init();
48    }
49   
50    /*
(gdb) list 

51     * \brief Main entry point of Arduino application
52     */
53    int main( void )
54    {
55      initVariant();
56   
57      setup();
58   
59      for (;;)
60      {
(gdb) list 

61    #if defined(CORE_CALLBACK)
62        CoreCallback();
63    #endif
64        loop();
65        if (serialEventRun) serialEventRun();
66      }
67   
68      return 0;
69    }
(gdb)


Il s'agit du code d'initialization et plus bas on voit la fonction main().

Posons un point d'arrêt (commande "break" ou "b") :

(gdb) break loop
Breakpoint 1 at 0x8000fdc: file /home/riton/Arduino/BlinkWithoutDelay/BlinkWithoutDelay.ino, line 50.
(gdb)


Lançons l'application (commande "continue" ou "c") :

(gdb) continue
 Continuing.
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 2, loop () at /home/riton/Arduino/BlinkWithoutDelay/BlinkWithoutDelay.ino:50
50    void loop() {
(gdb)


L'application s'est arrêtée sur la fonction loop().
Visualisons quelques variables :

(gdb) print interval
$1 = 1000
(gdb) 

(gdb) print previousMillis
$2 = 0
(gdb)
(gdb) print currentMillis
$3 = <optimized out>

(gdb)

La variable interval vaut 1000, previousMillis vaut 0, ce qui semble assez normal.
La variable currentMillis n'existe pas encore.

Voyons un peu plus loin :

(gdb) list
45      // set the digital pin as output:
46      Serial.begin(115200);
47      pinMode(ledPin, OUTPUT);
48    }
49   
50    void loop() {
51      // here is where you'd put code that needs to be running all the time.
52   
53      // check to see if it's time to blink the LED; that is, if the difference
54      // between the current time and last time you blinked the LED is bigger than
(gdb) list

55      // the interval at which you want to blink the LED.
56      unsigned long currentMillis = millis();
57   
58      if (currentMillis - previousMillis >= interval) {
59        // save the last time you blinked the LED
60        Serial.print("currentMillis="); delay(100);
61        Serial.println(currentMillis); delay(100);
62        previousMillis = currentMillis;
63   
64        // if the LED is off turn it on and vice-versa:
(gdb)


Visualisons les points d'arret :

(gdb) info breakpoints
 1       breakpoint     keep y   0x08000fdc in loop() at /home/riton/Arduino/BlinkWithoutDelay/BlinkWithoutDelay.ino:50
(gdb)


Supprimons le premier car l'application va sans arrêt s'arrêter sur celui-ci si nous continuons :

(gdb) del 1
(gdb)

Posons un breakpoint en ligne 60 :

(gdb) break 60
Breakpoint 2 at 0x8000ff2: file /home/riton/Arduino/BlinkWithoutDelay/BlinkWithoutDelay.ino, line 60.
(gdb)


Continuons :

(gdb) c 
Continuing.

Breakpoint 3, loop () at /home/riton/Arduino/BlinkWithoutDelay/BlinkWithoutDelay.ino:60
60        Serial.print("currentMillis="); delay(100);
(gdb)


L'application s'est arrêtée en ligne 60.
Avançons de quelques pas :

(gdb) next
61        Serial.println(currentMillis); delay(100);
(gdb) next
62        previousMillis = currentMillis;
(gdb) next
65        if (ledState == LOW) {
(gdb)


Visualisons quelques variables :

(gdb) p previousMillis
$4 = 1000
(gdb) p currentMillis

$5 = 1000 
(gdb)

Cette fois-ci previousMillis vaut 1000, ce qui semble assez normal vu ce qui s'est passé en ligne 62.


Continuons :

(gdb) c 
Continuing.

Breakpoint 3, loop () at /home/riton/Arduino/BlinkWithoutDelay/BlinkWithoutDelay.ino:60
60        Serial.print("currentMillis="); delay(100);
(gdb)


Entrons dans la méthode print :

(gdb) step 
Print::print (this=this@entry=0x200003a4 <Serial2>, str=str@entry=0x80082a0 "currentMillis=") at /home/riton/.arduino15/packages/STM32/hardware/stm32/1.5.0/cores/arduino/Print.cpp:55
55    {
(gdb)


Nous sommes à l'entrée de la méthode print de l'objet Serial.
Visualisons le paramètre str :

(gdb) print str
$6 = 0x80082a0 "currentMillis="
(gdb)


Nous pourrions bien entendu afficher les paramètres de nos propres fonctions.

Visualisons la pile :

(gdb) backtrace
#0  Print::print (this=this@entry=0x200003a4 <Serial2>, str=str@entry=0x80082a0 "currentMillis=") at /home/riton/.arduino15/packages/STM32/hardware/stm32/1.5.0/cores/arduino/Print.cpp:55
#1  0x08000ffc in loop () at /home/riton/Arduino/BlinkWithoutDelay/BlinkWithoutDelay.ino:60
#2  0x080042b2 in main () at /home/riton/.arduino15/packages/STM32/hardware/stm32/1.5.0/cores/arduino/main.cpp:64
(gdb)


Nous sommes dans la méthode print, appelée depuis la fonction loop() située dans le fichier BlinkWithoutDelay.ino et l'appel se situe en ligne 60. Bien entendu, on retrouve en fin de pile d'appel la fonction principale : main(), dont la plupart des bidouilleurs sur ARDUINO ignorent l'existence.

Sortons de print :

(gdb) ret 
Make Print::print(char const*) return now? (y or n) y
#0  0x08000ffc in loop () at /home/riton/Arduino/BlinkWithoutDelay/BlinkWithoutDelay.ino:60
60        Serial.print("currentMillis="); delay(100);
(gdb)

Nous sommes de retour dans loop().

Voici une petite présentation sommaire qui se déroule plutôt pas mal.

J'ajoute quelques petits détails supplémentaires sur l'auto-complétion :

Quand on tape le début du nom d'une commande ou le début du nom d'une variable ou d'une fonction, une tabulation permet de compléter ou d'obtenir plusieurs propositions :

(gdb) c <tab>
call              cd                clone-inferior    commands          compile           condition         core-file        
catch             clear             collect           compare-sections  complete          continue         
(gdb) con <tab>

condition  continue  
(gdb) p prev <tab>

(gdb) p previousMillis
$7 = 7000
(gdb)


Sortons (CTRL-D)

(gdb) quit 
A debugging session is active.

    Inferior 1 [Remote target] will be killed.

Quit anyway? (y or n) y


De nombreux tutos sur gdb et des manuels vous atendent sur le WEB. C'est un très vieux logiciel très bien documenté et très puissant.

Remarque : à chaque fois que vous voulez recompiler et recharger l'application, il faut tuer st-util (CTRL_C).
Ensuite il faut compiler, recharger, et relancer  st-util.

5. gdbgui

Ici nous abordons le debugger graphique gdbgui. Je sens que ceux qui n'ont pas lu le chapitre précédent respirent.

Pour l'installer : https://gdbgui.com/downloads.html

C'est un logiciel écrit en PYTHON. Bien entendu les intimes de ce langage peuvent l'intaller autrement :

sudo pip install gdbgui

Nous laissons tourner st-util bien entendu. Il est indispensable.

Lançons-le. Bien sûr il faut lui dire que nous utilisons un gdb ARM.

gdbgui se lance aussi dans un terminal, mais vous pouvez aussi vous écrire un petit lanceur ou un raccourci :

gdbgui -g arm-none-eabi-gdb


Surprise !

Il s'affiche dans votre navigateur préféré. Une belle performance.

Dans le champ (gdb) tout en bas, entrons la commande permettant de se connecter à st-util :

(gdb) target extended-remote :4242

Rassurez-vous, tout le reste se passe avec la souris.
D'autre part avec les touches de rappel du clavier 🠅 et 🠇 il est possible de rappeler les commandes précédentes même après un redémarrage de la machine.

Il est toujours possible d'entrer dans cette zone les commandes gdb comme au chapitre précédent.

Dans la zone de texte en haut entrons le nom de notre application :

/tmp/arduino_build_575652/BlinkWithoutDelay.ino.elf

Appuyer sur <Entrée>.

Ici aussi, entre deus sessions, gdbgui conserve le nom de l'application précédemment entré.

Il affiche notre point d'entrée : main()

Supprimons le point d'arrêt sur la ligne 55 (en bleu). Un clic suffit.
Plaçons-en un autre sur la ligne 64 : l'appel à loop(). Un clic suffit aussi.

Lançon l'exécution par un clic sur le bouton 🡂 en haut à droite.

La ligne d'appel à loop() change. Elle devient gris clair. L'application s'est arrêtée sur le point d'arrêt.

Supprimons le point d'arrêt sur la ligne 64 : un clic suffit aussi.

Entrons dans la fonction loop d'un clic sur le bouton 🠇.


Nous voici dans le vif du sujet, la fonction loop(). Un point d'arrêt sur la ligne 60 nous permettra de nous arrêter après la condition :

if (currentMillis - previousMillis >= interval) {

Encore un clic sur le bouton 🡂.

L'application s'arrête bien sur la ligne voulue :

Serial.print("currentMillis="); delay(100);

Dans la fenêtre de droite il est possible de visualiser en direct les variables locales. Mais il y a mieux : il suffit de placer la souris sur le nom d'une variable pour que sa valeur s'affiche.

Lorsque l'on place la souris sur un bouton une bulle explique son utilisation.

Voilà, c'est un premier jet.

Bien évidemment les accros à la souris préfèreront la version graphique, mais je reste quand même fidèle à mon bon vieux gdb en ligne de commande. L'habitude sans doute. Cela ne m'empêche pas de comprendre le besoin de confort.

La suite au prochain numéro ...

Peut-être le débogage avec PlatformIO ?


6. Liens utiles

https://riton-duino.blogspot.com/2018/03/stm32f103c8t6-et-arduino.html
https://riton-duino.blogspot.com/2019/03/stm32-environnements-de-developpement.html
https://riton-duino.blogspot.com/2019/03/stm32-boitier-st-link.html



Cordialement

Henri

mardi 19 mars 2019

Arduino, ESP8266 et ESP32 une librairie timers



 Arduino, ESP8266 et ESP32

 une librairie timers


Sur ARDUINO la gestion du temps est pour le moins spartiate. Les développeurs utilisent souvent millis et lorsque l'on a plusieurs timers à gérer le code devient vite sac de nouilles.

Pourquoi ne pas bâtir une librairie qui permettent de gérer un ou plusieurs timers ?

Ou pourrait résumer l'interface à celle-ci :
  • créer  le timer
  • armer le timer
  • consulter le timer
  • le timer pourrait aussi appeler une fonction fournie par l'utilisateur lorsque le temps est écoulé.
Des choses existent déjà :
Oui, mais ces librairies monopolisent un timer pour chaque utilisation. Ce n'est pas suffisant si l'on a besoin de plus de deux timers, surtout sur un ARDUINO NANO qui n'en possède que deux.

Sur ESP8266 et ESP32 la librairie le kit de développement dispose déjà de timers modernes qui correspondent exactement au besoin.
Mais comme toujours dans le monde Espressif, chaque microcontrôleur a une interface logicielle différente. Pourquoi simplifier la vie des développeurs quand on peut s'en dispenser ?

On pourrait tout simplement exploiter les timers ESP8266 et ESP32 pour les intégrer dans la librairie. L'interface serait donc identique pour ARDUINO, ESP8266 et ESP32.

1. Le besoin

L'idée de base serait de reprendre l'idée de TimerOne ou Timer2, c'est à dire accrocher une routine d'interruption au vecteur TIMER1 ou TIMER2. A partir de cette routine gérer plusieurs instances purement logicielles ne devrait pas être bien complexe.

J'ai donc déterré une idée que j'avais eu à il y a dix ans : porter l'interface de timers du kernel LINUX :
  • void init_timer(struct timer_list * timer);
  • void setup_timer(struct timer_list timer, void (function)(unsigned long), unsigned long data );
  • int add_timer(struct timer_list* timer);
  • void mod_timer(struct timer_list* timer, unsigned long expires);
  • int del_timer(struct timer_list* timer);
init_timer crée un timer
setup_timer permet de fournir une fonction utilisateur
add_timer ajoute le timer au système
mod_timer arme le timer
del_timer le détruit

A l'époque ce portage avait été fait sur MSP430. Un ARDUINO sera parfaitement à l'aise. Le code de la routine d'interruption est très légère.

Un timer est un élément de liste chaînable, et nous pourrons en créer autant que nécessaire.

Un petit exemple de ce que cela pourrait donner :

#include <kernel_timers.h>

#define TIMEOUT          (HZ*5)

struct timer_list timer;

void setup()
{
  Serial.begin(115200);
  // initialize timers system with TIMER1
  timer_init(1);
  init_timer(&timer);
  add_timer(&timer);
  Serial.println("\nStart the timer for 5 seconds");
  mod_timer(&timer, jiffies + TIMEOUT);
}

void loop()
{
  if (timer.expires != 0 && timer.expires == jiffies) {
    Serial.println("Timer has elapsed");
    mod_timer(&timer, 0);
  }
} 

HZ représente la fréquence de d'actualisation des timers (l'interruption).
jiffies représente le temps courant.
Donc jiffies + (5 * HZ) représente le temps courant + 5 secondes.

Une explication des jiffies sous WIKIPEDIA : https://en.wikipedia.org/wiki/Jiffy_(time)

Notre librairie aura une valeur de HZ de 200. Cela nous donnera une résolution de 5 millisecondes pour nos timers.

La fonction d'initialisation de la librairie accepte un paramètre timer. Il s'agit du numéro de timer hardware :
  • sur Atmega328 vous avez le choix entre 1 et 2
  • sur Atmega2560 vous avez le choix entre 1,2, 3, 4 et 5
  • sur ESP8266 et ESP32 ce paramètre est ignoré

2. La librairie

La librairie est disponible ici :
https://bitbucket.org/henri_bachetti/kernel_timers.git

Cette page vous donne toutes les informations nécessaires :
https://riton-duino.blogspot.com/p/migration-sous-bitbucket.html

Quelques  exemples d'utilisation sont fournis.

Tous les exemples tournent sur ARDUINO UNO, MEGA, ESP8266 et ESP32.


Cordialement

Henri BACHETTI

samedi 16 mars 2019

Arduino, ESP8266, ESP32 et STM32 : une librairie console / logger



Arduino, ESP8266, ESP32 et STM32

une librairie console / logger


Dans cet article je vais vous présenter un développement récent, une librairie composée de deux parties :
  • une partie affichage sur la console, appelée moniteur série dans le monde ARDUINO
  • un logger permettant d'afficher ou d'enregistrer des messages de log.
Cette librairie doit être utilisable sur les plateformes suivants :
  • ARDUINO
  • ESP8266
  • ESP32
  • STM32

1. Les notions de base

Dans un logiciel, lorsque l'on affiche des messages sur une console, ceux-ci peuvent être de différents type :
  • messages à destination de l'utilisateur
  • messages d'erreur, warning, erreur critique
  • messages destinés à déverminer le logiciel (logging)
Ces messages sont affichés sur un terminal. Le moniteur série de l'IDE ARDUINO en est un, mais ce n'est pas le seul possible. Il en existe d'autres :
  • minicom, picocom, kermit sous Linux
  • teraterm sous Windows
  • etc.
Classiquement dans un logiciel en C, si l'application ne dispose pas d'un écran graphique, les messages à destination de l'utilisateur sont affichés via la sortie standard stdout à l'aide de fonctions telles que putchar, puts, printf, etc.

Les messages d'erreur sont affichés via la sortie standard stderr à l'aide de fonctions telles que fprintf, perror, etc.

En langage C, les messages destinés à déverminer le logiciel utilisent des mécanismes peu standardisés. On appelle ces mécanismes des loggers. Sous Linux,on utilise généralement syslog.
Un logger peut diriger les messages à afficher vers la console, un fichier ou une liaison Ethernet.

Un langage moderne comme PYTHON possède un système de logging évolué permettant la création de logs hautement personnalisables :
  • activation dynamique
  • affichage sur la console
  • création de fichiers de logs
  • création de fichiers de logs avec rotation
  • envoi vers une socket
  • envoi vers une base de données 
  • etc.
Un fichier de log, en français "fichier journal", est un fichier texte où l'on enregistre des messages pouvant être consultés ultérieurement :
  • messages de diagnostic
    • traces de debug
    • erreurs
    • problèmes de communication
  • événements
    • démarrage
    • pannes
    • défauts
  • tentatives d'accès  à un serveur WEB
  • etc.
Sur les plateformes ARDUINO on ne dispose pas de ces facilités, à moins d'installer une librairie.
Sur ESP8266 ou ESP32 une sortie de logs est prévue mais elle ne permet pas de créer des fichiers de logs.

La librairie présentée ici se propose de remédier partiellement à ces manques.

2. Les différentes architectures

Les différentes plateformes testées sont :
  • ARDUINO UNO
  • ARDUINO MEGA
  • ESP8266 NodeMCU (ESP12E) 
  • ESP32 VROOM
Je vous propose un tableau pour résumer les différentes possibilités en matière de sortie sur ligne série :

Carte Serial Serial1 Serial2 Serial3 Soft Serial
UNO NANO Oui


Oui
MEGA Oui Oui Oui Oui Oui
ESP8266 Oui Oui (1)

Oui
ESP32 Oui Oui (2) Oui
Oui
STM32 Oui (3) (3) (3)

(1) limité à la ligne TX
(2) toutes les cartes ne possèdent pas les pins appropriées
(3) Tout dépend de la carte utilisée

2.1. HardwareSerial

La classe HardwareSerial repose sur un périphérique natif du microcontrôleur, un UART ou USART. L'envoi et la réception des bits à la bonne cadence sont assurés par l'UART.
Dans le tableau ci-dessus les lignes série Hard correspondent aux colonnes Serial à Serial3.

2.2. SoftwareSerial

La classe SoftwareSerial repose sur une émulation logicielle d'UART. L'envoi et la réception des bits à la bonne cadence sont assurés par du code. Il faut donc installer une librairie.

ARDUINO : https://github.com/PaulStoffregen/SoftwareSerial.git
ESP8266 :  installée par défaut dans le package support
ESP32 : https://github.com/akshaybaweja/SoftwareSerial.git

La librairie SoftwareSerial pour ESP32 doit être installée dans le répertoire où se trouve le package support ESP32 :
Sous Linux :
/home/username/.arduino15/packages/esp32/hardware/esp32/1.0.1/libraries/
Sous Windows :
C:\Users\username\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.1/libraries/

2.3. L'ARDUINO

La sortie standard existe mais il faut lui affecter un flux. On procède comme ceci :

static FILE uartout = {0} ;

static int console_putchar (char c, FILE *stream)
{
  if (c == '\n') Serial.write('\r');
  Serial.write(c);
  return 0;
}

void setup(void)
{
  Serial.begin(115200);
  Serial.println("printf() sur ARDUINO");
  fdev_setup_stream (&uartout, console_putchar, NULL, _FDEV_SETUP_WRITE);
  stdout = &uartout;
}

// Ensuite les fonctions standard putchar, printf et puts sont parfaitement utilisables :

void loop() {
  for (int i = 0 ; i < 10 ; i++) {
    printf("compteur : %d\n", i);
    delay(100);
  }
  delay(10000);
}


L'ARDUINO ne possède aucun mécanisme de sortie de logs. Il faut ajouter une librairie.

Cette page explique comment rendre le logging dépendant d'une directive de compilation :
https://arduino103.blogspot.com/2012/09/define-debug-une-methode-de-debugging.html

Cette page donne explique comment sortir des logs sur un SoftwareSerial :
https://wolfgang-ziegler.com/blog/serial-debugging-on-arduino

Celles-ci permettent d'afficher des messages sur Serial uniquement :
https://github.com/ptlug/Arduino-logging
https://github.com/thijse/Arduino-Log

Celle-ci permet d'afficher des messages sur HardwareSerial ou SoftwareSerial :
https://github.com/mrRobot62/Arduino-logging-library

Une autre librairie beaucoup plus élaborée, permettant d'activer les logs dynamiquement :
https://github.com/JoaoLopesF/SerialDebug.git

2.4. L'ESP8266

Sur ESP8266 la sortie standard s'active comme ceci :

void setup(void)
{
  Serial.setDebugOutput(true);
}

Malheureusement cela ne fonctionne pas pour la sortie Serial1 ou Serial2. Cela fonctionnait pourtant en version 2.2.0 mais cela ne fonctionne plus en 2.5.0.
L'utilisation n'est pas possible avec un SoftwareSerial.

La librairie standard de l'ESP8266 possède une sortie de DEBUG activable par l'IDE ARDUINO :
https://arduino-esp8266.readthedocs.io/en/latest/Troubleshooting/debugging.html

Cette sortie est utilisable par le développeur d'applications :

  DEBUG_ESP_PORT.printf("output from \n", "DEBUG_ESP_PORT");

Cette sortie, lorsqu'elle est redirigée vers Serial1, a un petit défaut. L'affichage d'un '\n' provoque un saut à la ligne suivante mais pas de retour chariot.
En utilisant Serial le comportement est correct.

2.5. L'ESP32

Sur ESP32 la sortie standard s'active comme ceci :

void setup(void)
{
  Serial.setDebugOutput(true);
}

Comme pour l'ESP8266 cela ne fonctionne pas pour la sortie Serial1 ou Serial2.
L'utilisation n'est pas possible avec un SoftwareSerial.

L'ESP32 possède également sa propre librairie de logging :
https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/log.html

Cette sortie est utilisable aussi par le développeur d'applications :

#include <esp_log.h>

  static const char *TAG = "example";
  esp_log_level_set(TAG, ESP_LOG_VERBOSE);
  ESP_LOGI(TAG, "output from %s\n", "ESP32_LOG");


Avant la compilation il faudra sélectionner un niveau de log par défaut : voir Outils/Core DebugLevel.

Cette sortie, sur Serial ou lorsqu'elle est redirigée vers Serial2, fonctionne correctement.

3. Le bilan

En résumé que faut-il faire ?
En premier lieu réussir à rediriger la sortie standard vers la sortie série de notre choix, et ceci quelle que soit la plateforme.

On peut utiliser ensuite une des librairie de logging citées au chapitre 2 ou développer une solution.

3.1. La sortie console standard


3.1.1. L'ARDUINO
Cela va être très simple. La sortie standard va être redirigée vers la console choisie à l'aide de la méthode décrite au chapitre 2.
Comme nous écrivons nous-même notre fonction de redirection, elle peut faire un travail très simple :

static FILE uartout = {0} ;

static int console_putchar (char c, FILE *stream)
{
  if (c == '\n') console->write('\r');
  console->write(c);
  return 0;
}


Tout passera donc par cette fonction.

3.1.2. L'ESP8266 et l'ESP32

Nous avons constaté certains défauts de la librairie standard :
  • la redirection de la sortie standard vers Serial1 ou Serial2 ne fonctionne pas
  • la redirection de la sortie standard vers SoftwareSerial n'existe pas
  • la redirection de la sortie log vers Serial1 ne fonctionne pas sur l'ESP8266
  • la redirection de la sortie log vers SoftwareSerial n'existe pas
Comme nous ne pouvons pas intervenir dans la librairie nous allons redéfinir printf, puts et putchar dans un fichier console.h :

#define printf console_printf
#define puts console_puts
#define putchar console_putc


Tout passera donc par ces fonctions. Par contre pour que cela marche il faudra inclure le fichier console.h dans chaque source.

3.1. Le logger

Si l'on a besoin d'un logger ayant les mêmes fonctionnalités que celui de l'ESP32 il faut le développer. L'avantage est qu'il aura une API identique sur les plateformes ARDUINO, ESP8266 et ESP32

4. Développement d'un logger

Peu d'amateurs ARDUINO savent qu'il existe des librairies de logging.

Lorsque l'on veut déverminer son sketch on ajoute généralement des lignes de Serial.println().

Ensuite lorsque le logiciel fonctionne, en principe on retire ces lignes. C'est dommage car on peut fort bien en avoir besoin par la suite pour corriger un bug ou faire une évolution.

4.1. Définir des macros

Une première astuce consiste à définir des macros :

#define LOGS_ACTIVE

#ifdef LOGS_ACTIVE
#define log_print       Serial.print
#define log_println     Serial.println
#else
#define log_print
#define log_println
#endif

void setup()
{
  Serial.begin(115200);
  log_println("***** BOOT *****");
}

void loop()
{
}


Ainsi, au lieu de retirer les lignes d'affichage de logs, on met simplement la définition de LOGS_ACTIVE en commentaire et les logs ne seront plus affichés.

Comment cela fonctionne t-il ? Le préprocesseur évalue ces lignes de la manière suivante :

  log_println("***** BOOT *****");

Si LOG_ACTIVE est défini, lorsque le mot log_println est rencontré il est remplacé par Serial.println. La ligne devient  :

  Serial.println("***** BOOT *****");

Si LOG_ACTIVE nest pas défini, lorsque le mot log_println est rencontré il est remplacé par du vide. La ligne devient  :

  ("***** BOOT *****");

Cette ligne ne comporte pas d'erreur de syntaxe, mais comme elle n'a pas d'utilité, elle ne sera pas compilée. L'effet sera identique à celui obtenu en supprimant la ligne.

4.2. Activation dynamique

Ensuite on peut ajouter la possibilité d'activer ou de désactiver les logs de manière dynamique sur plusieurs niveaux :
  • messages permanents
  • messages de debug
  • messages d'erreur
#include <SoftwareSerial.h>

#if defined ESP8266 || defined(ESP32)
SoftwareSerial swSer(14, 12, false, 256);
#else
SoftwareSerial softSerial(10, 11);
#endif

#define LOGS_ACTIVE

#define LOG_DEBUG     2
#define LOG_ERROR     1

#define log_port Serial

#ifdef LOGS_ACTIVE
int log_level;

void log_setLevel(int level) {
  log_level = level;
}

void log_always(const char *msg)
{
  log_port.println(msg);
}

void log_debug(const char *msg)
{
  if (log_level >= LOG_DEBUG) {
    log_port.println(msg);
  }
}

void log_error(const char *msg)
{
  if (log_level >= LOG_ERROR) {
    log_port.println(msg);
  }
}

#else
#define log_setLevel
#define log_always
#define log_debug
#define log_error
#endif

void setup()
{
  log_port.begin(115200);
  log_always("***** BOOT *****");
  log_setLevel(LOG_DEBUG);
}

void loop()
{
  log_debug("This is a DEBUG information");
  log_error("This is an ERROR information");
  delay(2000);
}


Avec ce code il est facile d'activer les logs sur trois niveaux :
  • log_setLevel(0);                          // voir seulement les messages permanents
  • log_setLevel(LOG_ERROR);    // voir seulement les messages d'erreur
  • log_setLevel(LOG_DEBUG);    // voir tous les messages
Il est également possible d'utiliser une ligne série différente de Serial :
  • #define log_port Serial              // Serial
  • #define log_port Serial1            // Serial1
  • #define log_port Serial2            // Serial2
  • #define log_port Serial3            // Serial3
  • #define log_port sofSerial         // SoftwareSerial
Si l'on travaille en C++ il est également possible d'instancier un objet logger par fichier source et activer ou non les logs pour chacun d'eux.

Et il reste encore la possibilité de mettre la définition de LOGS_ACTIVE en commentaire pour supprimer les logs.

4.3. Expression du besoin

Notre logger, contrairement à la console sera développé sous forme de classe. Il sera instancié soit de manière unique soit dans chaque fichier source de l'application :

Logger LOGGER("test");
// ou
static Logger LOGGER("test");

Il sera possible d'instancier plusieurs loggers bien entendu. Par exemple les logs d'accès d'un serveur WEB seront enregistrés dans des fichiers. Les logs de debug seront simplement affichés.

Les fonctions de sortie de logs de la librairie accepteront des arguments variables, comme printf() ou les fonction de logs ESP32 :

void Logger::debug(const char *format, ...);

Nous allons factoriser tout cela et développer une fonction du type vprintf :

void Logger::vprintf(const char *format, va_list ap);

Les fonctions de logging utiliseront toutes cette fonction et donc tout passera par elle.

Les logs pourront être redirigés vers différents supports physiques. Chaque support sera géré par une classe Handler :
  • handler Stream (console standard, HardwareSerial ou SoftwareSerial)
  • handler de fichier sur SPIFFS
  • handler de fichier sur µSD
Bien entendu il est possible d'écrire un handler personnalisé (socket, syslog, MYSQL, etc.)
La librairie fournit un handler pour créer des fichiers dans le système de fichiers SPIFFS de l'ESP8266 et ESP32
Le logger possédera différentes classes de formatage de message :
  • formatage simple (formateur par défaut)
  • formatage indiquant la source du message
  • formatage avec horodatage par timestamp en milliseconde
  • formatage avec horodatage par date et heure
Bien entendu il est possible d'écrire un formateur personnalisé
Dans le cas où les logs sont générés dans des fichiers, la récupération de ceux-ci sera confiée à un serveur FTP ou HTTP.

4.4. L'application

La première des choses à faire sera, dans l'application, d'initialiser la ou les lignes série choisies :

  Serial.begin(115200);

Ensuite nous passerons l'adresse de cette instance du type Stream aux fonctions d'initialisation de la librairie. La console et le ou les loggers pourront fonctionner avec deux lignes série différentes, ou rediriger les messages vers un fichier.

Cette adresse sera mémorisée par la fonction console_init() dans une variable du type Stream :

Stream *console_output;

Elle sera aussi mémorisée par le constructeur de l'instance du logger ou du handler choisi.

Un exemple avec un logger sur Serial et un logger sur SD :

Logger LOGGER("http");
LogNameFormatter formatter(&LOGGER);
StreamLogHandler streamHandler(&formatter, &Serial);
Logger ACCESS("access");
LogTimeDateFormatter logFormatter(&ACCESS);
SdFatFileLogHandler logHandler(&logFormatter, &SD, "/access.log", 10000, 5);
void setup()
{
  Serial.begin(115200);
  console_init(&Serial);
  printf("ESP32 SDFAT logging WebServer demo\n");
  LOGGER.init(&Serial);  LOGGER.setLevel(DEBUG);
  formatter.setHandler(&streamHandler);  LOGGER.setFormatter(&formatter);
  ACCESS.init(&Serial);
  ACCESS.setLevel(INFO);
  logFormatter.setHandler(&streamHandler);  ACCESS.setFormatter(&logFormatter);
  if (SD.begin(SS)) {
    log_info("SD Card initialized.");
    logFormatter.setHandler(&logHandler);    log_info(F("SdFatFileLogHandler installed"));
  }
}


Dans cet exemple, on passe au formateur logFormatter le handler streamHandler, donc de type Stream.

Si la SD est montée avec succès, le handler est changé pour que les messages du logger ACCESS soient enregistrés. Sinon on continue en affichant sur le handler streamHandler.

Afin de simplifier les choses, comme cela correspond à la majeure partie des utilisations, le logger pourra être créé avec l'adresse d'un Stream en paramètres. Il pourra créer ainsi lui-même son formateur et son hander par défaut.

Un exemple avec un seul logger sur Serial :

Logger LOGGER("test");

void setup()
{
  Serial.begin(115200);
  console_init(&Serial);
  printf("Basic logging demo\n");
  LOGGER.init(&Serial);
  LOGGER.setLevel(DEBUG);
}


5. La librairie

La librairie est disponible ici :
https://bitbucket.org/henri_bachetti/mpp-console-logger.git

Cette page vous donne toutes les informations nécessaires :
https://riton-duino.blogspot.com/p/migration-sous-bitbucket.html

De nombreux exemples d'utilisation sont fournis.

Certains exemples, comme leur nom l'indique, sont dédiés :
  • ESP32-sdfat : logs sur µSD.
  • ESP32-sdfat-http-server : WebServer avec logs sur µSD
  • ESP32-spiffs : logs sur SPIFFS.
  • ESP32-spiffs-http-server : WebServer avec logs sur SPIFFS
  • ESP8266-spiffs : logs sur SPIFFS.
Les serveurs WEB des exemples permettent de visualiser les logs et de les télécharger.

Les exemple suivants sont utilisables sur une UNO, NANO ou MINI :
  • console
  • basic-logger
  • basic-logger-handler
  • name-log-formatter
  • timedate-log-formatter
  • timestamp-log-formatter
  • soft-serial-logger
Les exemple suivants sont utilisables sur une MEGA  ou un STM32 :
  • tous les exemples précédents
  • dual-serial-logger
Les exemple suivants sont utilisables sur un ESP8266 :
  • tous les exemples précédents
  • ESP-spiffs 
Le support SD n'est pas implémenté pour l'ESP8266 dans la librairie, car le renommage des fichiers est absent.
Les exemple suivants sont utilisables sur un ESP32 :
  • tous les exemples précédents
  • ESP32-sdfat
  • ESP32-sdfat-http-server
  • ESP32-spiffs-http-server
Exemple basic-logger sur une UNO :
Le sketch utilise 4478 octets (13%) de FLASH et 418 octets (20%) de RAM.

Exemple soft-serial-logger sur une UNO :
Le sketch utilise 6846 octets (21%) de FLASH et 659 octets (32%) de RAM.

Cordialement
Henri

6. Mises à jour

18/03/2019 : exemples de serveurs WEB
23/03/2019 : ajout du STM32
15/04/2019 : ajout d'une méthode hexDump