Alternatives de thread pour les systèmes embarqués
J'étudie actuellement le génie électrique. En raison des pandémies, mes cours ont été suspendus et j'utilise ce temps pour en apprendre davantage sur l'électronique et la programmation.
J'essaie actuellement d'utiliser un Pic16f628a
et un affichage numérique générique pour construire une horloge numérique avec certaines fonctionnalités. Le truc, c'est que j'avais dû accéder à un menu en appuyant sur un bouton dans le temps d'exécution, pendant que l'horloge était affichée. Normalement, j'appelle un thread pour l'affichage de l'horloge et le thread principal surveille les entrées, mais en raison de la simplicité du contrôleur pic, je ne peux pas utiliser la ressource.
Donc, mon code C (pas encore implémenté spécifiquement pour pic) est quelque chose comme ceci:
void display_timer(){
static struct current_time timer;
static int is_time_set = 0;
set_current_time(&timer, &is_time_set);
while (is_time_set){
system("clear");
printf("########\n");
printf("%d:%d:%d\n", timer.HOURS, timer.MINUTES, timer.SECONDS);
printf("########\n");
sleep(1);
update_timer(&timer, &is_time_set);
}
}
int main ()
{
while (1){
display_menu();
}
}
Pendant le sommeil (), le contrôleur devrait être capable de surveiller les nouvelles entrées et d'agir en conséquence.
Une alternative que j'ai pensé était d'utiliser une machine d'état pour stocker une pression sur un bouton, en divisant la fonction de veille en 4 ou 8 intervalles, quelque chose comme ceci:
while (is_time_set){
system("clear");
printf("########\n");
printf("%d:%d:%d\n", timer.HOURS, timer.MINUTES, timer.SECONDS);
printf("########\n");
for (int i = 0; i<8; i++){
if (state_machine_input == 1){state_machine_input = 0; break;}
sleep(1/8);
}
update_timer(&timer, &is_time_set);
Cela pourrait être fait, mais j'apprécierais de ne pas avoir à ajouter plus de complexité au projet, en ajoutant une autre machine à états par exemple. Que puis-je faire dans les thermes du logiciel pour implémenter cette fonctionnalité?
Réponses
Le filetage est un concept de niveau supérieur à la programmation par microcontrôleur. En termes simples, les threads sont implémentés comme un planificateur qui utilise des interruptions de minuterie, qui à son tour enregistre le compteur de programme + le pointeur de pile, etc. Il est donc tout à fait possible et facile d'implémenter un concept similaire en utilisant des interruptions - avec l'avantage que vous obtenez des interruptions spécialisées au lieu d'un multi-threading générique.
C'est à peu près la seule façon judicieuse de le faire avec un héritage restreint 8 amer comme PIC, qui est extrêmement limité en ce qui concerne l'utilisation de la pile. Oubliez les bibliothèques de threads, même celles écrites pour les microcontrôleurs. Cela ne fera qu'ajouter des ballonnements excessifs et de la complexité, pour rien de gagné. C'est une mauvaise idée en général de faire glisser des concepts de programmation PC dans le monde embarqué.
Ce que vous devriez faire, c'est mettre le balayage de vos boutons dans une interruption de minuterie cyclique qui est exécutée une fois toutes les 10 ms environ. De l'intérieur de l'interruption, vous interrogez les boutons et comparez le bouton lu avec le précédent une fois, à des fins de dépannage. Le résultat de ceci est stocké dans une variable partagée avec le programme principal, déclaré comme volatile
et protégé des conditions de course. Puisque vous n'écrivez dans la variable qu'à partir des interruptions, cela peut être une protection suffisante pour vous assurer que les lectures sont de 8 bits, mais vous devez démonter pour être sûr. Plus d'informations à ce sujet ici: Utilisation de volatile dans le développement C embarqué .
Utiliser les interruptions
Vous voulez exécuter du code en appuyant sur un bouton? Utiliser une interruption de changement de broche
Vous souhaitez faire quelque chose à intervalle fixe? Utiliser une interruption par minuterie
D'une certaine manière, le matériel du microcontrôleur exécute un «thread» qui surveille les sources d'interruption, et exécute un «rappel» ou une routine d'interruption pour chaque événement.
Le programme principal est automatiquement mis en pause lors de l'exécution de l'interruption.
Un moyen courant de partager des données entre les interruptions et le code principal consiste à volatile
utiliser des variables globales et à désactiver temporairement les interruptions lors de la lecture de données à partir de ces globaux lorsqu'ils dépassent la taille de mot du contrôleur (presque toujours sur un contrôleur 8 bits)
Je suggérerais probablement une bibliothèque multitâche coopérative. Un que j'ai utilisé dans le passé est Protothreads:http://www.dunkels.com/adam/pt/
Toute bibliothèque multitâche coopérative décente aidera à faire abstraction de la machine à états implicite requise pour suivre les choses.
Bonne chance.
Il existe en général différentes approches du multitâche en matière de système embarqué:
- Interrogation ou Multitâche coopératif : Tout est fait dans une boucle infinie et les tâches sont conçues pour prendre le moins de temps possible et revenir à l'exécution principale le plus rapidement possible, pour éviter les retards. Notez que les tâches adaptées à cette architecture peuvent ne pas être ce à quoi vous penseriez en termes de concept de niveau supérieur, par exemple dans votre application, une tâche pourrait être
update_display
et une autre tâche pourrait êtrecheck_button
et vous créeriez une boucle telle que:
while(1){
check_buttons();
update_display();
sleep(0.1); //seconds
}
Interruptions : toutes les entrées possibles reliées aux interruptions matérielles et l'exécution principale est laissée aux choses qui ne peuvent pas être interrompues (peut-être rien, auquel cas généralement le microcontrôleur est mis en mode veille pour réduire la consommation d'énergie. Cela dépend généralement du microcontrôleur et du compilateur utilisés.
RTOS : en fonction de la puissance fournie par le microcontrôleur, il peut être possible d'exécuter un système d'exploitation en temps réel (RTOS), qui pourrait fournir une API pour créer des tâches ou même des threads. Cela dépend de l'application et des capacités matérielles, et pour les exemples éducatifs ne devrait pas être nécessaire (ou conseillé, imo)
Considérez également qu'un autre élément important dans la décision de l'architecture globale de l'application est la division des tâches et la manière dont elles coopèrent. L'un des paradigmes utilisés est les State Machines (le lien est vers la page wikipedia générale qui pourrait être écrasante, des ressources plus simples spécifiques à la programmation embarquée peuvent être trouvées sur votre manuel ou sur google).
La plupart du temps, les appareils 8 bits ont une source limitée. Je pense qu'une solution plus simple est une meilleure solution dans les PIC 8 bits.
Vous pouvez créer des minuteries matérielles pour 2 tâches différentes. Définissez un drapeau et vérifiez le drapeau dans votre boucle infinie, faites la tâche et réinitialisez le drapeau. N'utilisez pas de retards. Cette méthode garantit de faire vos tâches dans votre boucle infinie, si votre drapeau est en place.
Mais, vous devez savoir que les tâches ne sont pas exécutées au moment exact où les drapeaux se manifestent. S'il y avait deux indicateurs définis en même temps, vous ne pouvez pas savoir lequel est exécuté en premier. Parce que vous ne savez pas où dans la boucle infinie. Mais, c'est généralement bien pour les applications d'interfaçage qui ne sont pas critiques pour le temps.