Vous disposez d'un processeur à un seul cœur et à un seul thread. Cela signifie qu'à tout moment, il fait exactement une chose. Si votre application l'exige pour faire plusieurs choses, le code doit être conçu pour basculer entre toutes. Cela peut être aussi simple ou complexe que vous le souhaitez.
Normalement, le CPU tourne autour de la boucle principale, en faisant tout ce qu'il y a dedans, et c'est très bien jusqu'à ce qu'une interruption se produise. Le matériel d'interruption force essentiellement un appel de fonction à l'ISR approprié, même s'il n'y a aucune instruction d'appel pour celui-ci dans la boucle principale. Cette imprévisibilité est la source de la plupart des règles d'écriture des ISR.
Quel que soit le temps que vous passez dans un ISR, c'est le temps que la boucle principale est mise en pause, en attendant le retour de l'ISR. Si la boucle principale est la seule à réinitialiser un minuteur de chien de garde actif (très bonne pratique), alors le chien de garde ne sera pas réinitialisé pendant ce temps. Si le chien de garde expire, il vous donne une réinitialisation matérielle. Tout comme la réinitialisation externe, mais avec différents indicateurs que vous pouvez vérifier au démarrage. C'est probablement le "crash" dont vous avez entendu parler.
C'est une très bonne pratique d'utiliser le chien de garde et de ne le réinitialiser qu'une fois à chaque trajet autour de la boucle principale. Cela vous oblige à écrire du code qui reste réactif. Si vous avez besoin d'attendre quelque chose, vous pouvez configurer un événement (minuterie terminée, prochain caractère reçu, etc.) et passer à autre chose. Vérifiez périodiquement cet événement ou définissez une autre interruption pour son achèvement, puis revenez-y. En attendant, vous continuez tout ce que vous faisiez.
Ma structure principale est typiquement quelque chose comme ceci:
#include "module1.h"
#include "module2.h"
void main (void)
{
//global
//puce
//installer
mod1_init ();
mod2_init ();
// efface les drapeaux d'interruption
// activation d'interruption globale
tandis que (1)
{
// effacer le chien de garde
mod1_run ();
mod2_run ();
}
}
Et mes modules sont comme ceci:
void modX_init (void)
{
// hardware et variable init pour ce module uniquement
// n'utilise pas d'interruptions si l'interrogation est assez bonne
}
void modX_run (void)
{
si (POLLED_INTERRUPT_FLAG)
{
POLLED_INTERRUPT_FLAG = 0;
// code "ISR" non bloquant
}
}
void ISR modX_ISR (void)
{
// d'accord, cela nécessite une réponse * immédiate *
// passe le temps minimum absolu ici et sors
}
Les signatures de fonction n'ont pas besoin d'être void
, mais la plupart le sont. Parfois, j'aurai un timing large dans un module qui est également utilisé par un autre, et il est pratique d'utiliser la valeur de retour d'un modX_run ()
et les arguments d'un autre (ou d'une logique de base) pour faire ce lien. Par exemple:
if (DMX_run ()) // inclut son propre timing, et retourne true au début de chaque intervalle de 30Hz, sinon false
{
I2C_start (); // Les trames I2C sont synchronisées avec DMX
}
I2C_run (); // une fois démarré, une trame I2C tourne librement jusqu'à la fin
Si vous étudiez la fiche technique, vous constaterez peut-être également que les périphériques matériels peuvent être massés pour faire ce que vous voulez sans aucune intervention du processeur.
La génération d'impulsions de sortie, par exemple, est courante. Allumez-le, réglez le périphérique pour qu'il s'éteigne plus tard et oubliez-le. C'est généralement dans le même domaine général que PWM.