MSP430: Hello World

Valutazione attuale: 4 / 5

Dopo essere diventati degli esperti relativamente all'architettura MSP430 e agli strumenti necessari per iniziare la programmazione e sviluppo di applicazioni contenenti questi microcontrollori, iniziamo a programmare! Come ogni buon programmatore ben educato, iniziamo con il salutare il mondo. Un programma semplice ed essenziale che ci permetterà di compilare il nostro primo programma e vedere i primi risultati.

MSP430: Il nostro primo programma

Forti dell'esperienza dei capitoli precedenti non mi dilungherò molto sul come creare il progetto all'interno del nostro Workspace, se non dicendo di creare un nuovo progetto per MSP430 facendo uso dell'MSP430G2231 incluso nel LaunchPad.  Una volta creato un progetto vuoto, dovete copiare il file riportato sotto, all'interno del file main.c del vostro progetto. Dettagli sul programma saranno dati nel prossimo paragrafo.


#include <msp430.h>

void main (void) {


// Stop watchdog timer
WDTCTL = WDTPW + WDTHOLD;


// Imposta BIT0 come Output
P1DIR |= BIT0 + BIT6;

//Imposta  BIT0 a 1 e BIT6 a 0

P1OUT = 0x01;

int
i = 0;

while
(1) {
// Pausa

for (i = 0;  i <30000; i++);


// Inverte lo stato del bit
P1OUT ^= BIT0 + BIT6 ;
}

}

Una volta copiato il file, salvare e compilare premendo  il martello nella Tool bar. Il programma dovrebbe venir compilato senza problemi (a partire dalla versione di CCS 5.2 è presente il plug-in Ultra Low Power Advisor che si presenterà automaticamente dopo la compilazione, premere semplicemente avanti per proseguire).
A questo punto potete collegare la scheda LaunchPad al PC per mezzo di un cavo USB. Se è la prima volta che la collegate date tempo al PC di installare i driver associati alla periferica, permettendo a Windows di istallarli automaticamente. L'installazione dei driver avverrà con successo se CCS è stato installato prima di collegare la scheda LaunchPad al PC. Infatti CCS installa i vari driver delle schede di sviluppo della Texas Instruments oltre ai diversi programmatori supportati.  
Finita l'installazione della scheda LaunchPad è possibile procedere al caricamento del programma all'interno del microcontrollore, premendo il tasto della “cimice” verde. Questo tasto avvierà la compilazione e caricherà il programma nel nostro MSP430. Faccio notare che si sarebbe potuto premere direttamente questo tasto senza passare per la compilazione preventiva. La sola compilazione è utile per vedere se sono presenti degli errori senza dover inizializzare il nostro hardware (programmatore). A fine programmazione noterete che la videata di CCS cambia da Edit a Debug, come riportato in Figura 1.

 

Figura 1: Schermata di Debug Code Composer Studio.

Figura 1: Schermata di Debug di Code Composer Studio.

A questo punto si può eseguire il programma semplicemente premendo il tasto Play (nominato Resume) o il tasto Funzione F8. Il programma inizierà a salutare il mondo facendo accendere in maniera alternata il LED rosso e il LED verse. Faccio notare che il programma caricato nell'MSP430 rimane al suo interno anche una volta terminato CCS. Questo significa che attaccando semplicemente la scheda ad un altro PC o presa USB, per avere l'alimentazione, i LED inizieranno a lampeggiare. Allo stesso modo se si dovesse staccare il microcontrollore e inserire in una Breadboard, il programma continuerà a funzionare. Detto questo capirete che la scheda LaunchPad può essere utilizzata anche come programmatore. Le linee di programmazione e alimentazione sono rappresentate dai 5 Jumper presenti sul connettore J3. Facendo un prolungamento è possibile anche programmare direttamente un MSP430 su un'altra scheda o Breadboard (oltre alle linee del connettore J3 bisogna collegare la massa GND). Per i collegamenti richiesti per la programmazione rimando allo schema elettrico della scheda LaunchPad e all'application Note slau320.
 

MSP430: Vediamo cosa abbiamo scritto

A questo punto è bene fare un passo indietro e cercare di comprendere cosa abbiamo scritto. Il programma inizia includendo un file Header:

#include <msp430.h>

Avevo già detto che è necessario includere il file di heaeder che contiene le informazioni del dispositivo utilizzato. Quello incluso è però generico e in realtà non è quello che contiene le informazioni del dispositivo. Aprendo il file  (posizionare il cursore sul nome msp430 e premere il tasto destro del mouse e cliccare su “Open Decleration” o premere semplicemente il tasto Funzione F3).
Aperto il file vi renderete conto che al suo interno sono presenti tutti i file Header degli MSP430, tutti con sfondo grigio, tranne uno, ovvero quello associato all'MSP430G2231, che effettivamente viene incluso.

#elif defined (__MSP430G2101__)
#include "msp430g2101.h"
#elif defined (__MSP430G2001__)
#include "msp430g2001.h"
#elif defined (__MSP430G2231__)
#include "msp430g2231.h"
#elif defined (__MSP430G2221__)
#include "msp430g2221.h"
#elif defined (__MSP430G2131__)
#include "msp430g2131.h"


Quando viene creato il progetto e selezionato l'MSP430G2231, viene definita la “costante” __MSP430G2231__ per cui, per mezzo delle direttive #elif (ovvero else if) viene incluso il file msp430G2231.h . Questo file lo si sarebbe anche potuto includere direttamente nel nostro file main.c al posto del file msp430.h. Qualora non si abbiano le informazione del progetto, il tipo di MSP430 utilizzato non è noto, per cui potrebbe essere utile anche la pratica dell'includere il file header del dispositivo piuttosto che quello generico.
Aprendo il file msp430G2231.h è possibile trovare altre informazioni importanti sul dispositivo utilizzato, in particolare troviamo la definizione dei bit associati alle varie periferiche disponibili e definizioni utili  per la programmazione, come per esempio:

/************************************************************
* STANDARD BITS
************************************************************/

#define BIT0                   (0x0001)
#define BIT1                    (0x0002)
#define BIT2                   (0x0004)
#define BIT3                   (0x0008)
#define BIT4                   (0x0010)
#define BIT5                   (0x0020)
#define BIT6                   (0x0040)
#define BIT7                   (0x0080)
#define BIT8                   (0x0100)
#define BIT9                   (0x0200)
#define BITA                   (0x0400)
#define BITB                   (0x0800)
#define BITC                   (0x1000)
#define BITD                   (0x2000)
#define BITE                   (0x4000)
#define BITF                   (0x8000)


e i valori relativi alla calibrazione delle periferiche. I valori calibrati dipendono da dispositivo a dispositivo, per cui l'header file può essere utile per vedere quali valori sono disponibili (i valori sono riportati anche nel datasheet del microcontrollore utilizzato).
Oltre alle varie definizioni, viene incluso anche un file molto importante, ovvero:

#include "in430.h"

Aprendo il file troviamo le seguenti informazioni:

void _enable_interrupts(void );
void _disable_interrupts(void );
unsigned short _bic_SR_register(unsigned short mask);
unsigned short _bic_SR_register_on_exit(unsigned short mask);
unsigned short _bis_SR_register(unsigned short mask);
unsigned short _bis_SR_register_on_exit(unsigned short mask);
unsigned short _get_SR_register(void );
unsigned short _get_SR_register_on_exit(void );
unsigned short _swap_bytes(unsigned short src);
void _nop(void );
void _never_executed(void );


Queste rappresentano le cosiddette Intrinsic Functions e rappresentano delle funzioni offerte a supporto del programmatore. In particolare ogni funzione potrebbe anche essere implementata facilmente dal programmatore settando i vari bit dei registri ad eccezione della funzione swap che effettua un'operazione di manipolazione sul registro passato. Vedremo alcuni esempi di utilizzo di tali funzioni nei prossimi capitoli.
Oltre a queste funzioni sono presenti anche le definizione di alcune funzioni utilizzate in ambiente IAR. Questo viene fatto per rendere quanto più compatibili possibile i due ambienti di sviluppo.
Prima di andare oltre vi consiglio di leggere almeno una volta il file header dell'MSP430 utilizzato, al fine di avere una panoramica delle periferiche e del modo con cui vengono definite le varie constanti.

Il programma inizia con la funzione main, ovvero la funzione principale per un programma in C.
La prima istruzione è:

WDTCTL = WDTPW + WDTHOLD;

Questa istruzione consiste nel caricare due costanti nel registro WDTCTL. I registri che terminano con CTL rappresentano i registri di controllo, in particolare WDTCTL rappresenta il registro di controllo per il Watchdog. Si ricorda che il Watchdog rappresenta un Timer che viene incrementato automaticamente e  nel passare da 0xFFFF a 0x0000 genera un Reset della CPU. In particolare la sua funzione è quella di monitorare la CPU da eventuali stalli di software, infatti il Watchdog deve essere periodicamente resettato per mezzo del bit WDTCNTCL prima che giunga a 0 a causa di un overflow e venga generato un Reset. Se questo non dovesse avvenire, vorrebbe dire che il programma è entrato in stallo (probabilmente), per cui il Reset generato dal Watchdog permette di riavviare il microcontrollore. In molte applicazioni questo non serve, per cui può essere disattivato. Le ragioni che portano a disattivarlo sono principalmente due, non si ha bisogno di proteggere il software e per risparmiare energia (vita più lunga di una batteria). Per disattivarlo si deve scrivere quanto scritto sopra, ovvero settare il bit WDTHOLD (che mette in pausa il contatore). Per poter scrivere nel registro WDTCTL è necessario scrivere anche una password che è contenuta nella costante WDTPW (le costanti che terminano con PW rappresentano in generale delle password). Controllando l'header file (che saprete già a memoria)  potete verificare che la password è definita come:

#define WDTPW   (0x5A00)

Questo significa che il byte più significativo viene considerato come password. Se si legge il registro WDTCTL, anche dopo aver scritto la password, si ha che il byte più  significativo è pari a 0x69.

Vi sarete subito resi conto, per chi viene dal mondo dei microcontrollori PIC, che il Watchdog non viene disabilitato durante la fase di programmazione (usando dei pseudo fusibili che disattivano il timer) ma durante l'esecuzione del programma stesso. Dal momento che il Watchdog serve per garantire la sicurezza del programma in esecuzioni c'è chi preferisce attivare o disattivare il Watchdog in fase di programmazione, ed avere impossibilitata l'opzione di attivare/disattivare il Watchdog in fase di esecuzione del programma, visto che un malfunzionamento del programma potrebbe disattivare il Watchdog.

Gli MSP430 permettono di attivare/disattivare il Watchdog via software ma per garantire che non venga disattivato inavvertitamente, la scrittura del registro di controllo può avvenire solo previa scrittura della password. I critici dicono che questa procedura non è comunque sicura visto che la password è scritta nel codice stesso, per cui ci potrebbe essere anche una remota possibilità che la password venga utilizzata da una parte di codice impazzito e blocchi il Watchdog. Questo aspetto filosofico-probabilistico non è molto fondato, uno perché molto improbabile, secondo poiché in applicazioni ad alta affidabilità in cui l'applicazione debba essere monitorata, sia che il Watchdog sia attivabile solo durante la fase di programmazione o via Software, se si vogliono ottenere alti standard di sicurezza, si fa uso di Watchdog esterno (per esempio in ambito automobilistico). In applicazioni di questo tipo non è raro trovare due Microcontrollori che eseguono lo stesso compito in maniera ridondante (alcune volte si usano anche due Microcontrollori di marche diverse al fine di ridurre la probabilità di rottura di entrambi allo stesso tempo).  

Torniamo a noi. All'avvio dell'MSP430, il Watchdog è automaticamente attivato per cui bisogna disattivarlo qualora non se ne faccia uso.  Nel registro di controllo del Watchdog, è possibile impostare anche il clock da utilizzare per il Timer, come anche impostare il Watchdog per uso generico (semplice Timer). Oltre a queste funzioni, il registro di controllo permette di impostare se il pin RST/NMI debba essere RST o NMI (di default è impostato come RST). Qualora si selezioni la funzione NMI (Interruzione non mascherabile) è anche possibile impostare il fronte sul quale debba essere generata l'interruzione.    

L'istruzione successiva è:

// Imposta BIT0 come Output
P1DIR |= BIT0 + BIT6;


questa permette d'impostare il registro della direzione associato alla porta P1, alla quale sono collegati i LED del LauchPad. In particolare il LED rosso è collegato al pin P1.0 mentre il LED verde al pin P1.6 (si veda la serigrafia sul LaunchPad). I due bit per essere impostati come uscite devono essere impostati ad 1 (per i PIC si sarebbero dovuti impostare a 0). Per fare questo piuttosto che usare dei numeri si fa uso delle costanti BITx, definite all'interno del file Header e le si carica nel registro di direzione P1DIR (si capisce dunque che scrivere BIT0 imposta a 1 il bit 0). Di Default ogni pin è impostato come ingresso. Dopo aver impostato la direzione come uscita si inizializza il valore dei bit sulla porta di uscita, in particolare si accende il LED rosso e si spegne il LED verde, facendo uso della seguente istruzione.

//Imposta  BIT0 a 1 e BIT6 a 0
P1OUT = 0x01;

Terminata l'inizializzazione delle porte si può procedere al programma vero e proprio. Come verrà messo in evidenza nel prossimo capitolo, l'inizializzazione siffatta non è l'ottimale per minimizzare i consumi (infatti se eseguite Ultra Low Power Advisor, tra le varie obiezioni su Hello World,  dirà che le porte non sono propriamente inizializzate per minimizzare i consumi). La parte del programma che permette di far lampeggiare i LED è:

int i = 0;

while (1) {


// Pausa
for
(i = 0;  i <30000; i++);
// Inverte lo stato del bit


P1OUT ^= BIT0 + BIT6 ;

}

Ovvero un loop infinito composto da un while (1) all'interno del quale è inserita una pausa realizzata con un semplice for nullafacente (30000 operazioni a vuoto) seguita dall'inversione dei bit. Si noti che per invertire i bit si è fatto uso dell'operatore XOR sulla porta di uscita, in particolare facendo uso del formato compatto ovvero:

P1OUT = P1OUT ^ (BIT0 + BIT6) ;

è scritto:

P1OUT ^= BIT0 + BIT6 ;

Per rinfrescare la memoria, la Tabella della verità dell'operatore XOR è:

Tabella della verita' porta XOR


Detto a parole l'operatore XOR ha in uscita il valore 1 qualora o A o B valga 1 ma non quando entrambi valgono 1. Quindi se l'uscita della porta valesse 1 e viene messa in XOR con la costante BITx che vale 1 nella posizione del bit d'interesse, l'uscita viene posta a 0 (ovvero invertita). Al ciclo successivo facendo XOR tra l'uscita che vale 0 e la costante BITx, si ha che l'uscita viene posta ad 1, ovvero invertita nuovamente.

La pratica dello scrivere:

P1OUT ^= BIT0 + BIT6 ;

Devo dire che non mi fa impazzire, in particolare prima che prenderete l'abitudine ad usarla soffrirete di qualche fastidio! Probabilmente questa brutta pratica di programmazione discende dal fatto che gli MSP erano programmi inizialmente in Assembly e si è sempre cercato di ottimizzare il codice. Formalmente il la pratica dello scrivere

P1OUT ^= BIT0 + BIT6 ;

piuttosto che:

P1OUT = P1OUT ^ (BIT0 + BIT6) ;

Permette di ottimizzare il codice, almeno questo dice il programmatore che lo usa.

Ma se vediamo il codice assembly (menu Window -> Show View -> Disassembly)  vediamo che

P1OUT ^= BIT0 + BIT6 ;

è tradotto in:

E0F2 0041 0021 XOR.B   #0x0041,&Port_1_2_P1OUT

mentre

P1OUT = P1OUT ^ (BIT0 + BIT6) ;

è tradotto in:


E0F2 0041 0021 XOR.B   #0x0041,&Port_1_2_P1OUT

Che e' lo tesso!
Quindi il nostro aver ottimizzato il codice non è servito a nulla!
Infatti i compilatori odierni sono divenuti piuttosto “svegli” e riescono ad ottimizzare il codice senza doverglielo dire (ci sono inoltre delle impostazioni ad hoc per il compilatore per mezzo delle quali si possono dare alcune direttive di ottimizzazione al fine di ottimizzare velocità, consumi o dimensioni del codice compilato).
Ciononostante dal momento che in ambiente MSP430 tutti gli esempi fanno uso della forma compatta “ottimizzata” continuerò a usare tale forma.

Una domanda che spesso molti si chiedono è la ragione per cui Code Composer studio non supporti l'accesso dei registri per mezzo del bit field, ovvero poter scrivere qualcosa del tipo:

P1OUT.BIT0 = 0x01;

al fine di impostare ad 1 l'uscita BIT0 di P1OUT (cosa a cui ci si è abituati usando MPLAB e i microcontrollori della Microchip).

Quando feci questa domanda mi risposero per ottimizzare il codice.

Questo non è però sempre valido visto che un accesso del tipo

P1OUT.BIT0 = 0x01;

può essere facilmente cambiato dal compilatore in un'istruzione del tipo:

P1OUT = BIT0;

per cui la giustificazione per ottimizzare il codice non regge molto, soprattutto considerando che usare la tecnica del bit masking non è proprio una buona pratica di programmazione (rende il programma poco leggibile).  L'ambiente di sviluppo IAR supporta invece l'accesso ai registri SFRs (Special Function Registers) per mezzo del bit field, rendendo almeno all'inizio, il percorso di apprendimento un  po' più indolore.

I programmatori C avranno notato che la variabile i usata nel loop del delay è stata dichiarata prima del loop stesso. Questo dal punto della sintassi C è un errore, e se provate a compilare programmi con MPLAB mettendo la variabile i prima del while, piuttosto che subito dopo il main, avrete un errore di compilazione. Questo non avviene con Code Composer Studio poiché è un compilatore C/C++, ed in particolare il poter posizionare una variabile in qualunque parte del programma appartiene alla sintassi del C++. Questa viene considerata una buona pratica di programmazione, visto che la variabile viene dichiarata vicino al punto del suo utilizzo, rendendo più chiara la sua esistenza. Dal momento che questa regola non è accettata dai compilatori C, sebbene l'abbia usata a scopo dimostrativo, sconsiglio di utilizzarla al fine di mantenere il programma conforme all'ANSI C; ovvero tutte le variabili devono essere dichiarate subito dopo le parentesi graffe della funzione in cui vengono utilizzate.

 

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] : Application Note "MSP430 Programming Via the JTAG Interface"

 

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!