Journal TapTempo sur STM32F469i-Discovery

Posté par . Licence CC by-sa.
41
28
avr.
2018

Voici une implémentation de TapTempo sur microcontrôleur. C'est un peu le "Hello, world!" 2.0 de l'embarqué car il permet de valider plusieurs périphériques et configurations en un seul coup : l'affichage (driver LCD, librairie graphique …), un bouton (test de l'algorithme de debouncing) ou touch screen, ainsi que la bonne fréquence du processeur et du système sous-jacent (timer OS ou hardware).

La cible embarquée n'est pas un Arduino mais un coeur plus puissant : un Cortex-M4 à 180Mhz de chez ST Micro, plus précisément le modèle STM32F462 intégré sur une carte de développement appelée discovery. Celle-ci embarque un bon nombre de périphériques dont un très beau LCD de 4 pouces avec capteur capacitif.

L'exemple n'utilise pas de RTOS mais un scheduler simple proposé par la librairie graphique utilisée ici : uGfx. La librairie propose des widgets classiques, j'en utilise un ici, le bouton servant à capturer les appuis digitaux (pas numérique, le doigt quoi).

Les digits sont des images BMP générées avec ImageMagick par la commande suivante (ici pour le chiffre zéro) :

convert -background black -fill white -gravity Center -size 100x200 -font DejaVu-Sans-Mono  caption:"0" 0.bmp

Puis, on les transforme en tableau d'octets :

./file2c -c -n digit_0 -f 0.bmp 0.bmp romfs_0.h

'file2c' étant un petit utilitaire fournit par uGfx. Notez que Linux intègre de base un utilitaire similaire, je l'ai découvert il y a peu :

xxd -i -a filename

On génère ainsi les 10 chiffres utilisés pour l'affichage des BMP (pas l'image mais le nombre d'appuis par seconde, faut suivre un peu !).

Un timer de 100ms périodique permet de réaliser notre calcul moyen de BMP, puis on rafraîchit l'écran toutes les secondes.

Enfin, l'absence d'appui pendant 3 secondes provoque une remise à zéro des calculs.

Concernant les outils de développement, une sonde JTAG est intégrée sur la carte. Celle-ci permet d'utiliser plusieurs programmateur et débogueurs ; pour ma part, j'ai utilisé l'environnement “System Workbench for STM32 - Bare Metal Edition” disponible gratuitement via http://www.openstm32.org. C'est en gros Eclipse + GCC bien packagé avec tous les outils qu'il faut (OpenOCD notamment, pour le débogage sur cible).

Je vous ai fait une petite vidéo et le code source est disponible sur github comme d'habitude, en attendant l'intégration dans le compte de la fédération Taptempo que je compte bien rejoindre.

Image TapTempo

  • # Arduino ?! formulation maladroite

    Posté par . Évalué à 2.

    La cible embarquée n'est pas un Arduino mais un coeur plus puissant : un Cortex-M4

    Tu fais un raccourci et compares un potager avec une variété de tomate : Arduino n'est pas une architecture de microcontroleur mais l'une des marques de cartes de prototypage basées sur des microcontroleurs Atmel.

    Donc quand tu parles d'Arduino, tu veux parler de la puissance des microcontroleurs de type AVR (ou PIC, vu que c'est la même chose surtout depuis le rachat d'Atmel par Microchip en 2016) ou bien de celle du Cortex-M3 disponible sur l'Arduino DUE ?

    • [^] # Re: Arduino ?! formulation maladroite

      Posté par . Évalué à 2.

      Tu es imprécis aussi, un PIC n'est pas identique à un AVR, un rachat de société ne rapproche pas pour autant les cœurs ;)

      Je pense que tu sais répondre à ta propre question, surtout que ton extrait est tronqué donc tu pinailles, c'est vraiment lourd.

    • [^] # Re: Arduino ?! formulation maladroite

      Posté par . Évalué à 1.

      En même temps, le point important, à mon avis, c'est surtout de sortir de l'environnement Arduino. La gamme STM32, ça se programme à plus bas niveau qu'un Arduino, quelle que soit son architecture.

      Il faut, entre autre, initialiser les horloges, configurer les pins utilisés pour leur usage (simple GPIO in/out ou partie d'un bus programmé en hardware) ainsi que les "modules" (hardware) utilisées pour les différents BUS. Bref, tout plein de choses qu'Arduino a déjà fait pour toi (avec des choix probablement pas optimaux pour ton projet).

      Arduino, c'est très bien pour débuter le micro, pour permettre à plein de gens dont ce n'est pas le métier de prototyper leurs idées. Mais ça n'a rien à voir en terme de possibilités avec une Nucleo (c'est bien plus puissant ^ ^ ).

      • [^] # Re: Arduino ?! formulation maladroite

        Posté par . Évalué à 4. Dernière modification le 01/05/18 à 15:50.

        Je ne vois pas le rapport, la phrase parlait de puissance et pas d'optimisation ou de niveau de programmation.

        Arduino IDE ce n'est qu'un environnement, tu peux même y programmer pour un STM32 comme tu peux t'en passer pour les cartes de marque Arduino.
        Les bibliothèques pour les bus ça se fait aussi très bien à la main sur du Atmel et même via l'IDE Arduino.
        Tu peux aussi programmer du Atmel en langage assembleur si tu as besoin de timings ultra précis.
        Il y a une bonne quantité de projets sur les forges logicielles faits pour des microcontroleurs Atmel qui n'utilisent pas du tout l'environnement Arduino.

        Enfin, comme je l'ai dit Arduino est aussi passé à du Cortex-M pour sa nouvelle génération de cartes donc non ce n'est pas "bien plus puissant" à moins que tu ne parles que de l'ancienne gamme sur AVR.

        Bref, merci de ta réponse qui me laisse penser qu'il y a une confusion populaire entre les cartes Arduino, l'IDE du même nom, les microcontroleurs Atmel et l'architecture AVR.

  • # Logique

    Posté par (page perso) . Évalué à 2.

    Sympa cette nouvelle version de TapTempo !

    J'ai exploré un peu le code, avec la fonction callback1 du fichier src/main.c, et le calcul tu tempo s'effectue apparemment sur le nombre de frappe par intervalle de 3 secondes, et le compteur de frappes est remis à zéro toutes les 3 secondes ?
    J'en déduis qu'il n'y a pas de moyenne glissante comme dans l'original ?

    Autre question : à quoi sert la variable memoriseCounter ?

    Merci !

  • # Un bug d'accès de variables partagées en passant...

    Posté par . Évalué à 3.

    Salut,

    Je n'ai regardé que cet aspect là, je n'ai pas regardé le reste… A moins que je ne me trompe sur le séquencement des tâches ne connaissant pas l'environnement, mais la fonction callback1 est appelée par la routine d'interruption (ISR).
    Tes variables tapCounter et elapsedTime sont modifiées en même temps dans le main et dans l'interruption, ça fera un bug aléatoire en fonction de l'instant quand tombe ton interruption par rapport à l'exécution de ton main.
    Dans l'embarqué, ce genre d'erreur ne pardonne pas (on appelle ça une erreur de débutant chez les vieux barbus de l'embarqué) mais c'est une erreur habituelle et même une inattention est vite arrivée!
    Une petite explication pour ceux qui ne trouve pas celà évident au premier abord

    Il faut toujours masquer les interruptions qui vont bien avant de modifier une variable partagée entre les fonctions appelées par le main et les fonctions appelées par les routines d'interruptions.
    En généralisant, il faut toujours masquer les interruptions ou utiliser les services de l'os qui vont bien (sémaphores, mutex, ou autres) lorsqu'on modifie ou lorsqu'on accède à des variables partagées par des thread différents.
    Un bug méchant mais qui ne semble pas évident au premier abord: sur un micro-contrôleur 8 bits, l'accès en lecture d'une variable 32 bits accédée en lecture simple par le main (même avec une variable 16 bits, ça bug aussi)

    u32 variable;
    
    interruption ()
    {
        variable++
    }
    
    main
    {
        while (true)
        {
            if (variable > 0x00010000UL)
            {
                masqueInterruptions()
                variable = 0
                restaureInterruptions()
            }
        }
    }

    est buguée (si! si! je l'ai eut testé)! Pourquoi?
    Parce que la comparaison d'un 32 bits par un micro 8 bits se fait en plusieurs coups d'horloge (la connaissance de l'assembleur aidant pour lire le code généré). Il suffit (non pas d'un signe, un matin, même s'il est tout tranquille… ) mais que l'interruption 'claque' en plein milieu de l'exécution de ces instructions, le test peut être faux et ta variable peut ne pas être remise à 0.

    Il faut écrire la fonction main de la façon suivante:

    main
    {
       u32 temp32b;
    
        while (true)
        {
            masqueInterruptions()
            temp32b = variable
            restaureInterruptions()
    
            if (temp32b > 0x00010000UL)
            {
                masqueInterruptions()
                variable = 0
                restaureInterruptions()
            }
        }
    }

    Et même la fonction que je te propose peut être fausse si l'opération dans l'interruption est plus complexe et que le test dans la fonction main est aussi plus complexe. Dans ce genre de cas, il vaudrait mieux masquer les interruptions avant l'accès:

    main
    {
        while (true)
        {
            masqueInterruptions()
            if (variable > 0x00010000UL)
            {
                variable = 0
            }
            restaureInterruptions()
        }
    }

    Ce qui n'est pas sans pouvoir causer des problèmes de temps réel car on masque les interruptions trop longtemps. Mais c'est une autre histoire…

    Je viens de voir qu'ils ont implémentés des fonctions noInterrupts() et interrupts() dans l'environnement arduino.

    • [^] # Re: Un bug d'accès de variables partagées en passant...

      Posté par (page perso) . Évalué à 1.

      Pour le néophyte du logiciel embarqué que je suis, peux-tu préciser en quoi consiste l'action de "masquer les interruptions" ?
      Est-ce l'utilisation d'une sorte de mutex matériel ?

      • [^] # Re: Un bug d'accès de variables partagées en passant...

        Posté par . Évalué à 3.

        Dans l'exemple ici, l'objectif étant d'utiliser une variable (case mémoire) commune entre 2 fonctions, l'une pouvant interrompre l'autre; Dans un OS, on peut utiliser un
        mutex

        On pourrait simplifier à l'extrême et utiliser une implémentation qu'on pourrait appeler un "mutex matériel" comme tu le proposes (même si je ne suis pas entièrement d'accord pour des détails que je n'exposerait pas ici), mais "masquer des interruptions" est un mécanisme matériel qui peut être utilisé pour beaucoup plus de cas.

        Je ne sais pas quelle est ta connaissance des micro-contrôleurs, processeurs, alors je vais simplifier pour ceux qui passent par là avec une connaissance limitée, ne te vexe pas alors si tu trouve ça trop simplifié! ;-)

        Simplifié" à l'extrême, ne interruption, est un signal hardware ou software (appui sur un bouton, signal généré par un timer hardware, autre…) qui va faire que la cpu va interrompre l'exécution de la liste d'instructions en cours pour exécuter une fonction (listée dans la table des vecteurs d'interruption)
        Si dans le cas ici, callback1 est une fonction appelée par la routine d'interruption, la cpu va interrompre l'exécution des instructions de la fonction main.
        Masquer les interruptions permet d'empêcher l'exécution des routines d'interruptions (et donc de la fonction callback1) jusqu'à ce qu'on les réactive.

        C'est comme l'histoire de 2 personnes (A et B) qui éditent un fichier partagé sur un disque partagé: si tu ne bloques pas B alors que A est en train de l'éditer, je te laisse imaginer les dégats.

    • [^] # Re: Un bug d'accès de variables partagées en passant...

      Posté par . Évalué à 0.

      Et je conseillerais aussi de déclarer variable en volatile

  • # L'environnement de dev pour STM32

    Posté par (page perso) . Évalué à 0.

    Bonjour,

    Je voulais juste signaler que ST à racheté Atollic qui développe l'IDE Atollic TrueStudio, beaucoup plus plébiscité dans le monde professionnel.
    Cette IDE est maintenant distribuée dans sa version qui est était alors une pro (analyse hard fault, des viewer de consommation mémoire, etc….).
    C'est aussi une IDE basée sur Eclipse + CDT

    Je la conseillerai plutôt car :
    - maintenant l'IDE officielle
    - plus stable à l'usage
    - Linux / Windows officiellement supporté.

    Je conseillerai aussi d'utiliser STMCube (HAL et middle ware) et son configurateur CubeMX, sachant que le toolkit graphique emwin est distribué gratuitement par le biais de ST, et que son forum officiel est bien actif.
    Le configurateur MX à quelques défauts, mais il vous permet de configurer simplement les clocks, d'activer ou pas les périphériques, et de générer le projet atollic pour de nombres cartes comme la Nucléo.

  • # infos complémentaire sur l'accès concurrent aux variables

    Posté par (page perso) . Évalué à 0.

    Quand au masquage des ITs pour protéger l'intégrité d'une variable, c'est en règle général vrai de la protéger mais tout dépend de ce que l'on fait…
    - C'est vrai si l'on écrit sur cette donnée à partir d'un calcul qui utilise sa valeur courante.
    - C'est un peu moins vrai si d'un côté on se contente d'écrire une valeur déterminée (0)
    - Ca l'est encore moins quand on écrit sur la donnée d'un côté et que l'on se contente de la relire de l'autre (exemple des FIFOs non bloquante producteur/consommateur).

    Il faut rappeler aussi que l'usage des mutex ou verrou bloquant est réservée interthread, et que l'IT devra utiliser les routines non bloquantes.
    Attention aussi en masquant une IT, on peut désactiver plusieurs traitements qui ne sont pas concurrents, ou exposer une zone critique lorsque l'on réactive l'IT alors qu'il ne le faudrait pas (car le contexte appelant a désactivé l'IT).

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.