Dado un microcontrolador que ejecuta el siguiente código:
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
}
}
Supongamos que la if(has_flag)
condición se evalúa como falsa y estamos a punto de ejecutar la instrucción de suspensión. Justo antes de ejecutar la instrucción de suspensión, recibimos una interrupción. Después de dejar la interrupción, ejecutamos la instrucción de suspensión.
Esta secuencia de ejecución no es deseable porque:
- El microcontrolador se fue a dormir en lugar de despertarse y llamar
process()
. - Es posible que el microcontrolador nunca se active si no se recibe ninguna interrupción.
- La llamada a
process()
se pospone hasta la próxima interrupción.
¿Cómo se puede escribir el código para evitar que ocurra esta condición de carrera?
Editar
Algunos microcontroladores, como el ATMega, tienen un bit de habilitación de suspensión que evita que ocurra esta condición (gracias Kvegaoro por señalar esto). JRoberts ofrece una implementación de ejemplo que ejemplifica este comportamiento.
Otros micros, como los PIC18, no tienen este bit y el problema aún ocurre. Sin embargo, estos micros están diseñados de tal manera que las interrupciones aún pueden despertar el núcleo independientemente de si el bit de habilitación de interrupción global está configurado (gracias supercat por señalar esto). Para tales arquitecturas, la solución es deshabilitar las interrupciones globales justo antes de ir a dormir. Si se dispara una interrupción justo antes de ejecutar la instrucción de suspensión, el controlador de interrupción no se ejecutará, el núcleo se activará y, una vez que se vuelvan a habilitar las interrupciones globales, se ejecutará el controlador de interrupción. En pseudocódigo, la implementación se vería así:
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
como unint
, y lo incrementaría cada vez que haya una interrupción. Luego cambieif(has_flag)
awhile (interrupts_count)
y luego duerma. No obstante, la interrupción podría ocurrir después de que haya salido del ciclo while. Si esto es un problema, ¿el procesamiento en la interrupción misma?Respuestas:
Por lo general, hay algún tipo de soporte de hardware para este caso. Por ejemplo, la
sei
instrucción de los AVR para habilitar las interrupciones difiere hasta que se complete la siguiente instrucción. Con ella se puede hacer:La interrupción que se hubiera perdido en el ejemplo se retrasaría en este caso hasta que el procesador complete su secuencia de suspensión.
fuente
En muchos microcontroladores, además de poder habilitar o deshabilitar causas de interrupción particulares (generalmente dentro de un módulo de controlador de interrupción), hay un indicador maestro dentro del núcleo de la CPU que determina si se aceptarán solicitudes de interrupción. Muchos microcontroladores saldrán del modo de suspensión si una solicitud de interrupción llega al núcleo, tanto si el núcleo está dispuesto a aceptarlo como si no.
En tal diseño, un enfoque simple para lograr un comportamiento de sueño confiable es hacer que la verificación del bucle principal borre una bandera y luego verifique si conoce alguna razón por la que el procesador debe estar despierto. Cualquier interrupción que ocurra durante ese tiempo que pueda afectar a cualquiera de esos motivos debe establecer la bandera. Si el bucle principal no encontró ninguna causa para permanecer despierto, y si el indicador no está configurado, el bucle principal debe deshabilitar las interrupciones y verificar el indicador nuevamente [tal vez después de un par de instrucciones NOP si es posible que haya una interrupción pendiente durante una instrucción deshabilitar-interrumpir podría procesarse después de que la búsqueda de operando asociada con la siguiente instrucción ya se haya realizado]. Si la bandera aún no está configurada, entonces ve a dormir.
Bajo este escenario, una interrupción que ocurre antes de que el bucle principal desactive las interrupciones establecerá la bandera antes de la prueba final. Una interrupción que queda pendiente demasiado tarde para recibir servicio antes de la instrucción de suspensión evitará que el procesador se suspenda. Ambas situaciones están bien.
Dormir al salir es a veces un buen modelo para usar, pero no todas las aplicaciones realmente lo "encajan". Por ejemplo, un dispositivo con una pantalla LCD de bajo consumo de energía podría programarse más fácilmente con un código similar al siguiente:
Si no se presionan botones y no sucede nada más, no hay ninguna razón por la cual el sistema no deba dormir durante la ejecución del
get_key
método. Si bien es posible que las teclas activen una interrupción y administren toda la interacción de la interfaz de usuario a través de una máquina de estado, un código como el anterior es a menudo la forma más lógica de manejar flujos de interfaz de usuario altamente modal típicos de microcontroladores pequeños.fuente
Programe el micro para despertarse en la interrupción.
Los detalles específicos variarán según el micro que esté utilizando.
Luego modifique la rutina main ():
fuente