Bonjour mes amis!Nikolay est Ă nouveau avec vous, dans le dernier article "
DevBoy - comment j'ai créé un projet d'appareil open source et lancé un projet sur Kickstarter ", l'accent était mis davantage sur l'apparence et le matériel, aujourd'hui nous allons parler de la façon dont cela se fait "à l'
intérieur " et analyser la partie logicielle.

Peu importe - je demande un chat.
Comme mentionné précédemment, le projet est basé sur le microcontrôleur
STM32F415RG de STMicroelectronics sur le cœur ARM Cortex-M4. Il existe plusieurs IDE différents pour développer des microcontrôleurs pour ces microcontrôleurs, cependant, pour un projet open source, vous avez besoin d'au moins un IDE gratuit, et de préférence Open Source. En outre, l'IDE doit toujours être pris en charge dans
STM32CubeMX . Au moment où j'ai commencé à travailler sur ce projet, il n'y avait qu'un seul IDE qui répondait à toutes ces exigences -
System Workbench pour STM32 .
À l'heure actuelle, il y a Atollic TrueStudio, qui est devenu gratuit après que STMicroelectronics les a achetés.
Le prochain programme utilisé est
STM32CubeMX . Ce programme est un utilitaire pour configurer les périphériques du microcontrôleur à l'aide d'une interface graphique.

Le résultat est un code qui inclut
H ardware
A bstraction
L ayer (HAL). De nombreux programmeurs n'aiment pas vraiment cette "
création ", elle n'est pas sans bugs, mais, néanmoins, elle simplifie grandement le développement et améliore la portabilité des programmes entre les différents microcontrôleurs de STMicroelectronics.
De plus, pendant la configuration, vous pouvez spécifier l'utilisation de certains logiciels open source tiers tels que
FreeRTOS ,
FatFS et d'autres.
Nous avons terminé la description du logiciel utilisé, passons maintenant à la partie la plus intéressante -
DevCore . Le nom vient du "
Core Development " allons-y dans l'ordre.
Tout d'abord, c'est
Wrapper C ++ RTOS (
FreeRTOS dans ce cas ). Vrapper est nécessaire pour deux raisons:
- Il est beaucoup plus agréable de créer un objet, puis d'appeler mutex.Take () par exemple, que de créer un handle, d'appeler la fonction create, puis de passer ce handle à toutes les fonctions mutex
- S'il est nécessaire de remplacer RTOS, il suffit de remplacer le wrapper, et pas tous les appels aux fonctions RTOS du code
Cela n'a pas de sens d'apporter le code wrapper ici, peu importe -
nous regardons GitHub , et nous continuons.
La partie suivante est le
cadre d'application . Il s'agit de la classe de base pour toutes les tâches. Comme il ne s'agit que de deux fichiers relativement petits, il est logique de les répertorier complètement:
Les classes héritées peuvent remplacer 4 fonctions virtuelles:
- Setup () est une fonction appelée avant de démarrer une tâche. L'achèvement du code est garanti dans toutes ces fonctions de toutes les tâches avant l'exécution des cycles principaux.
- Loop () - le cycle de tâche principal, où la tâche elle-même organise ce qu'elle veut. Ne peut pas être utilisé conjointement avec les deux fonctions suivantes.
- TimerExpired () - une fonction appelée périodiquement avec un intervalle donné. Pratique pour implémenter l'interrogation d'un capteur par exemple.
- ProcessMessage () - fonction de traitement des messages provenant d'autres tâches.
Les deux premières fonctions implémentent "
Arduino-Style " pour les tâches.
Les deux suivants implémentent le système «
événementiel » simplifiant l'interaction des tâches. Avec cette approche, la tâche implémente une interface externe sous la forme de fonctions qui envoient des données à la tâche via une boîte aux lettres interne. Avec cette approche, l'utilisateur utilisant cette interface n'a pas besoin de s'inquiéter dans quel contexte les actions sont effectuées. Certes, cela n'est possible que pour les setters ou les équipes. Pour les getters, il est préférable d'utiliser des mutex et la copie de données pour empêcher la capture de mutex pendant une longue période.
Cette approche a été repérée lorsque je développais un logiciel pour les équipements médicaux. Le microcontrôleur a un
chien de garde et, dans le cas de nombreuses tâches, vous devez tous les suivre. Pour cela, il y avait une tâche distincte qui servait de chien de garde et recevait des messages d'autres tâches envoyées par la fonction TimerExpired (). Si pendant la durée de la minuterie de la tâche * n il n'y avait pas de message, la tâche est morte, nous
éteignons les lumières et prenons des mesures pour éteindre toutes les glandes qui affectent le patient.
Toutes les tâches sont singleton, vous ne pouvez pas les créer directement, mais vous pouvez obtenir un lien vers la tâche. Pour ce faire, chaque tâche implémente la
méthode statique
GetInstance () :
Sont également incluses les tâches de
sortie audio , de
modules d'entrée et de
maintenance d'écran.La tâche
de sortie du son est assez simple - il reçoit un tableau de fréquences et de durées et modifie simplement périodiquement les paramètres de la minuterie pour générer des impulsions rectangulaires d'une certaine fréquence.
La tâche de l'entretien
des modules d'eau est également assez simple. Parmi les points intéressants, le module est automatiquement détecté: d'abord, à l'aide de l'ADC, nous mesurons la tension, si elle est comprise entre 25% et 75% de la tension d'alimentation, un joystick analogique est inséré, sinon des boutons ou un encodeur. S'il ne s'agit pas d'un joystick, vérifiez la quatrième ligne du module d'E / S: s'il est à un niveau élevé, ce sont des boutons (
tous les boutons sont tirés vers le haut pour l'alimentation et, lorsque les boutons sont enfoncés, ils sont fermés au sol ), s'il est bas, c'est un encodeur (un
petit bouton est «tiré vers le haut») au sol et lorsqu'il est pressé se ferme au pouvoir ).
La tâche de maintenance d'écran est la tâche la plus intéressante. Pour commencer, l'écran est de 320x240x16 bits, vous avez donc besoin de 153600 octets pour le framebuffer. Ce n'est pas seulement beaucoup, c'est simplement énorme - dans ce microcontrôleur, il n'y a que 192 Ko de RAM, et dans les microcontrôleurs, il peut être plus facile de ne pas avoir du tout la bonne taille. Comment être? La réponse est simple: dessinez l'écran en plusieurs parties! Mais vous pouvez dessiner quelque chose de différentes manières ...La solution que j'ai appliquée pour cette tâche est comme toute ingénieuse. Il a un tampon sur deux lignes d'écran. Nous dessinons tout ce qui devrait être sur une seule ligne, l'envoyons à l'écran via SPI en mode DMA, et à ce moment nous pouvons préparer une autre ligne.Comment la tâche sait-elle ce qui devrait être dans la ligne et comment la dessiner? Mais elle ne sait pas!Mais elle a une liste d'objets qui savent se dessiner. Chacun de ces objets est hérité de la classe VisObject .La tâche de maintenance de l'écran pour chaque ligne passe par la liste des objets et appelle la fonction DrawInBufW (), en lui passant un pointeur sur le tampon, le nombre de points, la ligne à tracer et la position de départ ( jusqu'à l'idée d'utiliser le mode contrôleur d'écran pour mettre à jour la fenêtre ). En fait, chaque objet se dessine au-dessus des autres déjà dessinés et il est facile de disposer les objets dans l'ordre requis simplement en les plaçant à la position souhaitée dans la liste.De plus, cette approche facilite l'intégration du traitement des objets actifs - après avoir reçu les coordonnées du contrôleur à écran tactile, la tâche de maintenance de l'écran peut parcourir la feuille de calcul depuis la fin à la recherche de l'objet actif tombant dans les coordonnées de la pression. Si un tel objet est trouvé, la fonction virtuelle Action () est appelée pour l'objet donné., (
, , ), (
).
DevCore UI(
), I2C, I2C BME280 EEPROM 24256, — .
GitHub :
https://github.com/nickshl/devboyPS , Epic Fail'. , - "
", 180 $ d'une personne qui a probablement entendu parler de ce projet à partir d'un article sur Habré ( Merci, Andrey! ) Et le reste de mon collègue d'un cube voisin.Ne pas collecter d'argent n'est pas un problème. Le problème est un manque d'intérêt pour le projet ...