Dado um microcontrolador que está executando o seguinte 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
}
}
Suponha que a if(has_flag)
condição seja avaliada como falsa e estamos prestes a executar a instrução de suspensão. Logo antes de executarmos a instrução sleep, recebemos uma interrupção. Depois de deixarmos a interrupção, executamos a instrução sleep.
Essa sequência de execução não é desejável porque:
- O microcontrolador foi dormir em vez de acordar e ligar
process()
. - O microcontrolador nunca poderá ser ativado se nenhuma interrupção for recebida posteriormente.
- A chamada para
process()
é adiada até a próxima interrupção.
Como o código pode ser escrito para impedir que essa condição de corrida ocorra?
Editar
Alguns microcontroladores, como o ATMega, possuem um bit de habilitação para suspensão que impede que essa condição ocorra (obrigado Kvegaoro por apontar isso). O JRoberts oferece um exemplo de implementação que exemplifica esse comportamento.
Outros micros, como os PIC18s, não possuem esse bit e o problema ainda ocorre. No entanto, esses micros são projetados de modo que as interrupções ainda possam ativar o núcleo, independentemente de o bit de ativação de interrupção global estar definido (obrigado supercat por apontar isso). Para essas arquiteturas, a solução é desativar as interrupções globais antes de dormir. Se uma interrupção for acionada imediatamente antes de executar a instrução de suspensão, o manipulador de interrupções não será executado, o núcleo será ativado e, quando as interrupções globais forem reativadas, o manipulador de interrupções será executado. No pseudo-código, a implementação seria assim:
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 umint
e incrementá-lo sempre que houver interrupção. Então mudeif(has_flag)
parawhile (interrupts_count)
e depois durma. No entanto, a interrupção pode ocorrer depois que você sai do loop while. Se isso for um problema, faça o processamento na própria interrupção?Respostas:
Geralmente, há algum tipo de suporte de hardware para este caso. Por exemplo, a
sei
instrução dos AVRs para ativar as interrupções adia a ativação até que a instrução a seguir seja concluída. Com isso, pode-se fazer:A interrupção que seria perdida no exemplo, nesse caso, seria interrompida até que o processador conclua sua sequência de suspensão.
fonte
Em muitos microcontroladores, além de poder ativar ou desativar causas de interrupção específicas (geralmente dentro de um módulo controlador de interrupção), existe um sinalizador mestre no núcleo da CPU que determina se as solicitações de interrupção serão aceitas. Muitos microcontroladores sairão do modo de suspensão se uma solicitação de interrupção atingir o núcleo, esteja ele disposto ou não a aceitá-lo.
Nesse projeto, uma abordagem simples para obter um comportamento confiável do sono é fazer com que o loop principal limpe um sinalizador e, em seguida, verifique se sabe algum motivo para o processador estar acordado. Qualquer interrupção que ocorra durante esse período que possa afetar qualquer um desses motivos deve definir o sinalizador. Se o loop principal não encontrou nenhuma causa para permanecer acordado e se o sinalizador não estiver definido, o loop principal deve desativar as interrupções e verificar o sinalizador novamente [talvez depois de algumas instruções NOP, se é possível que uma interrupção fique pendente durante uma instrução de interrupção de desativação pode ser processada após a busca do operando associada à instrução a seguir já ter sido executada]. Se o sinalizador ainda não estiver definido, vá dormir.
Nesse cenário, uma interrupção que ocorre antes que o loop principal desative as interrupções definirá o sinalizador antes do teste final. Uma interrupção que fica pendente demais para ser atendida antes da instrução de suspensão impedirá que o processador entre no modo de suspensão. Ambas as situações estão bem.
Às vezes, dormir na saída é um bom modelo para usar, mas nem todos os aplicativos realmente o "encaixam". Por exemplo, um dispositivo com um LCD que economiza energia pode ser programado com mais facilidade com código parecido com:
Se nenhum botão for pressionado e nada mais estiver acontecendo, não há razão para o sistema não dormir durante a execução do
get_key
método. Embora seja possível que as chaves acionem uma interrupção e gerenciem toda a interação da interface do usuário por meio de uma máquina de estado, código como o acima é geralmente a maneira mais lógica de lidar com fluxos de interface do usuário altamente modais, típicos de pequenos microcontroladores.fonte
Programe o micro para acordar em interrupção.
Os detalhes específicos variam de acordo com o micro que você está usando.
Em seguida, modifique a rotina main ():
fonte