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 dormir, 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:
process()
.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?
Algunos microcontroladores, como 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 PIC18s, no tienen este bit, y el problema sigue ocurriendo. Sin embargo, estos micros están diseñados de tal manera que las interrupciones aún pueden activar el núcleo, independientemente de si el bit de activació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 irse a dormir. Si se activa una interrupción justo antes de ejecutar la instrucción de suspensión, el controlador de interrupciones no se ejecutará, el núcleo se reactivará y, una vez que se vuelvan a habilitar las interrupciones globales, se ejecutará el controlador de interrupciones. 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
}
}
Suele haber algún tipo de soporte de hardware para este caso. Por ejemplo, la instrucción de los AVR sei
para habilitar las interrupciones difiere la habilitación hasta que se complete la siguiente instrucción. Con ella se puede hacer:
forever,
interrupts off;
if has_flag,
interrupts on;
process interrupt;
else,
interrupts-on-and-sleep; # won't be interrupted
end
end
En este caso, la interrupción que se habría perdido en el ejemplo se retrasaría hasta que el procesador complete su secuencia de suspensión.
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 las solicitudes de interrupción. Muchos microcontroladores saldrán del modo de suspensión si una solicitud de interrupción llega al núcleo, ya sea que el núcleo esté dispuesto a aceptarla o no.
En un diseño de este tipo, un enfoque simple para lograr un comportamiento de suspensión confiable es hacer que la verificación del bucle principal borre una bandera y luego verifique si conoce alguna razón por la cual el procesador debería estar activo. Cualquier interrupción que ocurra durante ese tiempo que pueda afectar cualquiera de esas razones debe establecer la bandera. Si el bucle principal no encontró ninguna causa para permanecer despierto, y si la bandera no está configurada, el bucle principal debería deshabilitar las interrupciones y verificar la bandera nuevamente [quizás después de un par de instrucciones NOP si es posible que una interrupción quede pendiente durante una instrucción de inhabilitación-interrupción podría procesarse después de que ya se haya realizado la obtención del operando asociado con la siguiente instrucción]. Si la bandera aún no está configurada, vete a dormir.
En este escenario, una interrupción que ocurra antes de que el bucle principal deshabilite las interrupciones establecerá la bandera antes de la prueba final. Una interrupción que pasa a estar pendiente demasiado tarde para ser atendida antes de la instrucción de suspensión evitará que el procesador entre en suspensión. Ambas situaciones están bien.
Sleep-on-Exit a veces es un buen modelo para usar, pero no todas las aplicaciones realmente "encajan" en él. Por ejemplo, un dispositivo con una pantalla LCD de bajo consumo podría programarse más fácilmente con un código que se parece a:
void select_view_user(int default_user)
{
int current_user;
int ch;
current_user = default_user;
do
{
lcd_printf(0, "User %d");
lcd_printf(1, ...whatever... );
get_key();
if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
if (key_hit(KEY_ENTER)) view_user(current_user);
} while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}
Si no se presiona ningún botón y no sucede nada más, no hay razón para que el sistema no deba ir a 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 la máquina de estado, el código como el anterior suele ser la forma más lógica de manejar los flujos de interfaz de usuario altamente modal típicos de los microcontroladores pequeños.
Programe el micro para que se despierte en caso de interrupción.
Los detalles específicos variarán según el micro que esté utilizando.
Luego modifique la rutina main():
int main()
{
while(1)
{
sleep();
process(); //process the interrupt
}
}
AndrejaKo
Jugador Grady
kenny
angelagrande
interrupt_flag
como unint
, y lo incrementaría cada vez que haya una interrupción. Luego cambie elif(has_flag)
awhile (interrupts_count)
y luego duerma. No obstante, la interrupción podría ocurrir después de haber salido del ciclo while. Si esto es un problema, ¿realizar el procesamiento en la interrupción misma?Kvegaoro
TRISAbits
TRISAbits
TRISAbits
TRISAbits
Super gato