Une grande variété de réponses ici ... abordant principalement le problème de diverses manières.
J'écris des logiciels et micrologiciels de bas niveau intégrés depuis plus de 25 ans dans une variété de langues - principalement C (mais avec des détournements vers Ada, Occam2, PL / M et une variété d'assembleurs en cours de route).
Après une longue période de réflexion et d'essais et d'erreurs, je me suis installé dans une méthode qui obtient des résultats assez rapidement et est assez facile de créer des enveloppes de test et des harnais (où ils ajoutent de la valeur!)
La méthode va quelque chose comme ceci:
Écrivez un pilote ou une unité de code d'abstraction matérielle pour chaque périphérique principal que vous souhaitez utiliser. Écrivez-en également un pour initialiser le processeur et tout configurer (cela rend l'environnement convivial). Généralement, sur les petits processeurs intégrés - votre AVR en est un exemple - il peut y avoir 10 à 20 unités de ce type, toutes petites. Il peut s'agir d'unités d'initialisation, de conversion A / N en tampons de mémoire non mis à l'échelle, de sortie au niveau du bit, d'entrée de bouton-poussoir (pas de rebond juste échantillonné), de pilotes de modulation de largeur d'impulsion, de pilotes UART / série simples utilisant des interruptions et de petits tampons d'E / S. Il pourrait y en avoir quelques autres, par exemple des pilotes I2C ou SPI pour EEPROM, EPROM ou d'autres périphériques I2C / SPI.
Pour chacune des unités d'abstraction matérielle (HAL) / pilote, j'écris ensuite un programme de test. Cela repose sur un port série (UART) et un processeur init - donc le premier programme de test utilise uniquement ces 2 unités et ne fait que quelques entrées et sorties de base. Cela me permet de tester que je peux démarrer le processeur et que j'ai un support de débogage de base fonctionnant avec les E / S série. Une fois que cela fonctionne (et alors seulement), je développe les autres programmes de test HAL, en les construisant au-dessus des bonnes unités UART et INIT connues. Donc, je pourrais avoir des programmes de test pour lire les entrées au niveau du bit et les afficher sous une forme agréable (hexadécimale, décimale, peu importe) sur mon terminal de débogage série. Je peux ensuite passer à des choses plus grandes et plus complexes comme les programmes de test EEPROM ou EPROM - je fais la plupart de ces menus pour que je puisse sélectionner un test à exécuter, l'exécuter et voir le résultat. Je ne peux pas le SCRAPTER mais en général je ne le fais pas
Une fois que j'ai tout mon HAL en cours d'exécution, je trouve alors un moyen d'obtenir un tick de minuterie régulier. Ceci est typiquement à un taux quelque part entre 4 et 20 ms. Cela doit être régulier, généré lors d'une interruption. Le roulement / débordement des compteurs est généralement la façon dont cela peut être fait. Le gestionnaire d'interruption INCRÉMENT ensuite un "sémaphore" de taille octet. À ce stade, vous pouvez également jouer avec la gestion de l'alimentation si vous en avez besoin. L'idée du sémaphore est que si sa valeur est> 0, vous devez exécuter la "boucle principale".
L'EXÉCUTIF exécute la boucle principale. Il attend à peu près ce sémaphore pour devenir non-0 (je résume ce détail). À ce stade, vous pouvez jouer avec les compteurs pour compter ces ticks (car vous connaissez le taux de ticks) et vous pouvez donc définir des indicateurs indiquant si le tick exécutif actuel est pour un intervalle de 1 seconde, 1 minute et d'autres intervalles communs que vous pourrait vouloir utiliser. Une fois que l'exécutif sait que le sémaphore est> 0, il exécute un seul passage à travers chaque fonction "mise à jour" des processus "d'application".
Les processus de demande sont effectivement placés côte à côte et exécutés régulièrement par une coche de «mise à jour». Ce n'est qu'une fonction appelée par l'exécutif. Il s'agit en fait d'un multitâche pour les pauvres avec un RTOS local très simple qui repose sur toutes les applications entrant, effectuant un petit travail et sortant. Les applications doivent conserver leurs propres variables d'état et ne peuvent pas effectuer de calculs longs car il n'y a pas de système d'exploitation préventif pour forcer l'équité. Évidemment, le temps d'exécution des applications (cumulativement) doit être inférieur à la période de tick principale.
L'approche ci-dessus est facilement étendue afin que vous puissiez ajouter des éléments comme des piles de communication qui s'exécutent de manière asynchrone et des messages de communication peuvent ensuite être envoyés aux applications (vous ajoutez une nouvelle fonction à chacune qui est le "rx_message_handler" et vous écrivez un répartiteur de messages qui figure vers quelle demande envoyer).
Cette approche fonctionne pour à peu près tous les systèmes de communication que vous souhaitez nommer - elle peut (et a déjà fonctionné) pour de nombreux systèmes propriétaires, des systèmes de communication à normes ouvertes, elle fonctionne même pour les piles TCP / IP.
Il a également l'avantage d'être construit en pièces modulaires avec des interfaces bien définies. Vous pouvez retirer et retirer des pièces à tout moment, substituer différentes pièces. À chaque point le long du chemin, vous pouvez ajouter un faisceau de test ou des gestionnaires qui s'appuient sur les bonnes parties connues de la couche inférieure (les éléments ci-dessous). J'ai constaté qu'environ 30% à 50% d'une conception peuvent bénéficier de l'ajout de tests unitaires spécialement écrits qui sont généralement assez facilement ajoutés.
J'ai pris tout cela un peu plus loin (une idée que j'ai reprise de quelqu'un d'autre qui l'a fait) et j'ai remplacé la couche HAL par un équivalent pour PC. Ainsi, par exemple, vous pouvez utiliser C / C ++ et winforms ou similaire sur un PC et en écrivant le code ATTENTIVEMENT, vous pouvez émuler chaque interface (par exemple EEPROM = un fichier disque lu dans la mémoire du PC), puis exécuter l'intégralité de l'application intégrée sur un PC. La possibilité d'utiliser un environnement de débogage convivial peut économiser beaucoup de temps et d'efforts. Seuls de très gros projets peuvent généralement justifier cette quantité d'efforts.
La description ci-dessus n'est pas unique à la façon dont je fais les choses sur les plates-formes intégrées - j'ai rencontré de nombreuses organisations commerciales qui font de même. La façon dont cela se fait est généralement très différente dans la mise en œuvre, mais les principes sont souvent à peu près les mêmes.
J'espère que ce qui précède donne un peu de saveur ... cette approche fonctionne pour les petits systèmes embarqués qui fonctionnent dans quelques ko avec une gestion agressive de la batterie jusqu'aux monstres de 100K ou plus de lignes sources qui fonctionnent en permanence. Si vous exécutez "embarqué" sur un gros système d'exploitation comme Windows CE, etc., tout ce qui précède est complètement sans importance. Mais ce n'est pas vraiment de la programmation embarquée.