dimanche 21 avril 2019

ARDUINO : un protocole série




ARDUINO : un protocole série


Le but de cet article est de décrire un protocole de transmission de données sur ligne série. Ce protocole est fourni sous forme de librairie pour ARDUINO.

Il peut être utilisé entre deux systèmes du type ARDUINO ou un ARDUINO et un PC (un code écrit en PYTHON est également fourni).

1. Les bases

Le but d'un protocole est de fournir un service de transfert de données fiable et efficace entre deux systèmes.

On peut citer parmi les plus connus :
  • HDLC : High Level Data Link Control
  • SLIP : Serial Line Internet Protocol
  • PPP : Point To Point Protocol
  • KERMIT : transfert de fichiers
  • XMODEM, YMODEM, ZMODEM : transfert de fichiers
Un protocole transmet des entités appelées trames constituées le plus souvent de données encadrées par un marqueur ou fanions de début et de fin :


Dans cet exemple tiré de HDLC, dont est dérivé PPP d'ailleurs, nous voyons les deux fanions de début et de fin, mais également d'autres éléments :
  • adresse : le destinataire
  • commande : le N° de la commande à exécuter
  • FCS : un CRC de vérification de la validité des données
Les marqueurs de début et de fin permettent de s'assurer que la réception des données se fera intégralement. Tant que le marqueur de début n'est pas reçu, les caractères reçus sont ignorés. En effet à qui cela servirait-il de conserver des données si le début a été raté ?

Le CRC est un mot sur 16 bits calculé à partir des données et transmis à la suite de celles-ci. Il permettra au destinataire des données de vérifier leur intégrité.

Il ne faut pas confondre Checksum est CRC.
Un Checksum est une simple somme de tous les octets de données. Si deux octets sont permutés dans la trame, un Checksum ne permet pas de s'en rendre compte.
Un CRC est beaucoup plus élaboré et offre un niveau de sécurité nettement supérieur.

Un autre point important : un protocole sérieux implémente en général un acquittement des données transmises. Cela permet à l'expéditeur de savoir que sa requête est bien arrivée à destination et a été vérifiée.

Il ne faut pas confondre acquittement protocolaire et acquittement applicatif :

L'acquittement protocolaire permet au destinataire, en réponse à une requête, de dire qu'il a bien reçu celle-ci et qu'elle est conforme au protocole.
L'acquittement protocolaire est en général un simple octet : ACK.

L'acquittement applicatif permet au destinataire, en réponse à une requête, de dire qu'il a bien reconnu celle-ci et l'a bien exécutée.
Un acquittement applicatif est une trame à part entière et peut contenir :
  • un simple octet indiquant l'exécution de la requête :
    • oui / non
    • requête refusée
    • erreur interne
    • requête inconnue
  • un simple octet suivi de données
L'acquittement applicatif est facultatif, l'acquittement protocolaire ne l'est pas.

2. Les protocoles dits "légers"

Classiquement, dans le monde ARDUINO, un seul marqueur de fin est utilisé, qui est souvent '\r' ou "Carriage Return", la touche RETURN du clavier. Mais on rencontre aussi '\0' '\xff' ou '#' ou un quelconque caractère exotique.

En l'absence de marqueur de début, rien ne permet d'affirmer que la trame a été reçue dans son intégralité. Ce n'est pas un moyen sûr de transmettre des données.

L'absence de CRC est également un problème, car on n'est jamais certain qu'un parasite ne viendra pas perturber la transmission.

Pour ce qui est de l'acquittement des requêtes les amateurs se contentent en général de peu : on ignore purement et simplement ce point.

Lorsque l'on cumule absence de marqueur de début, absence de CRC et absence d'acquittement on obtient un protocole sans aucune fiabilité, qui montrera très vite ses faiblesses.

Bien entendu, pour transmettre des données destinées à un afficheur, il n'est pas nécessaire ni vital que le message soit transmis de manière sûre. Le seul risque est d'afficher une phrase incomplète ou comportant une faute d'orthographe.

Mais quand il s'agit d'envoyer des requêtes commandant l'activation de moteurs, de pompes, de gâches de porte, de vérins ou d'autres organes mécaniques, l'erreur de transmission peut poser un vrai problème et même coûter très cher.

3. Les besoins

Dans un projet ARDUINO les besoins sont réduits, la taille des données est faible, mais on ne doit pas négliger pour autant la fiabilité des données transmises.

Nous allons éviter les protocoles de transmission de fichiers, inadaptés. SLIP serait un choix possible mais il lui manque un petit CRC pour être plus sûr.Enfin PPP et HDLC sont vraiment très lourds et possèdent une notion de connexion dont nous n'avons pas besoin.

Je propose donc un protocole éprouvé provenant d'anciens projets développés dans le monde industriel sur des plateformes légères : ARM7, MSP430, STM32, etc.

Il a les caractéristiques suivantes
  • un marqueur de début et de fin standard : STX / ETX
  • acquittement ou non acquittement standard : ACK / NAK
  • un CRC sur 16 bits
  • mode transparent (DLE)
  • une numérotation des trames
  • un timer inter-caractères
  • un timer trame
  • des répétitions
 

3.1. Les caractères de contrôle

Ici les choix se sont portés sur des caractères de contrôle standard :
  • STX : Start of TeXt
  • ETX : End of TeXt
  • ACK : acquittement
  • NAK : non acquittement
  • DLE : échappement
Un caractère STX est transmis en début de chaque trame. Un caractère ETX est transmis en fin de chaque trame. Ceci permet de s'assurer que la trame reçue est complète.

3.2. CRC et acquittement

Chaque trame reçue est acquittée par un caractère ACK par le destinataire si le CRC recalculé par lui est correct. Dans le cas contraire, NAK est envoyé. L'émetteur répétera donc la requête. Le nombre de répétitions est fixé à 3.

Une trame reçue est acquittée par le destinataire en précisant quel est le numéro (SEQ) de la trame concernée. L'expéditeur peut donc s'assurer que la trame acquittée est bien celle qu'il a émise et pas une autre, transmise précédemment par exemple.

3.4. Le mode transparent

Qu'est-ce que le mode transparent ?
Si les données transmises sont binaires, il est parfaitement possible d'y rencontrer des caractères de contrôle STX (0x02), ETX (0x03), ACK (0x06) ou NACK (0x15).
Le mode transparent permet d'échapper ces caractères à l'aide d'un caractère spécial DLE (0x10). A chaque fois que le destinataire reçoit DLE, il sait donc que le caractère suivant est une donnée, et non pas un caractère de contrôle.

3.5. Les timers

Le timer inter-caractères est utilisé pour interrompre la réception des données dans le cas où le temps séparant deux caractères dépasse une certaine valeur. Cette valeur est fixée à 500 millisecondes.

Ce timer est important car quand la réception est interrompue, si le destinataire attend indéfiniment la suite de la trame qui n'arrive pas, il va se retrouver dans une situation critique car l'expéditeur, ne voyant pas d'acquittement, va répéter la requête. Le timer de répétition d'une requête est de 3 secondes.
Ce timer permet donc de jeter les caractères déjà reçus et de remettre l'automate de réception dans un état IDLE (en attente).

3.6. Les données

Les données transmises peuvent être composées d'un seul octet de commande ou d'un octet de commande suivi des données associées.
Il est bien entendu possible de réserver deux octets pour la commande. Rien n'est imposé.
Il est possible de transmettre également des mots et des phrases en clair. A la charge du destinataire d'interpréter la grammaire.

En général, j'organise les requêtes et les réponses sous forme de structures C, beaucoup plus faciles à interpréter.

4. L'implémentation

La librairie est disponible ici :
https://bitbucket.org/henri_bachetti/proton-serial-protocol/

Les librairies suivantes sont également utilisées :
https://bitbucket.org/henri_bachetti/mpp-console-logger/
https://bitbucket.org/henri_bachetti/kernel_timers 

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

Un exemple d'utilisation est fourni.

Cet exemple est organisé comme suit :
  • protonc-test.ino : le programme principal
  • host.h :  les définitions des structures des requêtes
  • host.cpp : l'implémentation des requêtes
  • server.h : les définitions de base du moteur
  • server.cpp : le moteur d'interprétation des requêtes
Un petit protocole applicatif est implémenté. Les commandes sont codées sur un octet et les réponses également :
  • 'V' : requête de demande de version
  • '!' :  echo
  • 'R' : reboot
  • 'Z' : poweroff (fictif)
  • 'U' : mise à l'heure
  • 'T' : récupération de l'heure
  • 'S' : delay
  • 'L' : LED ON ou OFF 
Chaque requête est acquittée par une réponse applicative contenant un octet identifiant la réponse :
  • 'v' : réponse à la demande de version
  • '!' :  réponse à echo
  • 'r' : réponse à reboot
  • 'z' : réponse à poweroff
  • 'u' : réponse à la mise à l'heure
  • 't' : réponse à la récupération de l'heure
  • 's' : delay
  • 'l' : LED ON ou OFF 
La réponse (acquittement applicatif) contient ensuite un octet de statut :
  • 'A' : requête acceptée
  • 'R' : requête refusée
  • 'B' : occupé
  • 'E' : erreur
  • 'U' : requête inconnue
La réponse contient ensuite les données, s'il y en a.

Le sketch exemple occupe de la place :
Le croquis utilise 15470 octets (47%) de l'espace de stockage de programmes.
Les variables globales utilisent 1106 octets (54%) de mémoire dynamique.

Il tient donc largement dans la mémoire d'une UNO ou NANO.
Il communique par l'intermédiaire d'un convertisseur USB / Série connecté sur les broches 10 et 11 (SoftwareSerial).

Un logiciel écrit en PYTHON est également présent dans la repository.
Il permet de tester le protocole et l'application exemple.

python test.py CommonTest.testVersion

Demande la version de l'application distante

python test.py CommonTest.testSetTime

Met à l'heure du PC l'application distante.

python test.py CommonTest.testGetTime

Récupère l'heure de l'application distante.

python test.py CommonTest.testAckTimeout

Teste le timeout d'acquittement

python test.py CommonTest.testCharTimeout

Teste le timeout inter-caractère

python test.py CommonTest.testNak

Test le non acquittement.

python test.py DemoTest.testLedOn

Allume la LED de l'ARDUINO.

python test.py DemoTest.testLedOff

Eteint la LED de l'ARDUINO.

python test.py

Exécute tous les tests.


PYTHON unittest est un bon outil de test très largement utilisé qu'il est assez intéressant de connaître.



Cordialement
Henri

vendredi 5 avril 2019

STM32 : l'ADC






STM32 : l'ADC



Cet article présente différentes techniques de travail avec l'ADC du STM32 :
  • le polling
  • les interruptions
  • la DMA
Si l'on veut se familiariser avec le STM32, quelques articles sont conseillés :
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
https://riton-duino.blogspot.com/2019/03/stm32-duino-deboguer.html

1. STM32DUINO

Le framework STM32DUINO, utilisable avec l'IDE ARDUINO, possède la même interface que le librairie standard ARDUINO. La mesure analogique se fait de la même façon avec analogRead().

Dans cet article nous n'allons pas aborder cette manière de procéder. D'une part elle est ultra connue, d'autre part son principe de fonctionnement n'est pas des plus optimisés.

Pour chaque appel à ananlogRead() l'ADC est totalement reconfiguré, ce qui provoque une perte de temps parfaitement inutile.

Remarque : l'utilisation d'une entrée analogique requiert une configuration, contrairement à ce qui se passe sur un ARDUINO :

pinMode(analogPin, INPUT_ANALOG);

Nous allons plutôt aborder ici la mise en œuvre des mesures analogiques à l'aide de la librairie ST (Stm32Cube).

Remarque : il est parfaitement possible d'utiliser la librairie Stm32Cube tout en restant sous IDE ARDUINO.

2. Mise en œuvre

2.1 Le logiciel

La mise en œuvre logicielle des différents essais a été fait avec l'IDE ARDUINO ou PlatfomIO. Dans les deux cas le framework ARDUINO est utilisé.

Le framework ARDUINO permet de ne pas se préoccuper des initialisations de base, en particulier celle de l'horloge système et de la ligne série pour la console.

2.2 Le hardware

Quelque soit la méthode choisie, il y a une marche à suivre commune :
  • choisir les pins sur lesquelles on veut faire la mesure
  • les configurer
  • savoir par quel canal ADC elles sont gérées

2.2.1. Choisir les pins de mesure

La première chose à faire est de récupérer le pinout de la carte, comme ici pour une NUCLEO F401RE :


On voit que les pins A0, A1, A2, etc. correspondent à PA0, PA1, PA4, etc..

2.2.2. Configuration

Il faut les configurer en entrée analogique :

int adc_configureGpios(void)
{
  GPIO_InitTypeDef gpioInit;

  __GPIOA_CLK_ENABLE();
  gpioInit.Pin = GPIO_PIN_0;
  gpioInit.Mode = GPIO_MODE_ANALOG;
  gpioInit.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOA, &gpioInit);
  return true;
}


2.2.3. Les canaux ADC

Dans la datasheet du microcontrôleur on trouve pour chaque pin le canal ADC correspondant :

https://www.st.com/resource/en/datasheet/stm32f401re.pdf

En page 39, on voit que PA0, PA1, PA4 correspondent aux canaux suivants :
  • PA0 : ADC1_IN0
  • PA1 : ADC1_IN1
  • PA4 : ADC1_IN4
Après avoir configuré l'ADC, on lui affecte un canal :

ADC_ChannelConfTypeDef adcChannel[2] =
{
  {ADC_CHANNEL_0, 1, ADC_SAMPLETIME_112CYCLES, 0},
  {ADC_CHANNEL_1, 1, ADC_SAMPLETIME_112CYCLES, 0}
};


// ...
  if (HAL_ADC_ConfigChannel(&adcHandle, &adcChannel[0]) != HAL_OK) {
    Serial.println("HAL_ADC_ConfigChannel failed");
    return;
  }
// ...


Le code complet : https://bitbucket.org/henri_bachetti/stm32duino-samples/

3. Le polling

Le polling est une technique qui ne fait appel ni aux interruptions ni à la DMA. L'application, après avoir configuré l'ADC, demande une conversion et attend que l'ADC ait terminé celle-ci pour aller lire le résultat.

Un STM32 peut posséder un seul ADC ou plusieurs.

3.1. Un seul ADC

Lorsque l'on désire lire des échantillons sur un seul canal la méthode est simple :
  • configurer une GPIO en entrée analogique
  • configurer l'ADC
  • configurer un canal ADC correspondant à la GPIO
  • démarrer l'ADC
  • tant que l'on a besoin de lire :
    • demander une conversion
    • attendre que l'ADC soit prêt
    • lire la valeur
  • stopper l'ADC
Lorsque l'on désire lire des échantillons sur deux canaux :
  • configurer deux GPIO en entrée analogique
  • configurer l'ADC
  • tant que l'on a besoin de lire : 
    • configurer le premier canal ADC correspondant à la GPIO N°1
    • démarrer l'ADC
    • demander une conversion
    • attendre que l'ADC soit prêt
    • lire la valeur
    • stopper l'ADC
    • configurer le deuxième canal ADC correspondant à la GPIO N°2
    • démarrer l'ADC
    • demander une conversion
    • attendre que l'ADC soit prêt
    • lire la valeur
    • stopper l'ADC 
On voit que la lecture de deux canaux demande pas mal de travail et que l'on perd par mal de temps à changer de canal.

Voir exemple : f401re-polling

3.2. Deux ADC

Lorsque l'on désire lire des échantillons sur deux canaux :
  • configurer deux GPIO en entrée analogique
  • configurer l'ADC1
  • configurer l'ADC2
  • configurer le premier canal ADC correspondant à la GPIO N°1
  • configurer le deuxième canal ADC correspondant à la GPIO N°2
  • démarrer l'ADC1
  • démarrer l'ADC2
  • tant que l'on a besoin de lire :
    • demander une conversion sur l'ADC1
    • demander une conversion sur l'ADC2
    • attendre que l'ADC soit prêt
    • lire la valeur
    • attendre que l'ADC soit prêt
    • lire la valeur
  • stopper l'ADC1
  • stopper l'ADC2
On voit que la lecture de deux canaux se fait sur deux ADC différents avec chacun un canal différent et que l'on ne perd plus de temps à changer de canal.

Le lecture peut très bien se faire sur deux ADC utilisant le même canal si l'on a besoin d'un nombre d'échantillons par seconde plus important.

Voir exemples : f429zi-dual-channel-poll, l476rg-dual-channel-poll

4. Les interruptions

L'utilisation des interruptions permet à l'application de ne pas aller lire l'ADC en permanence, en particulier lorsqu'elle est occupée à faire autre chose. L'application, après avoir configuré l'ADC, demande le démarrage de la conversion sous interruption et va consulter le résultat lorsque cela est nécessaire.

Il est à noter que si le temps d'échantillonnage est trop court l'exécution des interruptions risque de monopoliser  tout le temps CPU et l'application principale n'aura plus l'occasion de s'exécuter.
Voir exemple : f429zi-dual-channel-interrupt

Dans cet exemple utilisant deux canaux sur deux ADC différents, la principale difficulté réside dans la mise à jour des valeurs lues par la routine d'interruption. En effet le STM32 possède un seul vecteur d'interruption ADC. Pour savoir quel ADC a provoqué l'interruption, il faut aller consulter le registre de statut de chaque ADC : le bit EOC (End Of Conversion).

Avec cette technique il est possible d'effectuer une surveillance de tension par exemple, et réagir en exécutant une action même si l'application n'est pas apte à l'exécuter à ce moment-là.

Rappel : l'exécution d'un routine d'interruption doit être courte, obligatoirement plus courte que l'intervalle entre deux interruption.
On peut par exemple allumer un voyant, qui revient à manipuler une GPIO.
Mais envoyer un message par la ligne série en mode polling sera impossible. Il faudra utiliser là aussi une émission sous interruption.

5. La DMA

La DMA se rapproche de la gestion par interruptions, à la différence près que les transferts de données entre l'ADC et la mémoire sont faits sans intervention du processeur.

Voir exemple : f429zi-dual-channel-dma

Cet exemple utilise deux canaux sur un seul ADC. Le nombre de samples / seconde est évidemment deux fois moins important que dans l'exemple précédent utilisant 2 ADCs.

6. Les chiffres

Ceux-ci sont éloquents : https://bitbucket.org/henri_bachetti/stm32duino-samples/

Il est possible de monter jusqu'à presque 1.000.000 de samples par seconde avec certaines cartes.

Cette repository permet d'avoir accès au code.

7. Conclusion

L'ADC du STM32, d'une part est un ADC 12 bits, contrairement à celui d'un ARDUINO : 10 bits.
Certaines cartes sont équipées de plusieurs ADC, ce qui permet une rapidité de mesure accrue.


Cordialement
Henri

8. Mises à jour

10/03/2019 : 1. STM32DUINO

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 = -g3upload_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