MSP430 – Controllo pulsante e Lampeggio Led su Interrupt del Timer A

Dopo il precedente articolo introduttivo sugli MSP430, ho cominciato a provarci gusto e così … sto andando avanti anche grazie al “LA” dato da Mauro con il suo corso sugli MSP430. In questo post lascio quindi qualche altro appunto sulle mie esperienze con questa nuova famiglia di MCU.

Dal momento che io senza gli interrupt non ci so stare, ho messo mano sul MSP430×2xx Family User’s Guide (Rev. H), sul datasheet dell’MSP430G2231 e sulla documentazione del CCS che è possibile trovare nella cartella:

C:\Program Files\Texas Instruments\ccsv4\msp430\doc\

Così incominciamo ad esplorare un po’ anche la gestione degli interrupt sugli MSP430. Eccoci qui con un primo esempio: facciamo lampeggiare un led sull’interrupt di un timer e contemporaneamente controlliamo la pressione di un pulsante.

Col CCS non ho trovato un sistema di refenziare direttamente i singoli pin di un banco di porte come faccio normalmente per i picmicro (es.: RA=1), dato che negli header non ci sono le macro già pronte ma i nomi dei bit sono definiti per essere utilizzati come maschere. Poco male: mi sono scritto delle semplici macro per accendere/spegnere i led e per controllare lo stato di pressione del pulsante:

// Macro per i led
// LED1 (rosso) su P1.0
#define LED1ON	(P1OUT | BIT0)
#define LED1OFF	(P1OUT & !BIT0)
#define LED1TOGGLE (P1OUT ^= BIT0)
// LED2 (verde) su P1,6
#define LED2ON	(P1OUT | BIT6)
#define LED2OFF	(P1OUT & !BIT6)
#define LED2TOGGLE (P1OUT ^= BIT6)

Abbiamo già incontrato P1OUT nel post precedente in cui illustro il programma che fa lampeggiare il led. Sappiamo che serve ad impostare il livello di uscita dei pin configurati come ingresso o abilitare le resistenze pullup/pulldown sui pin configurati come uscite.

Il significato delle macro dovrebbe essere chiaro: prendiamo ad esempio il Led1 (quello che sul Launchpad è di colore rosso): questo  è collegato alla porta P1.0. Configurando P1.0 come uscita, controlliamo il livello alto o basso tramite il registro P1OUT. Nell’Header degli MSP430 sono definite delle costanti BIT0, BIT1, BIT2 ecc che rappresentano un numero in cui è posto a 1 il bit indicato dalla costante (BIT0 vale 00000001, BIT2 vale 00000010 e così via). Con P1OUT | BIT0 quindi metto ad 1 il bit 0 di P1OUT, e di fatto accendo il led, con P1OUT & !BIT0 metto a zero il bit 0 e il led si spegne.

La macro LEDxTOGGLE esegue un XOR sul bit corrispondente al led e memorizza di nuovo lo stato di P1OUT, per cui serve ad invertire lo stato della porta. Questo mio articolo su come si “maneggiano” i bit dovrebbe togliere ogni dubbio.

Sul Launchpad abbiamo 2 pulsanti: S1 è il pulsante di reset, che non è utilizzabile per i nostri scopi, S2 è un pulsante a nostra disposizione ed è collegato a P1.3. S2, guardando lo schema elettrico del launchpad, chiude P1.3 verso massa, per cui è necessario attivare la resistenza di pullup su P1.3. Le resistenze (sia di pullup che di pulldown) si attivano/disabilitano nei registri PxREN, per cui nel mio caso, dovendo attivare la resistenza su P1.3 scriverò:

P1REN |= BIT3; // Resistenza attivata su P1.3

Per decidere se tale resistenza deve essere di pullup o pulldown, agisco sul pregistro PxOUT. Ho detto che i registri PxOUT servono a settare il livello di uscita qualora una porta sia impostata come uscita, o la resistenza di pullup/pulldown qualora la porta sia impostata come ingresso. Per attivare la resistenza di pullup viene messo a 1 il bit che ci interessa, mettendo 0 viene attivata una resistenza di pulldown:

P1OUT |= BIT3; // bit a 1 = La resistenza attivata è quella di pullup

Impostiamo quindi quali porte devono funzionare come ingressi e quali come uscite nei registri PxDIR (che sono in pratica gli equivalenti dei registri TRISx sui picmicro). Qui però bisogna ricordarsi che mettere un bit a 1 rende la porta un’uscita e metterla a zero un ingresso, per cui la logica è invertita rispetto ai picmicro:

P1DIR = 0x41;

con 0x41 (01000001) impostiamo quindi P1.6 e P1.0 come uscite, queste sono le porte dove sono collegati i due led, e le restanti come ingressi.

Avrei voluto scrivere P1DIR=0b01000001 ma col CCS non è possibile in quanto in realtà la notazione 0b(numero) non è una caratteristica propria del C a differenza della notazione esadecimale. Un workaround sarebbe quello di includere un file binary.h che definisce tutte le 256 costanti: #define 0b0000000 0x00, #define 0b00000001 0x01 e così via, ma sinceramente questo sistema non mi piace e preferisco piuttosto usare la calcolatrice scientifica.

Ora la porta P1.3, quella su cui è collegato il pulsante, è configurata come ingresso e leggerà normalmente un livello logico alto dato che su tale pin abbiamo attivato la resistenza di pullup. Premendo il pulsante S2, P1.3 va a massa. La pressione del pulsante la controllo con una macro:

// Macro per rilevare la pressione del pulsante S2
#define S2PUSH !(P1IN & BIT3)

Tale macro restituisce il valore booleano vero (grazie al fatto che ho messo un NOT) quando il bit 3 del registro P1IN si trova a zero, ovvero pulsante premuto. Nel main per controllarmi la pressione del pulsante scrivo:

if (S2PUSH)
   {
   __delay_cycles (70000);
      if (S2PUSH)
         {
         LED1TOGGLE;
         }
      while(S2PUSH);
   }

Quindi: controllo la pressione di S2 e do un ritardo di 70mila cicli che con il clock interno a 1MHz corrispondono a circa 70mS.

La frequenza di un ciclo istruzioni sugli MSP430 è uguale alla frequenza di clock, solo alcune istruzioni richiedono due colpi di clock per poter essere eseguite.

La funzione __delay_cycles è una funzione intrinseca del compilatore, ovvero fa parte di un set di funzioni predefinite dal compilatore (un po’ come le builtin di MPLAB C30). Eseguo quindi un secondo controllo del pulsante. Questa tecnica, sappiamo, serve per evitare i problemi derivanti dal rimbalzo del pulsante. Inverto quindi lo stato del led e rimango in blocco fino a che il pulsante non viene rilasciato. In realtà rimanendo in blocco non ci sarebbe bisogno di mettere l’antirimbalzo: ho messo entrambe le cose così scegliete voi come gestire il pulsante.

Passiamo ora a qualcosa di più avanzato: l’interrupt sul Timer. Sugli MSP430 abbiamo più vettori di interrupt: ogni periferica ha il suo vettore e quindi scriveremo tante funzioni quanti sono gli interrupt di cui abbiamo bisogno (a differenza dei PIC ad 8 bit). Ogni salto ad un vettore, inoltre, potrebbe essere stato causato da più di un flag associato alla periferica che l’ha scatenato, per cui in ogni interrupt, se dobbiamo controllare più flag, andremo a distinguere i vari flag. Per maggiori informazioni sugli interrupt fate riferimento al capitolo 2.2 Interrupts del MSP430×2xx Family User’s Guide (Rev. H).

Per questo esempio andrò ad utilizzare UN interrupt sul Timer A (sugli MSP430 i timer sono identificati da lettere, anzichè da numeri come siamo abituati coi picmicro).

Il Timer A è qualcosa di molto diverso rispetto ai Timer a cui siamo abituati sui Picmicro: qui oltre a funzionare da timer/contatore, lo stesso timer ha anche le funzioni di Capture/Compare/PWM. Mentre sui picmicro il modulo CCP è un modulo a parte (che lavora comunque in associazione ad un timer), qui è la stessa periferica Timer che esegue queste funzioni, per cui l’interrupt vector del timer potrà essere richiamato per vari motivi.

In particolare il Timer A possiede due vettori di interruzione. Diamo uno sguardo alla tabella che si trova a pagina 9 del datasheet dell’ MSP430G2231 (quello montato di default sul Launchpad):

A mio parere, il nome "Timer_A2" che fa riferimento alla locazione 0xFFF0, dovrebbe in realtà chiamarsi "Timer_A1" in quanto negli header è questo il nome utilizzato ed avrebbe anche più senso: Timer_A ha due vettori di interrupt, per cui li distinguiamo con 1 e 2. Penso sia un errore del datasheet

Vediamo che il Timer A ha un vettore di interruzione all’indirizzo 0xFFF2, che verrà richiamato dal flag CCIFG del registro TACCR0 (il salto a questo vettore è quindi richiamato da UN solo flag che si chiama appunto TACCR0 CCIFG), e un secondo vettore all’indirizzo 0xFFF0, richiamato dal flag CCIFG di TACCR1 e dal flag TAIFG (il salto a questo vettore è quindi richiamato da 2 diversi interrupts).

Quando il salto ad un vettore è causato da un solo flag, il flag viene resettato automaticamente (questo è il caso del salto al vettore 0xFFF2, causato da un solo interrupt). Quando invece il salto ad un vettore può essere causato da più di un interrupt, il relativo flag deve essere resettato manualmente.

Notiamo inoltre che gli interrupt sugli MSP430 hanno una loro priorità fissa: ci sono quindi dei livelli di priorità come sui picmicro a 16 bit, ma questi sono fissi e non assegnabili dall’utente: da una parte questo rende il tutto più semplice da gestire.

Anche qui abbiamo un flag di abilitazione globale interrupt, chiamato ugualmente GIE. Gli interrupt possono essere abilitati utilizzando un’altra macro intrinseca:

__enable_interrupt();

Oltre al flag di abilitazione globale ogni periferica ha i flag di abilitazione per i propri interrupt e di conseguenza i suoi flag di avvenuto interrupt.

Diamo ora uno sguardo ai settaggi del Timer A. I settaggi li possiamo trovare nel Family User Guide citato sopra. Il registro da impostare per il funzionamento del Timer A si chiama TACTL (Timer A ConTroL register):

TASSEL (Timer A Source SELect) : questi bit servono a scegliere la sorgente di clock che il Timer A deve utilizzare per il conteggio. Impostando questi due bit a 10 scegliamo SMCLK (clock di sistema – Sub Master CLocK).

ID (Input Divider) : questi bit selezionano il fattore di divisione del clock, mettiamo 00 per non eseguire alcuna divisione.

MC (Mode Control) : mettendo a zero entrambi questi bit, il Timer A è spento e non assorbe energia. Settandoli opportunamente è possibile fare in modo che il timer conti da zero fino ad un determinato valore (questo valore è da impostare nel registro TACCR0 – modalità chiamata up mode), oppure che conti fino a 0xFFFF (continuous mode) o infine che conti fino al valore di TACCTR0  e quindi a ritroso fino a tornare a zero (up/down mode). Imposterò questi due bit sul valore 10 che mi consente la modalità continua: il Timer A quindi conterà da 0 a 0xFFFF, giunto a 0xFFFF allo step successivo si ha un overflow, il contatore si azzera e scatta l’interrupt TAIFG.

TACLR (Timer A CLeaR) : serve ad azzerare il registro in cui il Timer A conserva il conteggio (registro TAR). Questo bit esegue anche l’azzeramento dell’input divider e del mode control.

TAIE (Timer A Interrupt Enable): messo a 1 consente di far scattare l’interrupt generico sull’overflow del Timer.

TAIFG (Timer A Interrupt FlaG): se 1 vuol dire che è scattato un interrupt causato dall’overflow.

Facendo uso dei nomi mnemonici contenuti nel file header dell’MSP430 che sto usando, posso quindi settare il Timer A in questo modo:

TACTL = TASSEL_2 + MC_2 + TAIE;

Il valore TASSEL2 imposta la sorgente di clock su SMCLK, il valore MC_2 consente di usare il Timer A in modalità continua e infine TAIE abilita il flag di interrupt sull’ overflow.

Oltre ad avere il flag TAIFG appena visto, vi è un registro TAIV (Timer A Interrupt Vector) che contiene un valore numerico associato alla causa di interrupt. Ad esempio se va in overflow il Timer A, oltre a trovarci settato il flag TAIFG vediamo che il registro TAIV contiene il valore 0x0A (valore definito come costante dal nome TAIV_TAIFG nel file header). Il registro TAIV può essere utilizzato per discernere la causa del salto al vettore di interrupt:

Vediamo ora come scrivere la routine di interrupt. La documentazione che ci serve è riportata nella guida utente del compilatore per gli MSP430:

C:\Program Files\Texas Instruments\ccsv4\msp430\doc\SLAU132E.pdf

Andate a pagina 91. Prima di scrivere la funzione che gestisce interrupt, andrà inserita una direttiva #Pragma che indica al compilatore per quale vettore di interrupt deve essere richiamata la funzione che seguirà:

#pragma vector=TIMERA1_VECTOR

Il nome mnemonico TIMERA1_VECTOR è contenuto nel file header dell’MSP430 e fa riferimento alla locazione 0xFFF0 alla quale viene eseguito il salto, tra gli altri motivi, su overflow del Timer A. Segue quindi la funzione che deve essere eseguita, preceduta dalla parola chiave __interrupt (che fa parte dello standard ANSI):

__interrupt void Timer_A (void)
{

Abbiamo visto che il salto a questo vettore potrebbe essere causato anche da un altro interrupt. Anche se in realtà non ce ne sarebbe bisogno dato che il nostro programma fa uso solo di questo, per buona abitudine mi vado a controllare quale interrupt l’ha causato. Notate ancora una volta che i nomi dei bit negli header sono dichiarati come maschere: non è possibile utilizzarli in maniera diretta come facciamo sui PICmicro (l’ho già detto prima a riguardo della referenziazione diretta dei nomi dei pin) ma vanno utilizzati in associazione ai registri di appartenenza con gli operatori logici, per cui è sbagliato scrivere:

if (TAIFG) // SBAGLIATO!

ma dobbiamo piuttosto scrivere:

if (TACTL & TAIFG)
	{

Il nome della funzione lo possiamo scegliere a piacere. Nell’interrupt decremento un contatore che ho preimpostato al valore 8. Nel momento in cui il contatore raggiunge il valore zero, inverto lo stato del led e quindi ricarico il contatore:

timerCount--;
if(timerCount==0)
        {
	LED2TOGGLE;
	timerCount=8;
	}

Il salto all’interrupt vector viene eseguito ogni 0xFFFF cicli dato che, lo ricordo ancora una volta, abbiamo impostato il Timer A per lavorare in modalità continua. Sappiamo che la frequenza di un ciclo istruzioni sugli MSP430 è uguale alla frequenza di clock: stiamo lavorando con il clock di default pari ad 1MHz, un ciclo istruzioni dura quindi 1μS. Il salto all’interrupt vector si verifica ogni 0xFFFF x 1μS =  65535μS. Moltiplicando per 8 questo tempo (la variabile timerCount serve a questo) abbiamo che lo stato del led si inverte ogni 524mS circa, corrispondenti alla frequenza di circa 1Hz:

Sull’oscilloscopio non otterremo un valore preciso per vari motivi, tra cui: il clock non è precisamente 1MHz ma qualcosina di più e c’è una certa latenza sia nel servire l’interrupt (6 cicli sugli MSP430) che nel ritornare dall’interrupt (5 cicli).

Dobbiamo quindi azzerare il flag. L’azzerare il bit TAIFG del registro TACTL non produce nessun effetto, bisogna operare nel registro TAIV:

TAIV=0;

Download

Il file è scaricabile dai soli iscritti a settorezero.com. Ricordo che l’iscrizione è gratuita e non sono necessari livelli o punteggi per accedere ai downloads, che sono quindi liberi a tutti. Questo non vuol dire che siete liberi di copiare il MIO materiale per far ingrassare gli altri: ricordo ancora una volta la roba che si trova su internet non è roba trovata in mezzo alla strada, dietro ci sono delle persone in carne e ossa, che esprimono delle volontà e avete l’obbligo di rispettarle. Il file zip include l’intero progetto in CCS per il Launchpad con su montato l’MSP430G2221: scompattatelo nella cartella workspace (ovvero la cartella che avete settato come spazio di lavoro per il CCS) e ve lo ritrovate direttamente nell’IDE del CCS.

MSP430 - Esempio interrupt su Timer A e controllo pulsante (441 download)

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)