Étant donné un microcontrôleur qui exécute le code suivant:
volatile bool has_flag = false;
void interrupt(void) //called when an interrupt is received
{
clear_interrupt_flag(); //clear interrupt flag
has_flag = true; //signal that we have an interrupt to process
}
int main()
{
while(1)
{
if(has_flag) //if we had an interrupt
{
has_flag = false; //clear the interrupt flag
process(); //process the interrupt
}
else
sleep(); //place the micro to sleep
}
}
Supposons que la if(has_flag)
condition soit fausse et que nous soyons sur le point d'exécuter l'instruction sleep. Juste avant d'exécuter l'instruction sleep, nous recevons une interruption. Après avoir quitté l'interruption, nous exécutons l'instruction sleep.
Cette séquence d'exécution n'est pas souhaitable car:
- Le microcontrôleur s'est endormi au lieu de se réveiller et d'appeler
process()
. - Le microcontrôleur peut ne jamais se réveiller si aucune interruption n'est reçue par la suite.
- L'appel à
process()
est reporté à la prochaine interruption.
Comment le code peut-il être écrit pour empêcher cette condition de concurrence critique de se produire?
Éditer
Certains microcontrôleurs, comme à l'ATMega, ont un bit d'activation de veille qui empêche cette condition de se produire (merci Kvegaoro de l'avoir signalé). JRoberts propose un exemple d'implémentation qui illustre ce comportement.
D'autres micros, tels que les PIC18, n'ont pas ce bit et le problème persiste. Cependant, ces micros sont conçus de telle sorte que les interruptions peuvent toujours réveiller le cœur indépendamment du fait que le bit d'activation d'interruption globale soit défini (merci supercat de l'avoir signalé). Pour de telles architectures, la solution consiste à désactiver les interruptions globales juste avant de s'endormir. Si une interruption se déclenche juste avant l'exécution de l'instruction sleep, le gestionnaire d'interruption ne sera pas exécuté, le cœur se réveillera et une fois les interruptions globales réactivées, le gestionnaire d'interruption sera exécuté. En pseudo-code, l'implémentation ressemblerait à ceci:
int main()
{
while(1)
{
//clear global interrupt enable bit.
//if the flag tested below is not set, then we enter
//sleep with the global interrupt bit cleared, which is
//the intended behavior.
disable_global_interrupts();
if(has_flag) //if we had an interrupt
{
has_flag = false; //clear the interrupt flag
enable_global_interrupts(); //set global interrupt enable bit.
process(); //process the interrupt
}
else
sleep(); //place the micro to sleep
}
}
interrupt_flag
comme unint
, et je l'incrémenterais chaque fois qu'il y a une interruption. Puis changez leif(has_flag)
enwhile (interrupts_count)
puis dormez. Néanmoins, l'interruption peut se produire une fois que vous avez quitté la boucle while. Si c'est un problème, alors le traitement dans l'interruption lui-même?Réponses:
Il existe généralement une sorte de support matériel pour ce cas. Par exemple, l'
sei
instruction des AVR d'activer les interruptions permet d'activer jusqu'à ce que l'instruction suivante soit terminée. Avec cela, on peut faire:L'interruption qui aurait été manquée dans l'exemple serait dans ce cas suspendue jusqu'à ce que le processeur termine sa séquence de mise en veille.
la source
Sur de nombreux microcontrôleurs, en plus d'être en mesure d'activer ou de désactiver des causes d'interruption particulières (généralement dans un module de contrôleur d'interruption), il existe un indicateur maître dans le cœur du processeur qui détermine si les demandes d'interruption seront acceptées. De nombreux microcontrôleurs quitteront le mode veille si une demande d'interruption atteint le cœur, que le cœur soit disposé à l'accepter ou non.
Sur une telle conception, une approche simple pour obtenir un comportement de sommeil fiable consiste à faire en sorte que la vérification de la boucle principale efface un indicateur, puis vérifie s'il connaît une raison pour laquelle le processeur devrait être éveillé. Toute interruption qui se produit pendant ce temps et qui pourrait affecter l'une de ces raisons devrait mettre le drapeau. Si la boucle principale n'a trouvé aucune raison de rester éveillée, et si l'indicateur n'est pas défini, la boucle principale devrait désactiver les interruptions et vérifier à nouveau l'indicateur [peut-être après quelques instructions NOP s'il est possible qu'une interruption qui devienne en attente pendant une instruction de désactivation-interruption peut être traitée après que la récupération d'opérande associée à l'instruction suivante a déjà été effectuée]. Si le drapeau n'est toujours pas réglé, alors allez vous coucher.
Dans ce scénario, une interruption qui se produit avant que la boucle principale ne désactive les interruptions définira l'indicateur avant le test final. Une interruption trop tardive pour être réparée avant l'instruction de mise en veille empêchera le processeur de se mettre en veille. Les deux situations sont très bien.
La mise en veille à la sortie est parfois un bon modèle à utiliser, mais toutes les applications ne lui conviennent pas vraiment. Par exemple, un appareil doté d'un écran LCD économe en énergie peut être plus facilement programmé avec un code qui ressemble à:
Si aucun bouton n'est enfoncé et que rien d'autre ne se produit, il n'y a aucune raison pour que le système ne s'endorme pas pendant l'exécution de la
get_key
méthode. Bien qu'il soit possible que des clés déclenchent une interruption et gèrent toutes les interactions de l'interface utilisateur via la machine d'état, un code comme celui-ci est souvent le moyen le plus logique de gérer les flux d'interface utilisateur hautement modaux typiques des petits microcontrôleurs.la source
Programmez le micro pour qu'il se réveille en cas d'interruption.
Les détails spécifiques varieront en fonction du micro que vous utilisez.
Modifiez ensuite la routine main ():
la source