Interrupt nei sistemi embedded

Interrupt Service RoutineIl programma di un controllore embedded si svolge all'interno di un loop infinito: inizia all'accensione del sistema e termina con il suo spegnimento. L'esecuzione non ha mai termine, ma  resta in attesa di segnali in ingresso (sensori) e predispone le uscite (attuatori).

Molti eventi avvengono in modo asincrono, non previsto o prevedibile da sistema. Si tratta ad, esempio, della pressione di un tasto o un di segnale da un sensore che arriva in modo non prevedibile.

Ci sono alcune tecniche per intercettare e gestire questi eventi. Una soluzione è detta di polling, un controllo periodico e ripetuto dello stato dell'ingresso per determinarne il valore. Ha un inconveniente: se l'evento è veloce e il ciclo di polling non è sufficientemente frequente, si può perdere l'evento e occorre ricorrere ad hardware più complesso con buffer o simili per far fronte  a simili situazioni.

Una soluzione migliore è il ricorso all'interrupt. Consiste nell'abbinare una funzione, detta di interrupt, all'ingresso da monitorare. All'arrivo dell'evento il processore agisce autonomamente, interrompendo il normale flusso del programma ed eseguendo la routine predisposta alla gestione dell'interrupt.

In questo modo l'ingresso è gestito direttamente dal processore e non dal programma principale e, salvo casi particolari, il segnale non può essere ignorato.

I vari processori prevedono diverse modalità di interrupt, che può scatenarsi a fronte di un cambiamento di un ingresso (hardware), ma anche da cambiamenti di stati interni (software). I vari interrupt possono agire su diversi ingressi e avere priorità: se due eventi avvengono contemporaneamente uno di questi viene gestito con priorità rispetto un altro. Occorre definire un metodo per discriminare i vari interrupt. Si ricorre  alll'uso di un interrupt vector (vettore di interrupt), che consiste in un indirizzo o comunque un numero che riconduce alla corretta routine del corrispondente evento.

La routine di interrupt deve essere breve e difficilmente gestisce l'evento nella sua completezza, ma ne  memorizza lo stato in una variabile o buffer. La variabile o il buffer sono, poi,  a disposizione del programma principale che agirà come necessario.  L'esempio è la ricezione di dati dalla linea seriale, l'arrivo del dato può generare un interrupt, la cui routine si limita a leggere il dato ed a posizionarlo in una area di memoria (buffer) a disposizione del programma principale.

La funzione di interrupt può essere disabilitata o abilitata. Si pensi ad esempio alla parte iniziale di un programma che predispone il sistema a determinate operazioni, come la definizione  di ingressi e uscite. In questa fase non è desiderabile avere interruzioni, perché il sistema non è ancora pronto per funzionare correttamente e potrebbe presentare comportamenti anomali.

 

Tags: 

Categoria: 

Interrupt con Arduino UNO

Arduino Uno può gestire due interrupt (due vettori di interrupt) su due piedini il pin 2 e il pin 3. Altri modelli di Arduino hanno più ampie possibilità.
All'accensione del processore l'interrupt è abilitato, all'occorrenza si può disabilitare.  Di seguito vengono riportate alcune funzioni per gestire l'interrupt ed esempi:

  • attachInterrupt(intvec, funzione, evento). Definisce la funzione che gestirà l'interrupt, a quale piedino è abbinata (interrupt vector) e a quale evento deve reagire. Gli eventi contemplati sono 4. RAISING il segnale, al pin di ingresso, passa da 0 a 1. FALLING il segnale passa da 1 a 0. CHANGE il segnale cambia o da 0 a 1 o da 1 a 0. LOW il segnale è 0.
    Quando l'evento si presenta, sul piedino definito con intvec, il controllo del programma passa a funzione.
    Arduino UNO gestisce due vettori di interrupt: lo zero, abbinato al pin 2 e l'uno abbinato al pin 3.
  • digitalPinToInterrupt(pin). Ritorna il vettore di interrupt in funzione del numero di pin. es digitalPinToInterrupt(3) ritorna il valore 1.
  • detachInterrupt(intvec). Disabilita lo specifico interrupt.
  • noInterrupts(). Disabilita tutti gli interrupt.
  • interrupts(). riabilita gli interrupt.

Si rimanda alla documentazione ufficiale per maggiori informazioni.

La gestione dell'interrupt del processore ATMEGA328 in realtà è più articolata, ed una più ampia esposizione è disponibile a questo indirizzo: http://courses.cs.washington.edu/courses/csep567/10wi/lectures/Lecture7.pdf

Per questa esposizione ci si limita a un semplice ma significativo (speriamo) esempio; l'accensione di un led alla pressione di un tasto.

Lo schema  è riportato al link: Schema

La gestione via programma.

Arduino con LEDIl programma è altrettanto semplice. Viene utilizzato il LED L già presente sulla scheda Arduino UNO. Il pulsante è collegato al piedino 2. L'ingresso viene mantenuto alto da una resistenza. La pressione del tasto lo manda a massa.

Il programma principale (loop) testa in continuazione lo stato dell'ingresso (polling). Se premuto (LOW) si accende il LED.

 

 

  1. #define LED 13                // LED al pin 13
  2.                               // Al pin 13 è collegato il led L giallo
  3.                               // sulla scheda di ARDUINO UNO
  4. #define PULSANTE 2            // Il pulsante collegato al pin 2  
  5. int  stato = 0;               // usato per gestire lo stato del pulsante
  6.                               // Pulsante NON premuto = 1 (alto)
  7.                               // Pulsante premuto = 0 (basso)
  8.                                
  9. void setup() {
  10.   // Inizializzo ingressi ed uscite
  11.   pinMode(LED, OUTPUT);       // il pin del LED come uscita  
  12.   pinMode(PULSANTE, INPUT);   // imposta il pin del pulsante come ingresso
  13. }
  14.  
  15. void loop() {
  16.   // Programma principale
  17.    stato = digitalRead(PULSANTE);  // legge il valore dell'input
  18.  
  19.   // controlla che l'input sia LOW = pulsante premuto  
  20.   if (stato == LOW)          
  21.     digitalWrite(LED, HIGH);  //accende il LED                                  
  22.   else  
  23.     digitalWrite(LED, LOW);   //spegne il LED  
  24.  
  25.   // delay(1000);
  26. }

Il listato è facilmente comprensibile leggendo i commenti.

L'ultima riga è commentata. Se si toglie il commento il programma passa la maggior parte del suo tempo nella funzione delay.Il delay vuole simulare una parte di programma che richiede tempo per essere esegiuta, durante il quale non è possibile  prestare attenzione allo stato del pulsante. In questo caso veloci pressioni del pulsante verranno ignorate ed occorrerà tenere premuto il pulsante per almeno il tempo di delay per vederne l'effetto.

La gestione via interrupt

Di seguito lo stesso programma attraverso l'uso di interrupt.

Occorre definire il vettore di interrupt, nel nostro caso 0, abbinato al pin 2. La routine di gestione dell'interrupt si occupa di gestire il LED. In questo caso, volutamente semplice, si evidenzia come il programma principale loop() resta vuoto.

  1. #define LED 13                // LED al pin digitale 13
  2.                               // Al pin 13 è collegato il led L giallo
  3.                               // sulla scheda di ARDUINO UNO
  4. #define PULSANTE 2            // Il pulsante collegato al pin 2                  
  5.  
  6.  
  7. void setup() {
  8.   // put your setup code here, to run once:
  9.   pinMode(LED, OUTPUT);       // il pin del LED come uscita  
  10.   pinMode(PULSANTE, INPUT);   // imposta il pin del pulsante come ingresso
  11.  
  12.   attachInterrupt(0, interruptGiallo, FALLING);
  13.  
  14. }
  15.  
  16. void loop() {
  17.  
  18.   //delay(1000);  
  19.  
  20. }
  21. void interruptGiallo()  {  
  22.    // leggo lo stato del pulsante, ne faccio la negazione (!)
  23.    // e scrivo il rusultato sull'uscita
  24.    digitalWrite(LED,!digitalRead(PULSANTE));  
  25.   }

La gestione dell'interrupt si riduce a digitalWrite(LED,!digitalRead(PULSANTE));. Viene letto lo stato del pulsante digitalRead(PULSANTE));. il simbolo ! nega quanto letto: 0 diventa1 e 1 diventa 0. Il risultato è scritto sull'output: digitalWrite(LED,...);

Se si toglie il commento alla funzione delay si può verificare come la risposta alla pressione del pulsante è immediata.

In questo esempio la gestione dell'interrupt è semplice ma, se le azioni conseguenti l'interrupt, sono più complesse e lunghe è bene ridurre al minimo la routin dell'interrupt e spostare nel programma principale le azioni da fare.

Inoltre, per la chiamata di attachInterrupt(), si è usato un metodo sconsigliato dalla documentazioen. E' sempre bene leggere la documentazione.

Di seguito lo stesso programma rivisto in base a queste due osservazioni.

  1. #define LED 13                // LED  al pin digitale 13
  2.                               // Al pin 13 è collegato il led L giallo
  3.                               // sulla scheda di ARDUINO UNO
  4. #define PULSANTE 2            // Il pulsante collegato al pin 2
  5.  
  6. volatile int stato = 0;       // Stato viene usato per memorizzato lo lo stato del pulsante.
  7.                               // la dichiarazione voltatile è richiesta
  8.                               // per assicurarsi la lettura di stato all'interno
  9.                               // di tutto il progrramma in C              
  10.  
  11. void setup() {
  12.   // put your setup code here, to run once:
  13.   pinMode(LED, OUTPUT);       // il pin del LED come uscita  
  14.   pinMode(PULSANTE, INPUT);   // imposta il pin del pulsante come ingresso
  15.  
  16.   attachInterrupt(digitalPinToInterrupt(PULSANTE), interruptGiallo, FALLING);
  17. }
  18.  
  19. void loop() {
  20.  
  21.   //delay(1000);  
  22.  
  23.   digitalWrite(LED,stato);    // Accendo o spengo il LED in funzione dello stato
  24.  
  25. }
  26. void interruptGiallo()  {  
  27.    // leggo lo stato del pulsante,
  28.    stato = !stato;  
  29.    
  30.   }

In questo esempio viene usata la funzione digitalPinToInterrupt(PULSANTE) come suggerito dalla documentazione. Il risultato è anche una più facile lettura del programma e una maggiore indipendenza di questo dalle versioni di Arduino.

La variabile definita come volatile.

Il linguaggio 'C' cerca ottimizzare il codice per risparmiare memoria e velocizzare il programma. In un caso come in esempio, all'interno di loop() viene usata la variabile stato e non c'è nessun elemento, all'interno di loop() che ne modifica il valore. Molti compilatori C riconoscono questa situazione e ottimizzano il codice mettendo il valore di stato in un registro interno, e il valore di stato non verrà aggiornato durante i cicli di loop successivi.

In realtà stato può cambiare, ma con una gestione esterna a loop(), comandata dall'interrupt. La definizione volatile obbliga il compilatore C a rinunciare ad ottimizzazioni per la variabile stato e a rileggerne il valore ad ogni uso della variabile. In questo modo, le modifiche del valore dovute alla routine di interrupt si possono riflettere nella funzione loop().

Ancora, la chiamata a delay, serve a verificare che il bottone viene letto sempre. Una veloce pressione del tasto, durante il tempo di delay, non viene persa e il led si accenderà o spegnerà anche se dopo il ritardo.

Ultima osservazione.

Rimbalzo pulsanteI pulsanti meccanici sono soggetti a rimbalzi, che possono produrre alla pressione o al rilascio, segnali spuri. Questo potrebbe generare diverse chiamate alla routine di interrupt creando comportamenti anomali.
In un progetto occorre valutare l'effetto di questi rimbalzi ed eventualmente porvi rimedio con tecniche chiamate di debouncing.

Per le tecniche per sopperire al problema si rimandano ad altro articolo.

 

 

 

Categoria: 

Tags: 

Mi piace: 

0
No votes yet

Interrupt con PSoC

Lo schemaLa gestione dell'interrupt in PSoC rispecchia quanto descritto nella sezione generale.

Procediamo con un facile esempio, si fa riferimento alla accensione di un LED attraverso un pulsante, prima attraverso il controllo del programma principale, poi con l'uso dell'interrupt.


Lo schema si realizza usando l'ambiente di sviluppo PSoC creator, dove è possibile definire le risorse (in questo caso un input e un output entrambi digitali) ed alcuni componenti esterni.

Il componente Bootloadable serve semplicemente per caricare il programma.

I componenti esterni, in blu nello schema, sono solo di riferimento (il LED, il pulsante, Vdd e GND) e non concorrono in alcun modo alla creazione del progetto.
le risorse: il pin di input e di output vengono usate nella fase di Build per generare le funzioni (API) che serviranno per controllarle.

Il nome LED, l'output in questo esempio, è stato definito in fase di progettazione configurando la risorsa Digital Output Pin. La fase di Build costruisce le funzioni relative a LED, in particolare la funzione LED_Write() e LED_Read() (l'output può anche essere letto). Da notare come le funzioni prendono come prefisso il nome (LED) definito in progetto.

Il programma si riduce ad una sola riga:

LED_Write(!Pulsante_Read());

all'interno del loop principale for(;;;). Viene letto lo stato del pulsante, Pulsante_Read(), negato il valore (il pulsante è attivo basso) e il risultato è scritto sul led LED_Write().

La funzione CyDelay(1000) -commentata- introduce un ritardo si 1000 mS per simulare l'effetto di un lungo programma.

Il programma completo è il seguente:

  1. /* ========================================
  2.  *
  3.  * Copyright YOUR COMPANY, THE YEAR
  4.  * All Rights Reserved
  5.  * UNPUBLISHED, LICENSED SOFTWARE.
  6.  *
  7.  * CONFIDENTIAL AND PROPRIETARY INFORMATION
  8.  * WHICH IS THE PROPERTY OF your company.
  9.  *
  10.  * ========================================
  11. */
  12. #include <project.h>
  13.  
  14. int main()
  15. {
  16.     CyGlobalIntEnable; /* Enable global interrupts. */
  17.  
  18.     /* Place your initialization/startup code here (e.g. MyInst_Start()) */
  19.  
  20.     for(;;)
  21.     {
  22.         /* Place your application code here. */
  23.         LED_Write(!Pulsante_Read());
  24.        
  25.         // CyDelay(1000);
  26.        
  27.     }
  28. }

Gestione via Interrupt

La stessa funzione può essere scritta con maggiore efficienza gestendo il pulsante via interrupt. Lo schema relativo è il seguente.

Componente interrupt PSoC

 

Il pulsante genera un interrupt. Nel PSoC Creator occorre configurare il pin di input per abbinarlo all'interrupt che dovrà attivarsi sia alla transizione da alto a basso (si preme il pulsante), sia alla transizione da basso ad alto (si rilascia il pulsante).

Configurazione interrupt pin ingresso

Alla nuova linea, agganciata al pin di input, va collegato il relativo componente interrupt:

Componente interrupt PSoC

La risorsa interrupt ha un nome definito nel progetto, in questo caso Pul_IRQ. Modificato il progetto, l'operazione di Build costruisce le funzioni (API) necessarie alla gestione dei suoi componenti. La gestione dello specifico interrupt la si può implementare modificando l'API generata del build, o implementandola all'interno del main.c. E' il metodo usato in questo progetto.

Occorre, poi, abilitare l'interrupt (CyGlobalIntEnable;) e inizializzare la funzione (Pul_IRQ_StartEx();). Il loop principale for(;;) resta vuoto.

Ecco il codice completo, in inglese quanto generato dal Creator:

  1. /* ========================================
  2.  *
  3.  * Copyright YOUR COMPANY, THE YEAR
  4.  * All Rights Reserved
  5.  * UNPUBLISHED, LICENSED SOFTWARE.
  6.  *
  7.  * CONFIDENTIAL AND PROPRIETARY INFORMATION
  8.  * WHICH IS THE PROPERTY OF your company.
  9.  *
  10.  * ========================================
  11. */
  12. #include "project.h"
  13.  
  14. CY_ISR(Pul_IRQ_handler)
  15. {
  16. /* ISR Code here */
  17.     // Resetto l'interrupt abbinato al pulsante
  18.     Pulsante_ClearInterrupt();
  19.  
  20.     // Leggo il pulsante e accendo il led se premuto
  21.     LED_Write(!Pulsante_Read());    
  22. }
  23.  
  24.  
  25. int main()
  26. {
  27.     CyGlobalIntEnable; /* Enable global interrupts. */
  28.    
  29.     // inizializzo la gestione dell'Interrupt
  30.     Pul_IRQ_StartEx(Pul_IRQ_handler);
  31.    
  32.  
  33.     /* Place your initialization/startup code here (e.g. MyInst_Start()) */
  34.  
  35.     for(;;)
  36.     {
  37.         /* Place your application code here. */
  38.        // Non c'è codice all'interno del loop
  39.        
  40.     }
  41. }
  42.  
  43. /* [] END OF FILE */

 

Definizione dei pin fisici di input e output e priorità interrupt

Configurazione PinIl progetto sviluppato utilizza, per le risorse, dei nomi simbolici che, a priori, non sono abbinati a nessuna risorsa fisica. Questo rende più flessibile il progetto, lasciando libero il programmatore di definire l'I/O successivamente e tanto altro senza nessuna necessità di modificare il programma.

Il progetto crea un file con estensione nomeprogetto.cydwr  (Design Wide Resource) che consente la configurazione fisica dei pin e di altre risorse. L'operazione è grafica. Cliccando sul file in questione (lato sx dell'ambiente di lavoro, tab Source) si apre la relativa grafica.

Il nome della risorsa (lato destro) la si abbina al pin fisico trascinandola sulla immagine del pin del processore (immagine centrale). Eventuali conflitti (es. abbinare l'input con il pin di alimentazione) impediscono l'operazione.

Per questo progetto sono stati utilizzati i pin P1[6] per il LED e il pin P0[7] per il pulsante, perché già presenti sul kit CY8KIT-049-42xx utilizzato per questi esempi, e quindi non necessita nessun componente esterno.

Priorità interruptIn modo analogo si possono definire il vettore di interrupt e la relativa priorità.

In questo esempio i valori di default vanno bene in quanto si stiamo utilizzando un solo interrupt.

 

 

 

Approfondimenti.

PSoCdella fama e diffusione in ambito hobbistico alla pari di Arduino. E' meno conosciuto, quindi si ritiene utlie aggiungere alcuni dettagli.

I componenti sono configurati in modo interattivo utilizzando l'interfaccia grafica (GUI) del  PSoC creator selezionandoli dal catalogo e trascinati nello schema.

I componenti

Il click destro sul componente mostra il menu contestuale. Le due voci più importanti sono Configure e Open DataSheet: la prima permette la configurazione dle componente, la seconda  apre un file PDF con la documentazione del componente.

Menu contestuale

 

Output

PSoC OutputL'immagine riassume la configurazione dell'output per questo esempio. Il none LED è importante perché diventa il prefissodi tutte le funzioni inerenti il pin. Il pin è configurato come Strong drive, ideale per pilotare il led. Lo stato iniziale sarà basso (Led Spento).

Il pin è una uscita digitale (Digital output) e non ha connessioni hardware (HW connection) perché pilotato via software.

La documentazione riporta la descrizione completa.

 

Input

PSoC inputImportanti sono il nome (Pulsante). Il drive mode, Resistive pull up, che inserisce internamente al PSoC, una resistenza verso l'alimentazione evitando l'uso di una resistenza esterna; in questo modo l'input pulsante è nello stato alto se non premuto e basso se premuto.

Il pulsante genera l'interrupt quando premuto e quando rilasciato. La relativa configurazione è nel tab input.

 

Interrupt abbinato al Pin

 

Interrupt

Il componente interrupt non richiede particolari configurazioni. Come gli altri componeti lo si selezione dal catlogo e lo si inserisce trascinandolo all'interno dello schema. Unico elemento importante è il nome. Per comodità viene cambiato in con un nome più utile per riconoscerne l'utilizzo.

Interrupt

 

 

 

Categoria: 

Tags: 

Mi piace: 

0
No votes yet