La dernière fois, je vous ai expliqué comment démarrer avec la carte STM32F411 « Black Pill » en utilisant le système d’exploitation Mbed. Certes, le programme d’exemple n’utilisait pas beaucoup des fonctionnalités du système d’exploitation, à moins que vous ne comptiez ce que le pilote de port série USB utilise en coulisses. Cependant, cette fois, nous allons créer un jouet pratique qui vous permet de régler le niveau de volume de votre PC avec un potentiomètre.
Le Black Pill est un bon choix pour cette application car il possède des entrées analogiques et peut faire office de clavier USB. En fait, le système d’exploitation Mbed dispose de pilotes pour toutes sortes de périphériques USB. Nous avons vu le port série, mais vous pouvez aussi ressembler à un périphérique de stockage de masse ou à une souris, par exemple. Juste pour s’entraîner, nous allons créer deux threads d’exécution. L’un lira le pot et enverra un message à l’autre fil. Ce fil communiquera avec le PC comme un clavier USB. Tout ordinateur qui comprend les touches multimédias d’un clavier doit fonctionner avec l’appareil.
Fils
La création de threads est très simple. Dans de nombreux cas, vous définissez simplement une fonction void qui ne prend aucun argument et l’utilisez avec un objet Thread :
readknobThread.start(vol_thread);
Bien sûr, la fonction ne devrait pas revenir à moins que vous ne vouliez que le thread se termine. Comme je l’ai mentionné dans le dernier message, vous pouvez dormir avec le ThisThread::sleep_for
appel. Il y a aussi yield
appelez si vous voulez simplement abandonner la tranche de temps sans dormir pendant un certain temps.
Vous pouvez également créer une fonction qui renvoie et la faire exécuter s’il y a un temps d’inactivité où aucun thread n’est prêt à s’exécuter. Appel rtos_attach_idle_hook
pour régler cette fonction. Si vous n’en fournissez pas, la valeur par défaut met le processeur en veille.
Vous pouvez devenir fantaisiste avec les threads, principalement en modifiant leur taille de pile et leur priorité. La méthode join de l’objet Thread vous permet d’attendre qu’un thread se termine. Vous pouvez définir le nom d’un thread lors de la construction de l’objet. Cependant, pour ce faire, vous devez également spécifier toutes les autres options. C’est pratique lorsque vous déboguez afin que vous puissiez distinguer facilement un thread d’un autre. Voici comment:
Thread readknobThread(osPriorityNormal,OS_STACK_SIZE,nullptr,"KNOB"); Thread keyboardThread(osPriorityNormal,OS_STACK_SIZE,nullptr,"KBD");
Normalement, cependant, vous pouvez simplement vous en tenir au constructeur par défaut. Vous pouvez toujours modifier la priorité ultérieurement. Vous pouvez également définir la taille de pile par défaut (normalement 4 ko) dans le dossier du projet. json
fichier, tant que vous voulez que tous les threads utilisent la même valeur par défaut.
Le volume
Est-il difficile de faire ressembler la Black Pill à un clavier USB et d’envoyer, par exemple, une commande de réduction du volume ? Facile:
USBKeyboard kbd; ... kbd.media_control(KEY_VOLUME_DOWN);
Cela pourrait être un programme très simple en effet. Cependant, je voulais en jouer avec le multithreading, donc je l’ai rendu un peu plus difficile. Le programme a deux threads. On regarde le potentiomètre et on décide s’il s’est déplacé vers le haut ou vers le bas d’une quantité définie. Ensuite, il envoie une commande à l’aide d’une boîte aux lettres à l’autre thread.
L’autre thread attend l’arrivée du courrier et agit en envoyant des clés multimédias. Cela aussi est un peu exagéré car il n’y a vraiment qu’une seule donnée partagée entre les threads. Mais le mécanisme de messagerie peut transférer des structures arbitraires, il est donc utile de le savoir.
Communications multithreads
Le système d’exploitation Mbed offre plusieurs fonctionnalités pour aider les threads à coopérer :
- ConditionVariable – Un mécanisme permettant à un thread de signaler aux autres threads qu’une condition a changé.
- EventFlags – Semblable aux variables de condition, mais permet à un thread d’attendre plusieurs événements. Vous pouvez attendre que n’importe lequel d’un ensemble de drapeaux signale ou attendre que tout l’ensemble signale.
- File d’attente – Une file d’attente permet à un thread de charger des pointeurs qu’un autre thread consomme.
- Mail – Ceci est similaire à une file d’attente, mais stocke les données envoyées plutôt que des pointeurs.
- Mutex – Un mutex est une ressource qu’un seul thread peut posséder à la fois. Cela permet aux threads de coopérer sans interférer les uns avec les autres.
- Sémaphore – Ceci est similaire à un mutex, mais a un compte qui lui est associé. Vous ne pouvez pas utiliser de mutex dans un gestionnaire d’interruptions, mais vous pouvez utiliser un sémaphore.
Pour cet exemple simple, en supposant que nous voulions utiliser des threads, nous aurions pu utiliser presque n’importe lequel de ces mécanismes. Une variable globale avec une variable de condition, un événement, un mutex ou un sémaphore aurait bien fonctionné.
Une file d’attente fonctionnerait également, mais j’ai décidé d’utiliser le courrier. L’expéditeur appelle simplement try_alloc
sur la boîte aux lettres pour allouer de l’espace pour une nouvelle entrée. Ensuite, vous remplissez la nouvelle entrée et appelez put.
Le récepteur fait un try_get
et, une fois les données terminées, appelle gratuitement la boîte aux lettres pour libérer la mémoire dans le pool.
Un problème
Le seul problème avec la conception est qu’un pot n’est pas un encodeur optique. Il s’arrêtera aux alentours de 0 ohms et également à la valeur maximale. Cela signifie que le pot peut rester « bloqué ». Par exemple, si le pot est déjà complètement baissé lorsque l’appareil démarre, vous ne pouvez pas baisser le volume plus bas qu’il n’a démarré. Vous avez également des problèmes si, par exemple, vous baissez le volume et que quelqu’un d’autre l’augmente en utilisant une méthode différente.
Pour lutter contre cela, le code utilise le bouton de la pilule noire comme bouton de sourdine. De plus, il réinitialise l’idée de la position du pot lorsque vous activez ou désactivez le son. Donc, si vous êtes bloqué, vous pouvez suivre la procédure consistant à couper le son, à centrer grossièrement le pot, puis à réactiver le son.
Certes, cela aurait été un meilleur endroit pour un encodeur, mais je voulais faire une entrée analogique et il se trouve que j’avais des pots montables sur une planche à pain.
Le résultat
Le code résultant est sur GitHub. Je voulais suréchantillonner l’entrée analogique car il y avait pas mal de bruit sur la ligne donc j’ai créé la classe AnalogInOversample :
#ifndef __ANALOGINOVERSAMPLE_H #define __ANALOGINOVERSAMPLE_H // Simple class to read 16-bit counts from ADC and average N samples // Up to you not to overflow 32-bits! class AnalogInOversample : public AnalogIn { protected: uint8_t N; // # of samples public: // constructor assumes 16 samples, or set your own AnalogInOversample(PinName pin, uint8_t n=16,float vref=MBED_CONF_TARGET_DEFAULT_ADC_VREF) : AnalogIn(pin,vref) { N=n; } // access N uint8_t get_N(void) { return N; } void set_N(uint8_t n) { N=n; } // Here's the meat of it unsigned short read_u16(void) { uint32_t samples=0; // 32 bits for 16-bit samples for (int i=0;i<N;i++) samples+=AnalogIn::read_u16(); return samples/N; } }; #endif
Il y a plus
Bien sûr, vous pouvez faire beaucoup plus avec ces planches. Vous pouvez également appliquer la plupart de ce dont nous avons parlé avec Mbed à l’un des tableaux pris en charge. Si vous avez besoin d’un contrôle et de performances ultimes, vous préféreriez peut-être quelque chose d’un peu moins abstrait. Mais si vous avez besoin d’un simple RTOS, vous pouvez faire pire que passer du temps à vous renseigner sur Mbed.
Cela dit, le projet STM32Duino est également très robuste et si vous avez de l’expérience avec l’Arduino, vous le préférerez peut-être. Quoi que vous fassiez, ces planches sont d’un bon rapport qualité-prix et certainement faciles à utiliser.