Nel precedente Capitolo abbiamo visto come salutare il mondo ed in particolare i vari file che vengono inclusi al fine di permettere una programmazione più confortevole. Sebbene il programma di esempio era piuttosto semplice, ha permesso di introdurre l'organizzazione generale di un programma per MSP430. In questo nuovo Capitolo affronteremo in maggior dettaglio le impostazioni associate al microcontrollore al fine di poter non solo scrivere dati in uscita, ma anche poter leggere dei dati, come per esempio la pressione di un pulsante. Si mostrerà inoltre con qualche dettaglio una corretta inizializzazione delle porte al fine di ridurre i consumi in applicazioni alimentate a batteria.
MSP430: Leggiamo la risposta del Mondo
Come per l'esempio precedente si utilizzerà la scheda LaunchPad con montato l'MSP430G2231. Create un nuovo progetto e copiate il sorgente sotto riportato, successivamente compilate ed eseguite il programma. Il semplice esempio permette di accendere il LED1 ogni qual volta venga premuto il pulsante S2.
#include <msp430.h>
void main(void) {
// Stop watchdog timer
WDTCTL = WDTPW + WDTHOLD;
// Imposta BIT0 come Output
P1DIR |= BIT0;
// Abilita resistore ingresso
P1REN |= BIT3;
// Pull-up resistor
P1OUT = BIT3;
while (1) {
if (P1IN & BIT3)
// Spengo LED1
P1OUT &= ~BIT0;
else
// Accendo LED1
P1OUT |= BIT0;
}
}
Vediamo come funziona il nostro esempio. Al fine di comprendere le varie impostazioni, dal momento che facciamo uso sia di linee di uscita che di ingresso, è bene avere sottomano lo schema elettrico del LaunchPad. In Figura 1 è riportato un dettaglio della parte associata al solo all'MSP430 montato sulla scheda, ovvero non considerando la parte del Programmatore-Debugger presente sulla scheda stessa (la documentazione completa si trova pubblicata nella User Guide del LaunchPad slau318).
Figura 1: Schema elettrico della scheda LauchPad.
Si noti che il LED1 è posizionato su P1.0 mentre il pulsante S2 è posizionato su P1.3 .
Il programma inizia disattivando il Watchdog, ovvero
// Stop watchdog timer
WDTCTL = WDTPW + WDTHOLD;
Successivamente si imposta il bit P1.0 come uscita, ovvero:
// Imposta BIT0 come Output
P1DIR |= BIT0;
Il pin P1.3 associato al pulsante, non viene toccato, poiché di default, dopo un PUC (Power Up Clear) i registri DIR vengono impostati per avere ogni pin come ingresso (inoltre BIT0 vale 00000001, per cui si ha il pin P1.0 come uscita e tutti gli altri pin come ingressi). Sebbene non sia necessario impostare P1.3 come ingresso è necessario inizializzare i resistori di pull-up, infatti il pulsante è normalmente aperto e alla chiusura vincola P1.3 massa. Per attivare il resistore di pull-up è necessario settare il relativo bit nel registro P1REN (Port 1 Resistor Enable), come riportato nel codice seguente.
// Abilita resistore ingresso
P1REN |= BIT3;
Come detto nel capitolo dedicato all'architettura degli MSP430, tali microcontrollori possiedono sia i resistori di pull-up che di pull-down, ed in particolare il registro PxREN permette solo di abilitarli e non di selezionare la tipologia del resistore, pull-up o pull-down. Per selezionare il tipo di resistore bisogna impostare P1OUT ovvero la porta di uscita. Infatti per i bit impostati come ingressi e per i quali è stato abilitato il resistore di pull-up/pull-down, la tipologia del resistore viene a dipendere dal relativo bit della porta di uscita. Impostare il bit a livello 1 attiva il resistore di pull-up mentre per avere il resistore di pull-down bisogna impostare il bit a 0.
Questo viene appunto fatto dall'istruzione successiva, ovvero:
// Pull-up resistor
P1OUT = BIT3;
A questo punto è importante ricordare che quando si ha a che fare con i resistori di pull-up/pull-down bisogna fare attenzione a quando si utilizza il registro PxOUT per scrivere dei valori sulle periferiche di uscita (per esempio per accendere i LED). Se si alterano anche i bit associati agli ingressi, e si cambia un resistore di pull-up in pull-down, il pulsante non verrebbe più letto. Per questo bisogna accedere ai registri facendo uso degli operatori logici (tecnica del bit masking), come si mostrerà a breve. Il cambiare un resistore di pull-up in pull-down, o viceversa, vi può creare dei bachi e rompicapi fastidiosi, soprattutto quando si è nuovi alla programmazione degli MSP430.
Il programma continua con un ciclo continuo in cui viene letto il pulsante, e a seconda del suo valore (premuto o meno) viene acceso o spento il LED1.
while (1) {
if (P1IN & BIT3)
// Spengo LED1
P1OUT &= ~BIT0;
else
// Accendo LED1
P1OUT |= BIT0;
}
Si noti che per leggere il bit 3, ovvero il pulsante, si pone in AND (operatore bitwise e non logico) la porta in ingresso P1IN e la costante BIT3 che vale in binario 00000100. Quando il pulsante non è premuto P1.3 vale 1 (per la presenza del resistore di pull-up). Quindi si ha che P1IN vale 00000100 (ignoro il valore degli altri bit). Facendo un AND con BIT3 si ha 00000100, per cui il controllo if viene verificato come vero, essendo maggiore di 0, e il LED1 viene spento (infatti il pulsante non è premuto).
Quando si preme il pulsante il valore di P1IN vale 00000000, che posto in AND con BIT3 genera 00000000, per cui il controllo if non viene verificato come vero e viene eseguito il blocco else, e viene acceso il LED1.
In questa descrizione, l'aver fatto l'AND con il BIT3 sembra non molto logico, visto che è presente un solo pulsante. Questo in realtà non è vero, visto che gli altri pin sono comunque posti come ingressi e potrebbero avere valori diversi da 0.
Si noti che per accendere e spegnere il LED1 sulla Porta 1 si è fatto uso degli operatori bitwise al fine di non alterare gli altri bit della Porta 1, ed in particolare i resistori di pull-up.
Se si fosse per esempio scritto:
while (1) {
if (P1IN & BIT3)
// Spengo LED1
P1OUT = 0x00;
else
// Accendo LED1
P1OUT = 0x01;
}
Il nostro esempio avrebbe funzionato per una sola pressione del tasto. Infatti una volta acceso il LED1, si avrebbe un resistore di pull-down sul BIT3 e il pulsante S2 non sarebbe più leggibile, non potendo più distinguere il caso di pressione dal caso di apertura, visto che in entrambi i casi si avrebbe 0.
Ammetto che il dover usare le maschere è un po' scoraggiante, ma come detto facendo uso di IAR è possibile accedere direttamente al singolo bit del registro (con un po' di abitudine diventerete degli esperti anche con il metodo del bit masking). Dunque l'approccio del bit masking non deve essere visto come un fastidio derivante dagli MSP430 ma dal compilatore. Infatti le porte di ingresso e uscita sono dei semplici registri, da leggere e scrivere, qualunque sia il microcontrollore, sia MSP430 che non. Il modo con cui è possibile accedervi dipende dagli strumenti che mette a disposizione il compilatore. Quando si programma in assembly il bit masking è il metodo per eccellenza, dato che normalmente non sono presenti alternative (a meno di non creare delle piccole macro).
Il programma appena scritto sebbene sia corretto non è in realtà ottimizzato per applicazioni Ultra Low Power. Infatti eseguendo il plug-in Ultra Low Power Advisor verranno messi in evidenza vari punti da modificare al fine di ridurre i consumi. In questo capitolo metterò in evidenza solo il fatto che un'ottimizzazione richiesta è nelle impostazioni delle porte (non tratterò ancora le interruzioni e le varie modalità a basso consumo).
All'avvio del microcontrollore viene a verificarsi un POR che a sua volta genera un PUC. Questo pone i registri relativi alle porte in stato di reset ovvero 0x00 (eccetto il registro di uscita e il registro relativo ai fronti delle interruzioni). Come abbiamo visto scrivere 0x00 nel registro PxDIR equivale a porre tutti i pin come ingressi. Questo rappresenta uno stato di sicurezza che permette di evitare dei cortocircuiti non voluti prima che l'applicazione dell'utente venga effettivamente eseguita (si ricorda infatti che prima dell'applicazione viene eseguito in generale il boot code interno all'MSP430).
In applicazioni in cui non tutti i pin vengano utilizzati, come per esempio nell'applicazione precedente, i pin non utilizzati devono essere propriamente impostati al fine di ridurre i consumi.
Per fare questo si hanno due possibilità, o si pongono i pin non usati come uscite o si impostano le uscite come input ma con un resistore di pull-up o pull-down.
Lo scopo in entrambi i casi è evitare che pin non usati cambino di stato logico, creando transizioni inutili, ovvero sprechi di energia. Ponendo un pin come uscita si garantisce in automatico che l'uscita avrà un valore fisso. Questa configurazione pero' alcune volte non piace poiché si perde il vantaggio di avere i pin come input che garantiscono una sicurezza in più rispetto eventuali cortocircuiti. Nel caso in cui si selezioni il pin come ingresso è necessario usare un resistore di pull-up o pull-down al fine di garantire che l'ingresso non rimanga fluttuante. Qualora sia fluttuante, a causa di rumore esterno si potrebbero verificare transizioni inutili che potrebbero aumentare i consumi. Questo sebbene sembri poco probabile, in realtà non lo è per quei pin che si trovano in prossimità di segnali variabili. Ho avuto modo di verificare diversi progetti con problemi di consumi anomali risolti semplicemente ponendo dei resistori di pull-up / pull-down nelle linee d'ingresso. Quanto appena detto non deve essere visto un problema degli MSP430 bensì un trucco o considerazione da applicare a qualunque microcontrollore. Nel caso dell'esempio precedente, dal momento che gli MSP430 possiedono resistori di pull-up/down interni, il codice lo si potrebbe riscrivere nel seguente modo.
#include <msp430.h>
void main(void) {
// Stop watchdog timer
WDTCTL = WDTPW + WDTHOLD;
// Imposta BIT0 come Output
P1DIR |= BIT0;
// Abilita resistori ingresso su P1
P1REN |= BIT1 + BIT2 + BIT3 + BIT4 + BIT5 + BIT6 + BIT7;
// Abilita resistore ingresso su P2
P2REN |= BIT0 + BIT1 + BIT2 + BIT3 + BIT4 + BIT5;
// Pull-up resistor
P1OUT = BIT1 + BIT2 + BIT3 + BIT4 + BIT5 + BIT6 + BIT7;
// Pull-up resistor
P2OUT = BIT0 + BIT1 + BIT2 + BIT3 + BIT4 + BIT5;
while (1) {
if (P1IN & BIT3)
// Spengo LED1
P1OUT &= ~BIT0;
else
// Accendo LED1
P1OUT |= BIT0;
}
}
Come visibile dal programma, rispetto al primo esempio, si sono solo impostati i registri PxREN e i registri PxOUT al fine di abilitare tutti resistori di pull-up. Tutto funziona come prima tranne per il fatto che questa volta il LED2 è lievemente acceso. Infatti P1.6 è stato impostato come ingresso ed ha un resistore di pull-up. La lieve accensione del LED 2 mostra la presenza del resistore di pull-up. Se non si volesse questo (e in generale non lo si vuole) si potrebbe impostare un resistore di pull-down (eliminare BIT6) o impostare P1.6 come uscita e porla a 0. In alternativa si può togliere semplicemente il Jumper del LED 2.
Sebbene negli esempi precedenti si sia trattato ogni pin come semplice Input o Output, in realtà ogni pin ha quasi sempre più funzioni condivise con vari moduli. Per decidere la funzione del pin è necessario impostare anche il valore del registro PxSEL (di default i pin sono impostati come semplici I/O). Oltre a questa possibilità gli MSP430 prodotti negli ultimi anni possiedono anche il cosiddetto Port Mapping in cui uno stesso pin può essere mappato su pin diversi a seconda delle esigenze di layout. Alcuni MSP430 possiedono anche un buffer di uscita al fine di pilotare carichi che richiedono maggiore corrente. Al fine di poter ridurre i consumi il buffer presente sui pin può essere attivato o meno per mezzo del registro PxDS ovvero Port Drive Strength. In generale bisogna far riferimento al datasheet per vedere se l'MSP430 scelto possiede o meno queste funzioni (presenti sulla famiglia MSP430F5xx)
MSP430: Filtro anti spikes
Sebbene negli esempi precedenti abbia mostrato un'applicazione semplice e funzionante, i pulsanti vengono letti generalmente in altro modo. Infatti un pulsante essendo un componente meccanico, al momento della sua chiusura e apertura crea molto “rumore” visibile con un oscilloscopio come una tensione con andamento irregolare prima di raggiungere il valore VCC o GND a seconda dei casi. Questo rumore può essere fonte di una mal interpretazione della pressione del pulsante che potrebbe essere rilevato come premuto/rilasciato più di una volta. Per evitare di rilevare una falsa lettura del pulsante si realizza spesso un filtro, frequentemente ottenuto con un semplice ritardo, per ridurre o eliminare questo problema. Il concetto del filtro, basato su un ritardo non fa altro che aspettare un po' di tempo dopo aver rilevato la pressione del pulsante e rileggere nuovamente lo stato del pulsante, ritenendo la seconda lettura come quella da interpretare valida. Con questo semplice filtro si eliminano anche i disturbi elettrici che si dovessero accoppiare sulla linea del pulsante. I tempi di ritardo del filtro sono generalmente di qualche decina di ms a seconda della “sensibilità” che si vuole avere. Tempi molto lunghi impongono che il pulsante rimanga premuto per un tempo prolungato. Un esempio in cui si applica un filtro anti spikes è riportato di seguito. Forse è leggermente più complesso degli altri ma a questo punto dovreste avere le basi per comprenderlo.
#include <msp430.h>
void main(void) {
int delay = 0;
// Stop watchdog timer
WDTCTL = WDTPW + WDTHOLD;
// Imposta BIT0 come Output
P1DIR |= BIT0;
// Abilita resistori ingresso su P1
P1REN |= BIT1 + BIT2 + BIT3 + BIT4 + BIT5 + BIT6 + BIT7;
// Abilita resistore ingresso su P2
P2REN |= BIT0 + BIT1 + BIT2 + BIT3 + BIT4 + BIT5;
// Pull-up resistor
P1OUT = BIT1 + BIT2 + BIT3 + BIT4 + BIT5 + BIT7;
// Pull-up resistor
P2OUT = BIT0 + BIT1 + BIT2 + BIT3 + BIT4 + BIT5;
while (1) {
// Attendo la rilevazione della pressione del pulsante
if ((P1IN & BIT3) == 0) {
for (delay= 0 ; delay < 10000; delay++);
if ((P1IN & BIT3) == 0) {
// Accendo LED1 se dopo la pausa il pulsante
//e' ancora premuto
P1OUT |= BIT0;
// Aspetta fino a quando il pulsante
// non viene rilasciato...
while ((P1IN & BIT3)== 0);
}
}
// Spengo LED1
P1OUT &= ~BIT0;
}
}
Si noti che in questo ultimo esempio ho posto P1.6 con un resistore di pull-down per cui il LED 2 rimane spento. Premendo velocemente il pulsante, la sua pressione viene ignorata ovvero considerata come accidentale o dovuta a rumore. In questo esempio la pausa per il filtro è stata scelta in maniera empirica visto che non abbiamo ancora fatto esempi con i Timer e non sappiamo ancora impostare il DCO interno al microcontrollore.
Sebbene questo esempio sia funzionale e lo usi spesso in altri esempi di programmazione, il suo utilizzo non è pratica comune. Infatti gli MSP430 sono spesso utilizzati in applicazioni Ultra Low Power ed una lettura in polling e loop di attesa per mezzo di cicli for, rappresentano un grande spreco di energia. Filtri più complessi fanno uso dei Timer interni e letture multiple mediando il valore delle letture (risultato di maggioranza). L'utilizzo degli interrupt e le modalità a basso consumo rappresentano la pratica comune, ma vedremo degli esempi nei prossimi capitoli.
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
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 |
devo vedere le impostazioni dei vari articoli.
Saluti,
Mauro
You don`t have permission to comment here!