MSP430: Le Interruzioni e le modalità Ultra Low Power

Valutazione attuale: 4 / 5

Studiare l'architettura di un microcontrollore non è sufficiente per iniziare a programmare lo stesso. Infatti molti aspetti della programmazione, sebbene strettamente legati al microcontrollore, vengono a dipendere anche dal linguaggio e compilatore utilizzato.
Il linguaggio C astrae certamente il programma dall'architettura del microcontrollore ma molti aspetti come le interruzioni o impostazioni delle periferiche, richiedono una conoscenza accurata sia del microcontrollore che del compilatore. In questa parte del corso utilizzeremo le interruzioni e le modalità Ultra Low Power.  
 

MSP430: Le interruzioni

Un'interruzione rappresenta un evento che interrompe il normale flusso del programma al fine di gestire un evento interno o esterno che necessita di “attenzione”. Al verificarsi di un interrupt il Program Counter viene salvato nello stack assieme allo Status Register e il programma continua la sua esecuzione dall'Interrupt Service Routine (ISR), ovvero la funzione ad hoc realizzata per la gestione di una determinata interruzione. Al fine di permettere l'esecuzione del codice, qualora il microcontrollore sia in sleep mode, qualunque mondatila Ultra Low Power viene lasciata per andare in Active Mode (AM).
Gli MSP430 possiedono un Interrupt Vector Table a priorità fissa, ovvero ad ogni interrupt è associato un indirizzo fisso al quale si trova l'indirizzo dell'Interrupt Service Routine. Più è alta la posizione del vettore delle interruzioni all'interno della tabella, maggiore è la sua priorità.
L'Interrup Service Routine ha il compito di gestire l'interruzione ma deve essere scritta in maniera più breve possibile. Infatti, quando è in esecuzione un ISR vengono disattivate le interruzioni, per cui non possono essere gestiti altri eventi. Essere veloci è dunque di importanza fondamentale al fine di garantire una rapida risposta ad eventi esterni. Qualora l'interrupt richieda calcoli lunghi è preferibile impostare un semplice flag all'interno dell'ISR ed eseguire una semplice macchina a stati all'interno del main.
Sebbene le interruzioni siano disabilitate durante l'esecuzione di un ISR questo non vuol dire che altri eventi d'interruzione siano persi, ma soltanto che non potranno essere serviti.
Infatti ad ogni sorgente d'interruzione è associato un flag d'interruzione che viene attivato anche se le interruzioni sono disattivate. Una vota che le interruzioni sono riattivate un flag d'interruzione posto ad 1 fa generare una nuova interruzione.
Per tale ragione è importante, alla fine dell'esecuzione di un ISR, resettare il flag delle interruzioni, al fine di evitare che vengano eseguite in maniera continua nuove interruzioni a causa di un flag che rimane sempre ad 1.
A seconda delle periferiche si possono avere Interrupt Vector dedicati ad un particolare evento o gruppi di eventi. Nel caso l'Interrupt Vector sia associato ad un solo evento, il flag relativo viene resettato automaticamente. Alcune periferiche pur avendo un solo Interrupt Vector associato a più eventi possiedono una tabella secondaria associa all'interruzione che permette di riconoscere facilmente la sorgente che ha causato l'interruzione. La presenza o meno di questa tabella IV (Interrupt Vector) deve essere verificata nel datasheet del dispositivo utilizzato.

Se due interrupt sono generati o attivi in contemporanea, quello a più alta priorità viene servito prima. Sebbene si possa pensare che due segnali difficilmente possano “arrivare” in contemporanea, la possibilità che due flag delle interruzioni siano attivi in contemporanea non è  remota. Basti pensare al fatto che durante l'esecuzione di un ISR si possono verificare altre interruzioni in tempi diversi. All'uscita dall'ISR si verrebbero ad avere più flag attivi in contemporanea. Indipendentemente dal tempo di arrivo, viene servita l'interruzione a più alta priorità. In Figura 1 è riportato un dettaglio del vettore delle interruzioni dell'MSP430G2553.

Figura 1: MSP430G2553 Interrupt Vector Table

Figura 1: MSP430G2553 Interrupt Vector Table.

Si può notare come la porta P1 e P2 abbiano due vettori diversi, ma i singoli pin fanno riferimento ad uno stesso vettore. Il Timer A ha due vettori delle interruzioni, uno associato al Capture Comper Module 0 e il secondo al resto dei Capture Compare Module (in questo caso solo 1). Questo discende da alcune peculiarità del modulo CC0 i cui dettagli verranno discussi nel capitolo dedicato ai timer.

MSP430: Le interruzioni sulla porta P1

Gli MSP430 della Value Line possiedono linee di interruzione sulla porta P1 e P2. Ad ogni porta è associato un Interrupt Vector e molteplici registri per le impostazioni. In particolare i registri per le interruzioni associati alla porta P1 sono:

  • P1EN
  • P1IES
  • P1IFG

Che rappresentano rispettivamente il registro per abilitare le interruzioni sui singoli pin (P1EN), la selezione del fronte (P1IES) e il relativo flag delle interruzioni, un bit per pin. P1IES può avere le seguenti impostazioni:

Bit 1: l’interrupt viene abilitato nella transizione da livello alto a livello basso del relativo pin.
Bit 0: l’interrupt viene abilitato nella transizione da livello basso a livello alto del relativo pin.  

Oltre a questi registri si ricorda che ogni pin può avere un resistore di pull-up e pull-down a seconda delle impostazioni dei registri:

  • P1REN
  • P1OUT

Come detto quando viene generata una interruzione, il programma viene interrotto o il microcontrollore viene risvegliato, al fine di eseguire le istruzioni contenute all'interno dell'Interrupt Service Routine.
Al fine di dichiarare una funzione come Interrupt Service Routine è necessario scrivere il seguente codice:

#pragma vector = PORT1_VECTOR

__interrupt void P1_ISR(void) {

// Codice

}

In particolare la direttiva pragma permette di associare al vettore delle interruzioni assegnato alla PORT1 l'indirizzo di inizio della funzione utilizzata come Interrupt Service Routine. In questo modo al verificarsi dell'interruzione viene richiamata la funzione corretta associa all'ISR.
La funzione ISR deve essere dichiarata con parametro d'ingresso e restituito pari a void, e deve essere preceduta dalla parola __interrupt.

Il nome delle funzione P1_ISR può essere cambiato a piacimento ma frequentemente si mette il nome del modulo seguito da ISR.

Il valore associato a vector  viene a dipendere dal particolare modulo e Interrupt Vector. Il nome corretto può essere trovato all'interno dell'header file del microcontrollore utilizzato.
Nel caso dell'MSP430G2231 si hanno per esempio i seguenti vettori:

#define PORT1_VECTOR        (2 * 1u)          /* 0xFFE4 Port 1 */
#define PORT2_VECTOR        (3 * 1u)          /* 0xFFE6 Port 2 */
#define USI_VECTOR          (4 * 1u)          /*0xFFE8 USI */
#define ADC10_VECTOR        (5 * 1u)          /* 0xFFEA ADC10 */
#define TIMERA1_VECTOR      (8 * 1u)          /* 0xFFF0 Timer A CC1,
#define TIMERA0_VECTOR      (9 * 1u)          /* 0xFFF2 Timer A CC0 */
#define WDT_VECTOR          (10 * 1u)         /* 0xFFF4 Watchdog Timer
#define NMI_VECTOR          (14 * 1u)         /* 0xFFFC Non-maskable
#define RESET_VECTOR        (15 * 1u)         /* 0xFFFE Reset [Highest


mentre per l'MSP430G2553 si hanno i seguenti vettori delle interruzioni.

#define PORT1_VECTOR            (2 * 1u)      /* 0xFFE4 Port 1 */
#define PORT2_VECTOR            (3 * 1u)      /* 0xFFE6 Port 2 */
#define ADC10_VECTOR            (5 * 1u)      /* 0xFFEA ADC10 */
#define USCIAB0TX_VECTOR        (6 * 1u)      /* 0xFFEC USCI A0/B0
#define USCIAB0RX_VECTOR        (7 * 1u)      /* 0xFFEE USCI A0/B0
#define TIMER0_A1_VECTOR        (8 * 1u)      /* 0xFFF0 Timer0)A CC1,
#define TIMER0_A0_VECTOR        (9 * 1u)      /* 0xFFF2 Timer0_A CC0
#define WDT_VECTOR              (10 * 1u)     /* 0xFFF4 Watchdog Timer
#define COMPARATORA_VECTOR      (11 * 1u)     /* 0xFFF6 Comparator A
#define TIMER1_A1_VECTOR        (12 * 1u)     /* 0xFFF8 Timer1_A CC1-
#define TIMER1_A0_VECTOR        (13 * 1u)     /* 0xFFFA Timer1_A CC0
#define NMI_VECTOR              (14 * 1u)     /* 0xFFFC Non-maskable
#define RESET_VECTOR            (15 * 1u)    /* 0xFFFE Reset [Highest


Il numero dei vettori delle interruzioni viene quindi a dipendere dal particolare dispositivo utilizzato visto che vari modelli possono o meno avere determinati moduli/funzioni. E' bene sempre fare riferimento al datasheet e all'header file al fine di utilizzare in maniera opportuna i nomi dei vettori.
 

MSP430: Esempio di risveglio da pressione del tasto

Vediamo ora come unire i vantaggi delle interruzioni con le modalità a basso consumo. Si supponga di voler spegnere il proprio sistema usando un semplice push-button, ovvero lasciare il sistema alimentato ma mandarlo in standby e risvegliarlo con un semplice tasto, come per una calcolatrice.
Si crei un nuovo progetto in Code Composer Studio basato su MSP430G2553 o MSP430G2xxx e si copi il seguente codice sorgente:


#include <msp430.h>

#define ON  1
#define OFF 0

volatile unsigned char state = OFF;


void main(void) {


unsigned char loop;

WDTCTL = WDTPW | WDTHOLD;

//Bit 0 Output (LED)
P1DIR  |= BIT0;
P2DIR  = 0x00;

//Attiva resistore per il pulsante S2 e sugli ingressi non usati
P1REN |= BIT1 + BIT2 + BIT3 + BIT4 + BIT5 + BIT6 + BIT7;
//Attiva resistore per il pulsante S2 e sugli ingressi non usati
P2REN |= BIT0 + BIT1 + BIT2 + BIT3 + BIT4 + BIT5 + BIT6 + BIT7;

//Attiva pull-up resistor
P1OUT |= BIT3;

// Abilita interruzione sul pulsante S2
P1IE |= BIT3;

// Fronte High to Low
P1IES |= BIT3;

//Abilita le interruzioni globali
__bis_SR_register (GIE);

while (1){

switch (state) {

case ON :

for (loop=0; loop<100; loop++){
__delay_cycles (1000);
}

P1OUT ^= BIT0;
break;

case OFF :   P1OUT &= ~BIT0;

__bis_SR_register (LPM4_bits);
break;

default:     break;

}

}

}


//*************************************
//               ISR
//*************************************


#pragma vector = PORT1_VECTOR
__interrupt void P1_ISR(void) {


if (P1IFG & BIT3 ) {

//Filtro antirimbalzo
__delay_cycles (10000);

if (!(P1IN & BIT3)) {

if (state == ON) {

state = OFF;

} else {

state = ON;

}

}

//Clear interrupt flag
P1IFG &= ~BIT3;

__bic_SR_register_on_exit(LPM4_bits);

}


}


L'esempio è leggermente più complesso degli altri fin ora presentati, ma è anche il primo esempio che in un certo qual modo fa qualcosa di “utile”, ovvero disattivare il sistema e andare in Low Power mode.

Il programma inizia definendo due costanti, rispettivamente ON e OFF in maniera da poter implementare una semplice state machine.
Lo stato del sistema viene dichiarato all'interno della variabile globale state. Tale variabile è stata definita globale (ovvero fuori dal main) in maniera da poter essere letta e scritta sia nel main che all'interno dell'ISR. Dal momento che anche l'ISR può accedere alla variabile, quest'ultima è stata definita volatile. In questo modo il compilatore non effettuerà nessuna ottimizzazione basandosi sul valore della variabile. Alcune volte il compilatore se ha già caricato la variabile in un registro Rxx non la ricarica per fare un nuovo confronto. Se la variabile è definita volatile, l'assumere che la variabile non sia cambiata potrebbe non essere corretto per cui il compilatore non fa alcuna ottimizzazione.

Il main inizia con il disattivare il watchdog e inizializzare le porte P1 e P2. Si noti che entrambe le porte sono state inizializzate impostando ogni pin come ingresso ad eccezione di BIT0 di P1 utilizzato per il LED.

Successivamente per ogni pin si è attivato il resistore di pull-down ad eccezione di BIT3 di P1 impostato con un resistore di pull-up. Questo è necessario perché il pulsante S2, alla sua pressione, è collegato a massa. In questo esempio, si sono attivati tutti i resistori in maniera da poter limitare i consumi in stato Low Power.

Tale funzione si legge come Bit Set Status Register (scrivendo __bi e premendo control + space compaiono tutte le funzioni intrinseche per lo Status Register).
Effettuata l'inizializzazione delle porte si abilitano le interruzione sul pin BIT3 al fine da rilevare la pressione del pulsante S2. Dal momento che è presente un resistore di pull-up e alla pressione del pulsante il pin BIT3 di P1 è posto a massa, si è impostato il fronte di riconoscimento falling edge (da alto a basso). Infine, si sono abilitate le interruzioni globali per mezzo del bit GIE dello Status Register, attivabile per mezzo della funzione intrinseca:

__bis_SR_register (GIE);

Finite le impostazioni il programma entra in un loop infinito creato per mezzo di un while. Ciononostante il programma non è sempre in esecuzione, infatti nel ciclo infinito viene controllata la variabile state, che di default vale OFF. Il controllo avviene per mezzo dell'operatore switch (tale controllo implementa una semplice state machine). Nel nostro caso la state machine ha solo due stati OFF e ON ed il passaggio dall'uno all'altro avviene per mezzo della pressione del pulsante. La prima volta che viene controllata la variabile state, il valore è OFF per cui  viene spento il LED ed eseguita l'istruzione (funzione intrinseca):

__bis_SR_register (LPM4_bits);

Questa permette di mandare in microcontrollore in Ultra Low Power mode (si ricorda che le interruzioni sono abilitate, se cosi non fosse solo un Reset o Power Cycle permetterebbe di riattivare il sistema). Per tale ragione il clock e la CPU si disattivano, il programma si arresta e i consumi del sistema si aggirano a circa 100nA!

Per continuare la spiegazione del nostro programma bisogna premere il pulsante S2. Tale evento genera un'interruzione che nel caso specifico non interrompe l'esecuzione del programma ma un sonno profondo. Il Program Counter viene impostato per permettere l'esecuzione della funzione ISR che abbiamo nominato P1_ISR.  Sebbene nel nostro caso abbiamo una sola sorgente d'interruzione,  
è bene sempre controllare il registro dei flag al fine di verificare quale pin o modulo ha generato l'interruzione, nel caso specifico il registro dei flag è P1IFG. Una volta accertati che il BIT3 di P1IFG è attivo viene inserito un piccolo ritardo di 10ms al fine di permettere la stabilizzazione della linea associata al BIT3 (non è la soluzione ottimale per ottenere bassi consumi, ma non avendo studiato i Timer andrà più che bene). Dopo il ritardo per implementare un filtro anti rimbalzo, si effettua la lettura del pulsante, se questo è premuto, ovvero pari a 0, viene cambiato il valore della variabile state, da OFF a ON, o da ON a OFF, a seconda del valore corrente. Nel caso di prima esecuzione il valore sarà invertito da OFF a ON.

Dopo queste semplici operazioni viene azzerato il BIT3 del registro P1IFG. Nel nostro caso si sarebbe potuto anche scrivere 0x00 nel registro, ma nel caso in cui vengano usate anche le altre linee di interruzione, è bene che solo il bit d'interesse sia resettato. In questo modo, se durante l'esecuzione della ISR dovesse essere settato un altro flag, l'informazione non verrebbe persa.  

Prima di uscire dalla ISR viene eseguita una nuova funzione intrinseca:

__bic_SR_register_on_exit(LPM4_bits);

Questa permette di cambiare il valore dello Status Register memorizzato nello Stack, ovvero il valore che lo Status Register aveva prima dell'interruzione. In questo caso dal momento che venivamo da uno stato di sleep, se il vecchio Status Register venisse ripristinato, l'MSP430 tornerebbe in stato di sleep (si ricorda che la modalità Low Power è memorizzata nello Status Register). La funzione intrinseca eseguita permette di resettare i bit associati al Low Power mode LPM4 in modo tale che al ritorno dall'interruzione lo stato del microcontrollore sarà AM (Active Mode) e il controllo della variabile state viene nuovamente eseguito.

Da quanto appena detto, usciti dall'ISR il programma continua la sua esecuzione dall'istruzione che segue la funzione intrinseca:

__bis_SR_register (LPM4_bits);

per cui viene eseguita l'istruzione break e l'operatore condizionale switch viene nuovamente eseguito dall'inizio. In questo secondo caso il nostro stato è ON per cui viene attivato il LED e fatto lampeggiare fino a quando una nuova pressione del pulsante cambierà lo stato da ON a OFF e il microcontrollore tornerà a consumare circa 100nA.   
 

MSP430: Qualche altra considerazione sul programma

Nel nostro esempio è stata trattata solo l'interruzione della porta P1 per cui è stata inserita solo l'ISR per la stessa. Per ragioni di sicurezza è buon uso dichiarare anche le altre ISR, una per vettore, al fine da prevenire comportamenti anomali della CPU qualora venga generata un'interruzione imprevista da parte di periferiche che formalmente non dovrebbero generare alcuna interruzione. Questo non  è un evento raro in sistemi a microcontrollori che devono eseguire codice 24 ore su 24 indipendentemente che siano MSP430 o altri microcontrollori. Infatti un registro RAM potrebbe variare il proprio valore a causa di radiazioni o fenomeni elettromagnetici (Il cellulare per esempio).

Il programma di esempio ha messo in evidenza che prima di andare in Low Power mode è necessario abilitare il bit GIE (General Interrupt Enable). Questo è necessario oltre all'abilitare le interruzioni sui singoli pin o moduli. Infatti tutte le interruzioni dei moduli interni, salvo rare eccezioni, sono di tipo mascherabile, ovvero possono essere abilitate e disabilitate per mezzo del bit GIE. Ogni volta che si sta eseguendo un ISR viene automaticamente disabilitato il bit GIE in maniera da disabilitare interruzioni annidate. Interruzioni annidate sarebbero possibili abilitando il bit GIE all'interno dell'ISR. Sebbene sia lecito farlo, gestire interruzioni annidate può causare comportamenti anomali legati al fatto che non si sono considerati tutti i casi possibili in cui si possono verificare interruzioni annidate.

L'esempio mostrato, rappresenta una buona pratica di programmazione per quando riguarda la gestione delle interruzioni, ovvero ridurre al minimo il tempo di esecuzione dell'ISR cambiando semplicemente lo stato di una variabile che viene controllata nel loop principale del main.
 

MSP430: Misura della corrente

L'esempio appena spiegato oltre a mostrare come poter gestire le interruzioni da parte di diversi moduli interni del microcontrollore, rappresenta anche un buon esempio di come utilizzare le modalità Ultra Low Power del microcontrollore.

Nel caso specifico è interessante misurare le correnti che si possono ottenere senza dover staccare fisicamente il circuito dalla batteria per mezzo di un interruttore.

Prima di procedere alla misura della corrente è necessario rimuovere il ponticello da J5, in particolare quello per il LED verde LED2 in maniera da evitare che possa circolare della corrente parassita. Il LED1 si può lasciare collegato perché il relativo pin del microcontrollore è impostato come uscita.  

Successivamente è necessario programmare il dispositivo e fermare il Debug. Staccare la scheda dalla porta USB e riattaccarla. In questo modo si può essere certi che la scheda non è più collegata con Code Composer Studio e il modulo di Debug è disattivo. La porta USB servirà solo per alimentare il sistema.

Per misurare la corrente è sufficiente scollegare il ponticello Vcc da J3 e posizionare un amperometro tra i due pin del ponticello, in maniera da alimentare nuovamente il sistema. L'amperometro deve essere impostato alla portata più piccola disponibile della scala DCA. Gli amperometri a 3 digit e mezzo con portata minima da 200uA potrebbero avere qualche problema a misurare le correnti in LPM4, visto che la cifra meno significativa vale già 100nA.

Se si dovesse alimentare un sistema simile con una batteria a bottone modello CR2032  con capacità tipica di 220mA/h, si avrebbe che il sistema in standby potrebbe durare  250anni!
Naturalmente questo numero non ha senso, per due ragioni:
 

  • Il sistema non fa nulla
  • La batteria ha comunque un processo di autoscarica che limita la vita della batteria a tempi di circa 10-20anni (se di buona qualità).


Nonostante la durata non abbia senso, si capisce che semplicemente mandando il microcontrollore in standby non si necessita di un interruttore. Premendo il pulsante i consumi saliranno rapidamente a circa 1-2mA, ma a causa del LED lampeggiante il valore oscillerà piuttosto rapidamente. Altri esempi di modalità Low Power verranno mostrati quando verranno descritti gli altri moduli, i quali per esempio possono continuare a lavorare usando LPM0-LMP3 a seconda del clock attivato. In ultimo si ricorda che i dati presenti in RAM vengono mantenuti anche in LPM4, ovvero non si perde nessuna informazione. La famiglia MSP430F5xx introduce anche le modalità LMP4.5 con la quale si può disattivare anche la RAM e scendere ulteriormente con i consumi a circa 50-20nA.

 

Forum

Per qualunque domanda e chiarimento potete scrivere sul primo Forum in Italia dedicato agli MSP430.

 

Bibliografia

[1] : Home page dei microcontrollori MSP430 (Texas Instruments)

[2] : LaunchPad User Guide, slau318

[3] : User Guide MSP430x2xx

[4] : User Guide MSP430x5xx MSP430x6xx

 

Capitoli del Corso MSP430

Corso

Data

 

Titolo


Parte I 16-10-2011   MSP430: Microcontrollori Ultra Low Power
       
Parte II 23-10-2011   MSP430: Strumenti per Iniziare
       
Parte III 11-12-2011   MSP430: Code Composer Studio e LaunchPad
       
Parte IV- 1 15-04-2012   MSP430: Architettura e periferiche Ultra Low Power (Parte 1)
       
Parte IV- 2 22-04-2012   MSP430: Architettura e periferiche Ultra Low Power (Parte 2)
       
Parte V
13-05-2012   MSP430: Hello World
       
Parte VI 01-07-2012   MSP430: Utilizzo ed impostazione delle Porte I/O
       
Parte VII 18-11-2012   MSP430: Il modulo di distribuzione del Clock e modalità Ultra Low Power
       
Parte VIII 10-03-2013   MSP430: Le interruzioni e le modalità Ultra Low Power
       
Parte IX-1 13-10-2013   MSP430: Utilizzo del Modulo Timer_A (Parte 1)
       
Parte IX-2 13-10-2013   MSP430: Utilizzo del Modulo Timer_A (Parte 2)
       
      ...nuovi in arrivo...tieniti informato

 

       
 
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       

 


You don`t have permission to comment here!

Registrati al sito

Accedi a tutte le risorse e articoli non visibili pubblicamente, puoi registrarti con pochi passi.

Registrati al sito LaurTec.

Login