ISR e Delay()

Olá,

Gostaria de saber se é boa prática chamar-mos uma função externa dentro de uma ISR.

Passo a explicar:
Se eu declarar um interrupt que é actuado por um pushbotton, a rotina ISR é imediatamente activada, mas, como é sabido, existe o efeito de Bounce nos interruptores.
Para tentar fazer o Debounce, eu gostaria de fazer um Delay() ao botão, mas a função Delay() não funciona dentro de uma ISR, assim, estava a pensar em chamar uma função externa ao ISR onde pudesse usar a função Delay().

Isto é viável? É boa pratica? O Arduino não frita?

Obrigado

Boas
Usa a funçao millis para te dar a diferença temporal.Usares o delay nao funciona!
algo tipo

void my_interrupt_handler()
{
 static unsigned long last_interrupt_time = 0;
 unsigned long interrupt_time = millis();
 // If interrupts come faster than 200ms, assume it's a bounce and ignore
 if (interrupt_time - last_interrupt_time > 200)
 {
   ... do your thing
 }
 last_interrupt_time = interrupt_time;
}

Boa tarde Hugo,

Se bem percebi o código que enviaste, não sei se funcionaria. Isto, partindo do princípio que a função my_interrupt_handler() é a minha ISR.

Senão, vejamos:

void my_interrupt_handler() // o utilizador clicou no botão e entrou neste ISR
{
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis(); // vamos supor que milis() = 600000
// If interrupts come faster than 200ms, assume it's a bounce and ignore
if (interrupt_time - last_interrupt_time > 200) // esta condição é sempre verdade
{
... do your thing
}
last_interrupt_time = interrupt_time; // guarda o interrupt_time em last_interrupt_time, mas quando voltar ao ISR o last_interrupt_time inicializa a Zero
}

Ou então não percebi a "coisa" que estavas a tentar explicar.

À primeira vista o que tenho para dizer é: "Um botão precisa de uma interrupção para ser detectado?", "A sério?!" :slight_smile:
Não estou a ver a aplicação, mas eu diria que não deve ser necessário. Isso cheira-me a tentar compensar um erro com outro erro.

Em relação à questão, se for mesmo obrigatório o debounce (e o botão) e não se puder chamar o delay() na ISR (que eu acho que não, no meu entender uma rotina de interrupção deve ser executada o mais rapidamente possível para não "interromper" o normal funcionamento do programa) também há a possibilidade de implementar o debounce por hardware.

Olá Luís.

Não percebi o porquê da afirmação: "Um botão precisa de uma interrupção para ser detectado?"
Não foi isso que eu disse.

A minha questão não se trata de erros. Trata-se tão somente de curiosidade sobre o que tenho lido sobre ISR's.

Sim, eu sei que: o ISR é "uma rotina de interrupção deve ser executada o mais rapidamente possível para não "interromper" o normal funcionamento do programa)"

Também já sei que: "há a possibilidade de implementar o debounce por hardware."

A minha questão inicial é: se é boa prática chamar-mos uma função externa dentro de uma ISR e se isto é viável? É boa pratica? O Arduino não frita?

Dado que, não encontrei nada sobre estas questões, é por isso que pus a questão neste forum.

Dei o exemplo "curioso" do Delay() dentro do ISR como poderia ter dado outro qualquer.

Digo "curioso" porque, dado que o Delay() não funciona dentro de uma ISR, pensei:

E que tal chamar uma função com Delay() de dentro de uma ISR, sendo essa função externa à ISR.

A questão é meramente teórica.

Obrigado.

Se a questão é apenas teórica, a reposta é afirmativa. No entanto, sendo possível, se a função for muito complexa (e portanto a sua execução demorar muito tempo) não é aconselhável (pelo que disse no post anterior).

Luís,

A resposta é afirmativa em que sentido?

É boa prática chamar-mos uma função externa dentro de uma ISR?
É viável em C++?
O Arduino não frita?

Porque é que tem de ser curta? Imagina um programa que tem duas partes de processamento: ou corre a Parte A dentro do loop() ou corre a Parte B dentro da ISR e o utilizador usa um botão para correr a Parte A e usa o mesmo botão para correr a Parte B? (sendo que esse botão faz a comutação entre as duas partes).

Teoricamente o loop() corre eternamente mas eu também posso ter uma função qualquer a correr eternamente fora do loop(). Verdade?

Luís, só estou em "learning mode", daí a minha insistência. Sabes aqueles alunos, chatos como o "caraças" que põem tudo em causa?

Volto a frisar, que introduzi esta dúvida "existêncial" no forum porque o google não dá respostas cabais (eu, pelo menos, não consegui encontrar).

Mais uma vez, obrigado.

Não é boa prática e é possível de o fazer.

O problema aqui é a assumpção de várias coisas.

Entre elas de que a interrupção interrompe um programa que está a correr com várias tarefas de controlo e/ou interface ao utilizador.

Nesse caso a interrupção longa é má politica porque o tempo que o programa demora a ser executado não é constante (problemático em controladores pid, por exemplo), o programa pode bloquear e dar a impressão que o sistema não está a funcionar.

Se por outro lado o programa apenas corre em interrupções com uma função loop vazia, não há problemas em meter código na interrupção. Um dos exemplos do arduino faz isso aliás.

Paraler teclas, o comentário do luis está correcto e uma interrupção não éa melhor maneira de executar.
Regra geral ler as teclas de 100 em 100 ms, ou menos pode evitar problemas de debounce com pouco incomodo para o utilizador.

No final de contas, o ser má prática é sempre uma generalização. Da mesma maneira que muita gente diz que não se deve usar o goto, mas ninguém explica porquê e quais as vantagens de o usar correctamente.

Logo, em termos gerais, não deves usar interrupções longas, mas cada caso é um caso e convém ver o impacto e decidir com base nessa informação. Mesmo assim, haverá sempre quem diga que se pode fazer o mesmo sem interrupções… E dependendo da aplicação, podes mesmo ter de programar sem uso de interrupções para a aplicação seguir normas de segurança, etc…

Boa noite Bubulindo,

Antes de mais,obrigado pela resposta.

Vou citar-te e vou pôr as minhas questões ("existenciais") depois das tuas afirmações e conselhos.
Após o teu texto e depois de // , os comentários/questões são meus.

bubulindo:
Não é boa prática e é possível de o fazer. // sendo possível fazê-lo porque é que não é boa pratica?

O problema aqui é a assumpção de várias coisas. // concordo.

Entre elas de que a interrupção interrompe um programa que está a correr com várias tarefas de controlo e/ou interface ao utilizador. // E então? podemos sempre, dentro do ISR, dar "feedback" do que está a acontecer.

Nesse caso a interrupção longa é má politica porque o tempo que o programa demora a ser executado não é constante (problemático em controladores pid, por exemplo), o programa pode bloquear e dar a impressão que o sistema não está a funcionar. // Repito: podemos sempre, dentro do ISR, dar "feedback" do que está a acontecer.

Se por outro lado o programa apenas corre em interrupções com uma função loop vazia, não há problemas em meter código na interrupção. Um dos exemplos do arduino faz isso aliás. // Por favor diz-me qual é esse exemplo.

Paraler teclas, o comentário do luis está correcto e uma interrupção não éa melhor maneira de executar.
Regra geral ler as teclas de 100 em 100 ms, ou menos pode evitar problemas de debounce com pouco incomodo para o utilizador. // aí, já entramos por áreas que desconheço.

No final de contas, o ser má prática é sempre uma generalização. Da mesma maneira que muita gente diz que não se deve usar o goto, mas ninguém explica porquê e quais as vantagens de o usar correctamente. // Aqui, eu posso dizer-te que o GOTO é uma péssima instrução em programação porque não há forma de ter um código estruturado e encapsulado e, por isso, de difícil leitura ( em tempos chamado: "Spagetti Code").

Logo, em termos gerais, não deves usar interrupções longas, mas cada caso é um caso e convém ver o impacto e decidir com base nessa informação. Mesmo assim, haverá sempre quem diga que se pode fazer o mesmo sem interrupções... E dependendo da aplicação, podes mesmo ter de programar sem uso de interrupções para a aplicação seguir normas de segurança, etc...// Concordo com tudo isto mas vou tentar dar um exemplo:

EXEMPLO: Imaginem que eu tenho um Arduino que controla a rega de dois jardins, o jardim A e o jardim B.
O jardim A tem relva e o jardim B tem flores várias.
Fisicamente só posso regar um dos jardim à vez.
No meu Arduino tenho um pushbotton que me permite comutar entre regar o jardim A que é o que está a regar por defeito (Void loop()).
MAS, quando pressiono o botão, existe um Interrupt que vai executar um ISR para regar o jardim B até que uma determinada condição se verifique (que pode demorar uma semana e aí, volta ao loop() para regar o jardim A).

Já sei que poderia fazer as coisas de outra maneira. Só vos peço que imaginem que esta era a única forma de o fazerem: Um Arduino, um botão que comuta para o ISR e duas rotinas que podem estar a correr durante semanas, à vez: rega jardim A ou rega jardim B.

Bubulindo, não quero abusar da tua paciência, só espero com isto, esclarecer certas coisas que se tomam como referência, quase como regras fixas e ninguém questiona.

Obrigado.

Encontrei na net um exemplo interessante que vou usar no exemplo que dei anteriormente:
Regar Jardim_A (que rega por defeito em void loop()) ou regar Jardim_B após accionar o interrupt, tratando o Bounce de um suposto botão/pushbotton:
Atenção que este código como está NÃO FUNCIONA. Isto é meramente ilustrativo daí não o editar da maneira correcta:

long debouncing_time = 15; //debouncing time em Millisegundos que se julgar conveniente
volatile unsigned long last_micros;

void setup()
{
attachInterrupt(0, debounceInterrupt, FALLING); // definição do interrupt como nos der mais jeito: podia ser RISING, depende.
}

void loop()
{
Valvula_A = true; // abre a torneira do Jardim_A e está a regar o Jardim_A
}

void debounceInterrupt() // quando o utilizador primir um pushbotton salta para a ISR.
{
if( (long) (micros() - last_micros >= debouncing_time * 1000)
{
last_micros = micros();
Interrupt(); //dentro da ISR chamo outra função externa

}
}

void interrupt()
{
// faz qualquer coisa, com a garantia do botão/switch ter sido primido e já sem erros de Bounce

Valvula_A = false; //fecha a torneira do Jardim_A
Valvula_B = true; // abre a torneira do Jardim_B
Rega_Jardim_B;
}

void Rega_Jardim_B() // e daqui não sai enquanto o sensor de humidade não tiver o valor definido
{
while(analogRead(Sensor_de_humidade) < 900)
{

}

Valvula_B = false; // fecha a torneira do Jardim_B

}

Volto então à pergunta inicial se esta forma é boa prática? (chamar-mos uma função externa dentro de uma ISR)
Sendo isto viável o Arduino não frita?

Já sei, que desta forma vocês irão dizer: mas a ISR é super curta e quase não interrompe o normal funcionamento do programa. MAS e se, em vez de chamar a rotina interrupt(), eu pusesse o código todo dentro da função debounceInterrupt() que é a minha ISR?

PS. Com esta solução não se usa o Delay() com ISR (título do tópico)

Eu estou no iPad sem acesso a um computador… Não sei qual é esse exemplo.

Eu dei motivos para ser má prática… Porque tornam o programa variável e além disso podem causar comportamentos estranhos que demoram a entender. É bastante imprático explicar isto escrevendo no iPad, mas até em controladores industriais encontrei erros que aconteciam devido a interrupções serem usadas.

Define o que entendes por feedback. Se referes feedback para indicar ao programa que aconteceu uma interrupção, então não tem mal. É apenas mudar o valor duma variável, que foi o que o luis te indicou.

Eu e programadores bem melhores que eu discordam de ti no uso do goto. Programar apenas com goto, pode causar sarilhos. Um goto numa situação especifíca pode tornar o código mais limpo e eficiente. Pesquisa por uma discussão em torno do goto pelo linus torvalds… Ou exemplos de optimização de código para teres uma ideia.

No final, o uso de goto é uma funcionalidade da linguagem… Se a souberes usar e souberes os riscos, não há problema em fazê-lo.

Quanto ao exemplo, o tempo não importa… Na realidade apenas mudas o estado duma variável com base num botão. É muito mais simples de fazer isso sem interrupções do que com interrupções. Nota que usar interrupções não é a resposta para o debounce dos botões.

Também ainda não percebi o que se deve entender por "o Arduino fritar".
Em reação ao resto não tenho nada a acrescentar, posso apenas resumir: Podem ser usadas as interrupções, mas não convém usá-las para esse fim. Repare que o a interrupção interrompe (imediatamente) o funcionamento do programa principal. Pegando no exemplo da rega, o que quero dizer, é que é impossível de prever em que ponto é que o programa principal é interrompido, sendo assim, o que pode acontecer é que acabe a regar os dois jardins ao mesmo tempo, mas sem pressão em nenhum dos dois.

Caros Bubulindo e Luís Silva,

Obrigado pelo vosso tempo e não vou adiantar nada mais no tópico.

Vocês tem razão de um ponto de vista prático, contrariamente à minha "onda" que é puramente teórico -académica.

O grau de abstração em que eu quis pôr o tema, não foi por mim conseguido.

No final "o Arduino frita":

Bubulindo disse: "Porque tornam o programa variável e além disso podem causar comportamentos estranhos que demoram a entender. É bastante imprático explicar isto escrevendo no iPad, mas até em controladores industriais encontrei erros que aconteciam devido a interrupções serem usadas."

@Luís: era a isto que eu me referia quando escrevi a pergunta "se o Arduino frita?". Afinal "frita"

Concluí que: não é boa prática, MAS pode fazer-se. @Bubulindo: é quase como o uso do GOTO...

Obrigado aos dois pelo esforço.

Bem hajam e Feliz Ano 2016.

Bom, você deve-se lembrar de duas coisas:
1: As funções "delay" e "micros" (e análogos) no arduíno se utilizam de timers e suas rotinas trabalham com interrupção. Logo deve-se observar se não há conflitos nos timers utilizados.
2: Não há como utilizar uma interrupção dentro de outra interrupção (pelo menos de mesma prioridade), logo mesmo utilizando timers diferentes o código pode não funcionar, pois podem ter a mesma prioridade ou a sua ISR ter maior prioridade.

Se fizer sua própria função "delay_t" utilizando um timer como contador, entretanto sem utilizar interrupção (comparando o valor do registrador via software), as duas condições acima serão atendidas e não vejo porque não funcionar.

Nota: Interrupção é bom para algo crítico ao sistema (projeto) dependente de tempo ou de ação; contagem precisa de tempo ou botão de alerta. É bom aprender a manusear para se ter o conhecimento mas não deve-se fugir das soluções mais simples para fazer um projeto. (já fiz e faço muito este erro)