Polling vs Interrupts
Muitas vezes o nosso programa tem que reagir à mudança dum sinal externo, por exemplo a mudança do nível lógico dum sinal gerado por um botão. Na página sobre Portas I/O este problema foi resolvido fazendo-se sucessivas leituras do pino de entrada até que a mudança do valor lógico fosse detetada. Esta abordagem, designada por polling tem contudo o senão de nos obrigar a sondar ciclicamente o pino com a agravante do instante em que ocorre a mudança ser determinado de forma pouco precisa pois depende do período dessa sondagem.
Uma abordagem alternativa vai ser descrita nesta página e recorre a interrupções. Neste caso começa-se por definir uma rotina (normalmente designada de ISR – Interrupt Service Rotine) que irá ser chamada assim que ocorrer a dita mudança de valor lógico no pino igualmente a definir.
Nem todos os pinos permitem esta abordagem. No caso do PIC32 existem 5 pinos com esta funcionalidade, sendo designados de INT0, INT1, INT2, INT3 e INT4 (respetivamente nos pinos 38, 2, 7, 8 e 35 da placa ChipKIT UNO 32:
Projeto intcount
Vamos agora apresentar o projeto intcount que incrementa um contador sempre que se prime um botão ligado ao pino 38 (onde se encontra a interrupção externa INT0).
Antes porém, vamos confirmar as localizações mencinadas no final da secção anterior recorrendo à função digitalPinToInterrupt(), em que passando o número do pino da placa, recebemos o número x do pino INTx respetivo:
#include "Arduino.h" void setup() { Serial.begin(115200); int n, x; for(n=0; n<=41; n++) { x = digitalPinToInterrupt(n); if (x >= 0) Serial.printf("Pino %d = INT%d\n", n, x); } } void loop() { delay(1000); }
Agora sim, vamos incrementar o contador (variável cnt) sempre que se prime o botão, que desce o sinal no pino 38 (INT0) ao nível zero quando se prime o botão. Para que a ISR (a rotina count neste caso) seja chamada nessas condições bastará usar a função attachInterrupt(). O último parâmetro desta função pode tomar os valores FALLING, RISING ou CHANGE consoante se pretenda uma interrupção na descida, subida ou ambas, do sinal em causa.
#include "Arduino.h" // Pin 38: INT0 #define INTPIN 38 void count(); int cnt = 0; bool bPrint = true; void setup() { Serial.begin(115200); Serial.println("Project intcount"); pinMode(INTPIN, INPUT); attachInterrupt(digitalPinToInterrupt(INTPIN), count, FALLING); } void loop() { if (bPrint) { bPrint = false; Serial.println(cnt); } } // INT0 ISR (make it simple!) void count() { cnt++; bPrint = true; }
De notar que a ISR deverá ser o mais rápida possível (de modo a interromper o programa principal o menor tempo possível). De outro modo corremos o risco de ela ser interrompida por ela própria. Daí que neste exemplo a impressão do contador decorra não na ISR mas fora dela!
Caso se pretenda mais tarde no programa usar o pino 38 sem as interrupções bastará usar a função detachInterrupt().
Interrupções com Timers
Uma funcionalidade útil, seria ter a possibilidade de associarmos as interrupções a temporizadores (Timers). A ideia seria que no final duma dada temporização fosse despoletada uma interrupção. De notar que ao contrário das temporizações realizadas com base nas funções millis() ou micros(), o uso de Timers permite que as temporizações sejam feitas em Hardware e portanto em paralelo (i.e. de forma independente) à execução do programa.
Como o microcontrolador PIC32 é bastante diferente neste assunto em relação aos usados nas placas Arduino, justifica-se rever a página sobre o uso dos Timers no PIC32.
Na página atrás mencionada, a funcionalidade é explorada recorrendo à escrita/leitura dos SFRs (Special Function Registers) e respetivos bits. Existe contudo uma biblioteca (Timer) que permite “esconder” estes elementos por traz dum objeto.
O seguinte código mantém a funcionalidade do programa anterior fazendo também piscar o LED4 (pino 13) da placa ChipKIT UNO32 a cada segundo. Em ambas as funcionalidades (“contador de toques no botão” e “piscar do LED”) são usadas interrupções:
#include "Arduino.h" #include <Timer.h> Timer4 timer; #define LED 13 void __attribute__((interrupt)) isrTimer(); // Pin 38: INT0 #define INTPIN 38 void count(); int cnt = 0; bool bPrint = true; void setup() { Serial.begin(115200); Serial.println("Project intcount"); pinMode(INTPIN, INPUT); attachInterrupt(digitalPinToInterrupt(INTPIN), count, FALLING); pinMode(LED, OUTPUT); timer.setFrequency(10); timer.attachInterrupt(isrTimer); timer.start(); } void loop() { if (bPrint) { bPrint = false; Serial.println(cnt); } } // INT0 ISR (make it simple!) void count() { cnt++; bPrint = true; } void __attribute__((interrupt)) isrTimer() { static uint8_t state = LOW; static int c = 0; if (++c == 10) { digitalWrite(LED, state); state = !state; c = 0; } clearIntFlag(_TIMER_4_IRQ); }
Alguns comentários sobre este código:
Dado que o Timer usado (Timer4) tem apenas 16 bits e mesmo com o prescaler no seu valor máximo (256) só são permitidas temporizações até (2^16)*256/80e6 ≈ 0.21s, pelo que foi feita uma temporização de 0,1s (10Hz). Este é mais um exemplo de que às vezes só conhecendo bem o microcontrolador ao nível dos seus SFRs se percebe as opções feitas no código C!
A ISR do Timer4 não é uma rotina como as outras e portanto tem que ser declarada de forma adequada: void __attribute__((interrupt)) isrTimer()
Para que as variáveis dessa ISR se mantenham entre chamadas à rotina, é necessário declará-las como estáticas: static
O uso de interrupções visto ao longo desta página tem a grande virtude de permitir a chamada de rotinas quando determinados eventos acontecem, fazendo a interrupção do programa principal mesmo quando se encontram em funções bloqueantes como o delay(). Isso pode ser comprovado substituindo no programa anterior a função loop por esta outra:
void loop() { delay(3000); Serial.println(cnt); }
Como se pode constatar, apesar do delay de 3s, toques no botão durante esse período continuam a ser detetados já que o contador é incrementado!
ToDo
No final da página sobre comunicação série, foram pedidas na secção ToDo, várias melhorias ao programa butledserial. Repita o ponto 4 mas agora recorrendo às funcionalidades descritas nesta página, ou seja use Timers e Interrupções.