MSP430: Utilizzo del Modulo Timer_A (Parte 2)

Valutazione attuale: 5 / 5

   Una volta comprese le varie opzioni e modalità offerte dal Timer_A possiamo procedere con degli esempi in maniera da mostrare le varie impostazioni e vantaggi applicativi derivanti dalle particolari modalità descritte. Gli esempi mostrano i dettagli per le seguenti applicazioni base:

  • Lampeggio di un LED per mezzo del Timer_A ed Interruzioni.

  • Lampeggio di un LED per mezzo delle uscite del modulo Capture e Compare.

  • Lampeggio di un LED e controllo dell'illuminazione con PWM.

  • Misura della frequenza di un segnale digitale.

 

Esempio 1 : Lampeggio di un LED per mezzo del Timer_A ed Interruzioni

   Ormai siamo esperti nel programmare gli MSP430, per cui non entrerò nel dettaglio di ogni istruzione. In particolare si noti che il codice non è ottimizzato per raggiungere bassi consumi, pur facendo uso della modalità LPM3. Infatti gli I/O non sono tutti inizializzati. Questa scelta è legata semplicemente al fatto di mantenere il codice più compatto e di facile comprensione.

L'esempio non fa altro che far lampeggiare il LED2 posto sul pin P1.6 della scheda LaunchPad. La base dei tempi è ottenuta per mezzo del Timer0, il cui clock è fornito dal cristallo esterno (al fine di far funzionare l'esempio è necessario saldare il cristallo da 32KHz fornito con il KIT LaunchPad o cambiare il clock di riferimento per ACLK con VLO piuttosto che XT). Il Timer0 viene impostato per lavorare in Up Mode, per cui il tempo è determinato dal registro CC0. Dal momento che il clock è di 32768Hz si capisce che impostando il registro TA0CCR0 a 16384, si avrà un evento Compare ogni 500ms. Il modulo Capture e Compare è inoltre impostato per generare un interrupt (bit CCIE del registro TA0CCTL0).

Il lampeggio del LED2 si ottiene nell'Interrupt Service Routine, i cui è invertito il valore del bit 6 della PORT1, per cui il LED lampeggerà alla frequenza di 1Hz. Si noti come non sia necessario resettare il Flag CCIFG dal momento che l'Interrupt Vector TIMER0_A0_VECTOR è associato solo a CCIFG, per cui viene resettato automaticamente al verificarsi di ogni interrupt. Si ricorda che questo è valido solo per gli Interrupt Vector che hanno una sola sorgente d’interruzione.

Si osservi che il LED continua a lampeggiare, anche se si è in modalità LPM3. Infatti, il cristallo da 32KHz non è disattivato in LPM3 e il Clock ACLK è anche disponibile. Per tale ragione il Timer continua il suo conteggio e ha la possibilità di generare un Interrupt che porta la CPU ad eseguire l'Interrupt Service Routine, all'interno della quale viene invertito ciclicamente lo stato del LED2.

L'utilizzo del Cristallo da 32KHz permette di ottenere una base dei tempi precisa ed in particolare può essere utilizzata per realizzare un orologio digitale. Si noti che per realizzare un orologio digitale non sarebbe richiesta la CPU sempre attiva. Impostando CCP0 a 0x7FFF è possibile ottenere un evento Compare ogni secondo e al risveglio del Microcontrollore la CPU può eseguire gli opportuni incrementi per Secondi, Minuti e Ore. Alcune varianti degli MSP430 possiedono anche un RTCC (Real Time Clock Calendar) interno, per cui l'orario viene mantenuto senza l'intervento della CPU.

Le impostazioni dei registri sono volutamente non commentate al fine da esortare il lettore a controllare il loro significato direttamente nei registri di configurazione descritti nei paragrafi precedenti.

 

 
#include <msp430.h>
 
void main(void) {
// Stop watchdog timer
WDTCTL = WDTPW | WDTHOLD;
 
//****************************************
// Impostazioni Porte I/O
//****************************************
 
P1OUT = 0x00;
P1DIR = BIT6;
 
//****************************************
// Impostazioni modulo Clock
//****************************************
 
BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;
BCSCTL1 |= XT2OFF | DIVA_0;
BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;
 
//****************************************
// Impostazioni Timer0
//****************************************
 
TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_0 | CCIE;
TA0CCR0 = 16383;
TA0CTL = TASSEL_1 | ID_0 | MC_1;
 
//****************************************
// Applicazione principale
//****************************************
 
// Loop Infinito
while (1){
__bis_SR_register(LPM3_bits + GIE);
}
}
 
//****************************************
// Timer0 CC0 Interrupt Service Routine
//****************************************
#pragma vector=TIMER0_A0_VECTOR
__interrupt void TIMER0_ISR(void) {
 
P1OUT ^= BIT6;
}

 

 

Esempio 2 : Lampeggio di un LED per mezzo delle uscite del modulo Capture e Compare

   Nel primo esempio abbiamo visto come far lampeggiare il LED invertendo lo stato dell'uscita del pin a cui è collegato. Sebbene questo sia un modo efficiente, poiché abbiamo usato una sola istruzione e siamo in LPM3 per il tempo restante, c'è un modo ancora più efficiente in cui si può restare sempre in LPM3 senza far intervenire la CPU. Questo modo sfrutta l'uscita le modulo Capture e Compare e richiede di abilitare la relativa uscita sulla porta PORT1. Per fare questo, oltre ad abilitare il bit 6 PORT1 come uscita, si è impostato il bit 6 anche del registro P1SEL, che permette appunto di avere in uscita il valore del modulo Capture e Compare. Diversamente dall'esempio precedente non vi è nessun Interrupt Service Routine ed in particolare la CPU non inverte lo stato del pin 6 della PORT1.

Oltre alle impostazioni precedenti, per il modulo Capture e Compare si è impostato anche il modulo Capture e Compare CC1 il quale controlla l'uscita alla quale è collegato il LED1.

In questo caso per avere il lampeggio di un secondo si è impostato CCR0 a 32768 (visto che si usa ancora il cristallo da 32768Hz), mentre CCR1 è impostato a 32000. In questo modo il LED2 viene acceso solo per 768 cicli di clock ovvero per 23ms, creando il tipico lampeggio di sistemi a basso consumo. Il lampeggio è dovuto al fatto che l'uscita del modulo CCR1 è impostata in modalità 3 ovvero Set/Reset.

Il risultato di questo esempio è quindi un avviso di un LED che lampeggia, senza avere nessun codice in esecuzione! Questa è la ragione per cui i moduli degli MSP430 vengono spesso chiamati “Smart Modules”.

 

 

 
#include <msp430.h>
 
void main(void) {
 
      // Stop watchdog timer
WDTCTL = WDTPW | WDTHOLD;
 
//****************************************
// Impostazioni modulo I/O
//****************************************
 
P1OUT = 0x00;
P1DIR = BIT6;
P1SEL |= BIT6;
P1SEL2 &= ~BIT6;
 
//****************************************
// Impostazioni modulo Clock
//****************************************
 
BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;
BCSCTL1 |= XT2OFF | DIVA_0;
BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;
 
//****************************************
// Impostazioni Timer0
//****************************************
 
TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_0 ;
TA0CCR0 = 32768;
 
TA0CCTL1 = CM_0 | CCIS_0 | OUTMOD_3 ;
TA0CCR1 = 32000;
 
TA0CTL = TASSEL_1 | ID_0 | MC_1;
 
//****************************************
// Applicazione principale
//****************************************
 
// Loop infinito
while (1){
__bis_SR_register(LPM3_bits);
}
 
}

 

 

Esempio 3 : Lampeggio di un LED e controllo dell´illuminazione con PWM

   In questo esempio si sono apportate alcune modifiche al precedente al fine di avere un'uscita PWM (Pulse Width Modulation). Per fare questo si è in primo luogo impostato il clock del Timer non più a 32KHz bensì ad 1 MHz, in particolare, se si osservano le impostazioni del modulo interno del Clock si può notare che si è fatto uso del valore calibrato di 1MHz. Questo non è necessario per la funzione PWM ma rende il progetto più completo. La frequenza più alta permette inoltre di avere una migliore risoluzione del PWM, permettendo un cambio d'intensità del LED più continua e fluida, per cui si potrebbe anche usare il valore di 16MHz, ovvero la massima frequenza possibile con la serie MSP430G2xx. Ciononostante si ricorda che con l'aumento della frequenza aumentano in generale i consumi, per cui è sempre bene utilizzare il valore opportuno e non eccedere le reali performance richieste.

Senza entrare nel dettaglio, si ricorda che un segnale PWM ha generalmente una frequenza costante e il suo Duty Cycle viene variato, ovvero il rapporto che esiste tra Ton e Toff della forma d'onda quadra in uscita al modulo Capture e Compare. La massima intensità del LED la si ottiene quando l'uscita del modulo CCR1 è sempre ad 1 mentre per valori intermedi si noterà una variazione dell'intensità del LED2.

Al fine di far partire il LED da spento, piuttosto che impostare CCR1 in modalità 3, si è impostata la modalità 7.

La frequenza del segnale PWM dipende da quanto rapidamente viene resettato il contatore del Timer0, il quale, essendo in modalità Up Mode conterà fino a CCR0. Dal momento che il clock usato è di 1MHz, e gli impulsi di clock contati prima del Reset sono 5000, si ha che la frequenza del PWM è di 200Hz. Con Frequenze dell'ordine di 200Hz si evita che l'occhio possa percepire la frequenza del PWM. Il processo per il calcolo di CCR0 è ingenerale inverso, ovvero si parte dalla frequenza PWM che si vuole avere e in base alla frequenza di clock fornita al Timer, si calcola il valore opportuno da scrivere nel registro CCR0.

Il Duty Cycle viene impostato per mezzo del registro TA1CCR1, ed in particolare il 100% si ha quando CCR1 è uguale a CCR0. In questo esempio il Duty Cycle viene variato all'interno del loop while di una quantità arbitraria pari ad 8.

In questo esempio la CPU è sempre in esecuzione per cui non abbiamo proprio un'applicazione Low Power. Volendo risparmiare energia ma dovendo tenere il il modulo DCO attivo, si può usare la modalità LPM0, che permette di disattivare la CPU. In questo caso sarebbe però necessario usare un secondo Timer che permetta di risvegliare la CPU periodicamente ed effettuare l'aggiornamento del Duty Cycle.

All'interno del loop è presente la funzione intrinseca __delay_cycles(5000); che permette di avere un ritardo di 5000 cicli di clock. Visto che MCLK è 1MHz si ha un ritardo di 5ms. Ogni 5ms viene effettuato l'incremento del registro CCR1 ovvero viene incrementato il Duty Cycle, il quale si riflette in un aumento dell'intensità del LED2. Quando il Duty Cycle raggiunge il valore di TA0CCR0, il registro TA0CCR1 viene posto nuovamente a 0.

 

 

#include <msp430.h>
void main(void) {
 
// Stop watchdog timer
WDTCTL = WDTPW | WDTHOLD;
 
//****************************************
// Impostazioni Porte I/O
//****************************************
 
P1OUT = 0x00;
P1DIR = BIT6;
P1SEL |= BIT6;
P1SEL2 &= ~BIT6;
 
//****************************************
// Impostazioni modulo Clock
//****************************************
 
BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;
 
if (CALBC1_1MHZ != 0xFF) {
   DCOCTL = 0x00;
   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;
}
 
 
BCSCTL1 |= XT2OFF | DIVA_0;
BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;
 
 
//****************************************
// Impostazioni Timer0
//****************************************
 
// Frequenza PWM 200Hz
TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_0 ;
TA0CCR0 = 5000;
 
TA0CCTL1 = CM_0 | CCIS_0 | OUTMOD_7 ;
TA0CCR1 = 0x00;
 
TA0CTL = TASSEL_2 | ID_0 | MC_1;
 
//****************************************
// Applicazione principale
//****************************************
 
// Loop infinito
while (1){
 
TA0CCR1 += 8;
 
if (TA0CCR1 > TA0CCR0) {
   TA0CCR1 = 0x00;
}
__delay_cycles(5000);
}
}
 

Esempio 4 : Misura della frequenza di un segnale digitale

   In questo esempio si riprende quanto già scritto nell'Esempio 2 che permette di segnalare un sistema in esecuzione, con l'aggiunta del controllo Capture per monitorare i fronti di discesa di un'onda quadra, in particolare si usa il Timer1 modulo CC1. Per mezzo del secondo Timer e del modulo Capture e Compare è possibile misurare il tempo che intercorre tra due fronti di discesa di un'onda quadra in ingresso al modulo, permettendo di rilevarne il periodo o la frequenza di quest'ultima. In particolare l'esempio permette di riconoscere la frequenza di 100Hz attivando il LED1 rosso. Il programma può essere facilmente cambiato per riconoscere altre frequenze e rappresenta una base su cui partire per applicazioni simili.

Per avere misure precise si fa uso del cristallo esterno da 32KHz, ma questo pone il vincolo di poter misurare frequenze non superiori a 10KHz. Formalmente il valore massimo o limite potrebbe essere fino alla frequenza di clock, ma per avere una minima risoluzione è bene non eccedere con la frequenza in ingresso. Infatti, la misura consiste nel misurare quanti periodi del nostro clock fornito dal cristallo da 32KHz entrano nel periodo del segnale che stiamo campionando. Si capisce che con questo principio, maggiore è la frequenza di riferimento rispetto al periodo dell'onda quadra in ingresso, migliore sarà la risoluzione con cui si effettua la misura. Facendo uso del valore calibrato di frequenza del DCO si possono avere errori dell'1%, che può arrivare fino al 3% considerando le possibili variazioni di temperatura e di tensione operative dell'MSP430 utilizzato. Per mantenere l'errore all'1% è comunque possibile effettuare una calibrazione della frequenza del DCO facendo uso del cristallo da 32KHz, in particolare la calibrazione permetterebbe di mitigare la variazione del valore calibrato originale, visto che verrebbe eseguita al valore di tensione e temperatura operativa effettiva. Con variazioni di temperatura di 5-10 gradi è sempre raccomandabile una nuova esecuzione del processo di calibrazione. Routine per effettuare la calibrazione del DCO facendo uso del cristallo da 32KHz si possono trovare tra gli esempi che vengono forniti con Code Composer Studio, ovvero nell'MSP430ware.

In ultimo si fa notare che la frequenza misurata con il metodo che segue, rappresenta una misura istantanea, infatti si determina la frequenza in base ad un solo periodo del segnale in analisi, mentre la definizione di frequenza sarebbe: il numero di periodi del segnale che si ripetono in un secondo (unità di tempo), per cui per misure reali di frequenza si dovrebbe fare una misura di 1s. Spesso per velocizzare le misure si fa uso di sotto intervalli temporali come 100ms.

Dopo questa digressione, vediamo qualche dettaglio legato alle scelte progettuali per questa applicazione.

Il programma inizia con il definire alcuni parametri ovvero valori di frequenza. Il valore delle costanti fanno riferimento al caso in cui la frequenza di riferimento sia 32KHz. In particolare un segnale da 100Hz ha un periodo di 0.01s per cui in questo intervallo sono presenti 328 periodi di clock generato a 32768Hz.

Il valore Frequency Accuracy serve per creare una piccola finestra all'interno della quale considereremo il segnale campionato ancora a 100Hz. Piuttosto che cambiare il valore della frequenza in più punti del programma, qualora volessimo rilevare frequenze diverse, il valore di riferimento è posto all'interno della costante FREQUENCY_THRESHOLD.

Dopo la definizione delle costanti, sono definite delle variabili globali. La necessità dell'avere delle variabili globali discende dal fatto che sono utilizzate sia dall'Interrupt Service Routine che dal programma principale. Inoltre dal momento che queste variabili possono essere variate da un Interrupt Service Routine, sono dichiarate volatile, per cui il compilatore non effettua nessuna ottimizzazione sulla gestione delle stesse.

La variabile measure_A rappresenta il valore del registro CC1 sul primo fronte di discesa. La variabile measure_B rappresenta il valore di CC1 sul secondo fronte di discesa. Dalla differenza dei due valori si calcola la frequenza, o meglio il periodo, dell'onda quadra in ingresso al modulo Capture e Compare, ovvero i periodi del clock da 32768Hz contenuti all'interno del periodo del segnale osservato. La variabile state, è una semplice variabile di stato per determinare se dobbiamo effettuare il calcolo della frequenza o meno, ovvero se entrambe le variabili measure_A e measure_B sono aggiornate.

Il programma continua in maniera molto simile all'Esempio 2 con la differenza che viene impostata anche la PORT2 visto che il pin P2.1 viene attivato come input per il modulo CC1 del Timer1. Successivamente si imposta il modulo relativo al Clock e il Timer0 come nell'Esempio 2 . Dopo l'inizializzazione del Timer0 si inizializza il Timer1 impostandolo in Continuous Mode e con il clock da 32KHz. Il modulo Capture e Compare utilizzato è CC1 ed è impostato in Capture Mode (il bit CAP è posto ad 1). Si sarebbe potuto utilizzare anche CC0 con il suo Interrupt Vector dedicato, ma per ragioni didattiche ho scelta CC1 in maniera da mostrare come, nel caso di Interrupt Vector condiviso, sia necessario controllare il bit dell'Interrupt e resettarlo. In applicazioni in cui il tempo di esecuzione è importatene ed eventi si possono susseguire molto rapidamente, è preferibile resettare il flag dell'Interrupt subito dopo averlo individuato. In queste applicazioni si potrebbe inoltre preferire CCR0 per la presenza del Vettore delle interruzioni dedicato, risparmiando di dover controllare e resettare il Flag d'Interrupt.

Una volta effettuate le inizializzazioni dei moduli e attivate le interruzioni, il programma entra in un loop infinito, interrotto solo dal verificarsi dei fronti di discesa nell'ingresso del nostro modulo Capture e Compare. Al verificarsi di una transizione valida del fronte, viene eseguita la relativa Interrupt Service Routine, all'interno della quale viene salvato il valore del registro CC1. In base al valore di state, si salva rispettivamente CCR1 in measure_A o measure_B. Il valore di state, viene controllato anche nel loop infinito del programma principale, in maniera da verificare se le due variabili measure_A e measure_B possiedono entrambe un valore valido. Quando questo si verifica, viene calcolato il valore di frequenza (o meglio periodo) e confrontato con il range impostato dalle costanti dichiarate ad inizio programma. Se il valore rientra nella finestra impostata viene attivato il LED1 rosso. Uscendo dall'intervallo prestabilito, il LED viene spento. Il programma non effettua controlli di validità sulle variabili measure_A e measure_B, in particolare se si dovessero verificare due o più overflow il valore non sarebbe valido, ma l'applicazione dell'esempio potrebbe riconoscere valori di frequenze pari a 100Hz anche per frequenze minori. Questo si verifica in caso di disturbi più lenti di 1Hz. Altri controlli che potrebbero essere implementati potrebbero essere il calcolo di un valore medio o l'inserimento di un time out nel quale si resettano i valori di measure_A, measure_B e state.

 

 
#include <msp430.h>
 
#define FREQUENCY_50_HZ 656
#define FREQUENCY_100_HZ 328
 
#define FREQUENCY_ACCURACY 5
 
#define FREQUENCY_THRESHOLD FREQUENCY_100_HZ
 
volatile unsigned int measure_A = 0;
volatile unsigned int measure_B = 0;
volatile unsigned int frequency = 0;
volatile unsigned char state = 0;
 
void main(void) {
 
// Stop watchdog timer
WDTCTL = WDTPW | WDTHOLD;
 
//****************************************
// Impostazioni Porte I/O
//****************************************
P1OUT = 0x00;
P1DIR = BIT6+BIT0;
P1SEL |= BIT6;
P1SEL2 &= ~BIT6;
 
P2OUT = 0x00;
P2DIR = 0x00;
P2SEL |= BIT1 ;
P2SEL2 &= ~BIT1;
 
//****************************************
// Impostazioni modulo Clock
//****************************************
 
BCSCTL2 = SELM_0 | DIVM_0 | DIVS_0;
BCSCTL1 |= XT2OFF | DIVA_0;
BCSCTL3 = XT2S_0 | LFXT1S_0 | XCAP_1;
 
//****************************************
// Impostazioni Timer0
//****************************************
 
TA0CCTL0 = CM_0 | CCIS_0 | OUTMOD_0 ;
TA0CCR0 = 32768;
 
TA0CCTL1 = CM_0 | CCIS_0 | OUTMOD_3 ;
TA0CCR1 = 32000;
 
TA0CTL = TASSEL_1 | ID_0 | MC_1;
 
//****************************************
// Impostazioni Timer1
//****************************************
 
TA1CCTL1 = CM_2 | CCIS_0| CAP |CCIE;
TA1CTL = TASSEL_1 | ID_0 | MC_2;
 
//****************************************
// Applicazione principale
//****************************************
 
__bis_SR_register(GIE);
 
// Loop infinito
while (1){
 
if (state == 0) {
 
if (measure_B > measure_A ) {
frequency = measure_B - measure_A;
} else {
frequency = (0xFFFF-measure_A) + measure_B;
}
 
if ((frequency > (FREQUENCY_THRESHOLD -FREQUENCY_ACCURACY)) &&
(frequency < (FREQUENCY_THRESHOLD + FREQUENCY_ACCURACY))){
 
P1OUT |= BIT0;
} else {
P1OUT &= ~BIT0;
}
}
}
}
 
//****************************************
// Timer1 CC1 Interrupt Service Routine
//****************************************
 
#pragma vector=TIMER1_A1_VECTOR
__interrupt void TIMER1_A1_ISR(void) {
 
if (TA1CCTL1 & CCIFG) {
 
TA1CCTL1 &= ~CCIFG;
 
if (state == 0) {
measure_A = TA1CCR1;
state++;
} else {
measure_B = TA1CCR1;
state = 0;
}
 
}
}

 

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

 

 

Gravatar
Mauro Laurenti
RE: MSP430: Utilizzo del Modulo Timer_A (Parte 2)
Se leggi le cose troppo velocemente mi rendi le cose complicate...perché a scriverle sono molto più lento! :)Certamente andrò avanti con il corso, ma sono momentaneamente fermo al fine di terminare la nuova versione del libro sui PIC18.Saluti,Mauro
0
Gravatar
Roberto Moncada
Nuovi capitoli del cordo
Complimenti per il tutorial...è fluido e scorrevole...e sopratutto ben scritto! l'ho letto tutto in un giorno!!! è prevista una continuazione???
0

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