Un economico kit di sviluppo di Freescale (ora NXP) permette di esplorare in maniera semplice e veloce le possibilità dei micro HCS08. La DEMO9S08QG8 è una scheda di sviluppo a basso costo dedicata al microcontrollore MC9S08QG8 che mette a disposizione tutto il necessario per iniziare a sviluppare delle applicazioni per questo microcontrollore e per testarle. Ecco una serie di esperimenti utili per conoscerli e iniziare ad usarli.
LA DEMO BOARD HC08
La scheda monta un debugger integrato (BDM) accessibile via USB che permette di programmare il microcontrollore ed eseguire il debug in-circuit dei programmi. Con la scheda sono forniti anche un cavo USB 2.0 A/B, il software di sviluppo CodeWarrior Development Studio e la documentazione sull’hardware e sul software. La scheda, visibile in figura 1, ha dimensioni molto contenute (circa 6x7,5cm) ma alloggia un discreto numero di componenti e periferiche utili per testare le funzionalità del microcontrollore e la correttezza dei programmi scritti.
Più in dettaglio la scheda monta i seguenti componenti accessibili dal microcontrollore:
- 2 pulsanti (SW1, SW2), più uno per il reset;
- 2 LED (LED1, LED2);
- un potenziometro (RV1);
- un sensore di luce (RZ1);
- un’interfaccia RS-232;
- un oscillatore esterno (opzionale);
- alcuni jumper di configurazione;
Il collegamento tra i vari dispositivi ed il microcontrollore è mostrato in figura 2 (sarà utile riferirsi a questo schema per comprendere il codice che verrà presentato nei seguenti paragrafi).
Sulla scheda sono presenti inoltre un connettore a 32 pin (passo 2,54mm), che permette di collegare ulteriori dispositivi o interfacciare la scheda ad un sistema esterno e il debugger integrato P&E USB-BDM, che verrà descritto meglio in seguito. L’interfaccia USB, utilizzata per la programmazione del micro e per il debug, fornisce anche l’alimentazione alla scheda (è presente comunque anche un connettore per l’alimentazione esterna).
IL MICRO MC9S08QG8
Come già detto la Demo Board monta un microcontrollore MC9S08QG8. Questo dispositivo fa parte dell’ampia famiglia di microcontrollori ad 8 bit 68HC08 di Motorola/Freescale. In realtà il dispositivo è basato sulla evoluzione HCS08, che pur mantenendo le principali caratteristiche architetturali e la compatibilità di codice con l’originario core, aggiunge alcune istruzioni e funzionalità dedicate al debug ed alla riduzione del consumo di potenza. Per chi non avesse familiarità con la serie HC08 è il caso di ricordare che si tratta di microcontrollori tipicamente CISC (sullo stile di 8051 o Z80), quindi caratterizzati da istruzioni e modi d’indirizzamento abbastanza complessi, basati sull’uso di un accumulatore, di registri indice e di uno stack pointer esplicito e dotati di supporto per le interruzioni vettorizzate. Queste caratteristiche li rendono particolarmente adatti ad essere programmati in C e ad essere impiegati in campi i cui è necessario eseguire complessi algoritmi di controllo (più che di elaborazione veloce di segnali). Nonostante le ridotte dimensioni del package degli MC9S08QG8 (8 o 16 pin), il microcontrollore integra oltre a 8KB di Flash per codice/dati e 512 byte di RAM, una nutrita serie di periferiche, che confermano l’orientamento di questo dispositivo verso applicazioni di controllo. Le periferiche integrate, alcune della quali verrano utilizzate negli esempi seguenti, sono:
- 12 porte di I/O programmabili
- ADC a 8 canali, 10 bit
- Comparatore analogico (ACMP)
- Interfaccia UART
- Interfaccia IIC ed SPI
- Modulo PWM a due canali
- Timer ad 8 bit (MTIM)
- Real Time Clock per interruzioni periodiche (RTI)
- Interfaccia per pulsanti/tastiere (KBI)
- Generatore di clock interno con FLL
- Watchdog e debugger in-circuit
Ciascuna di queste periferiche può essere configurata in maniera estesa e funzionare in diverse modalità. Il modulo di debug integrato merita un approfondimento. Questo dispositivo permette di controllare il microcontrollore attraverso un solo pin e non richiede l’utilizzo dei classici programmi “monitor” da aggiungere al proprio codice. Utilizzando il debugger integrato è possibile effettuare le tipiche operazioni di lettura e scrittura dei registri e della memoria, lo stepping, il tracing e l’inserimento di break-point complessi (con trigger a doppio livello). La cosa interessante è che alcune di queste operazioni possono essere eseguite in maniera totalmente non invasiva, cioè mentre il programma è in esecuzione e senza perturbarlo minimamente. Questo permette di eseguire le simulazioni del programma direttamente sul dispositivo (In-Circuit Debugging).
L’AMBIENTE DI SVILUPPO
Con la Demo Board viene fornito un cofanetto di CD che contiene l’ambiente di sviluppo CodeWarrior. Si tratta di un ambiente molto completo ed estremamente potente che ingloba tutto quello che normalmente è necessario nello sviluppo di applicazioni embedded. In particolare nel sofisticato ambiente grafico sono inglobati il compilatore ANSI/ISO C/C++, l’assembler, il linker ed il debugger, più altri strumenti avanzati. Nonostante la complessità dell’ambiente di sviluppo riuscire a creare un progetto, compilarlo, caricarlo nel microcontrollore ed eseguire il debug è relativamente semplice. Occorre però un po’ di pratica per iniziare a prendere confidenza e ad apprezzare gli strumenti più avanzati che CodeWarrior mette a disposizione. Per creare un nuovo progetto è sufficiente selezionare la voce New dal menu File e quindi l’opzione Project Wizard. Occorrerà quindi rispondere alle richieste delle varie finestre secondo le proprie esigenze. In particolare per provare i codici riportati di seguito sono state scelte le seguenti opzioni: microcontrollore MC9S08QG8, linguaggio C, nessuno utilizzo del “Processor Export” e del “PC-lint” (occorrono licenze specifiche), start-up code di tipo minimale o ANSI (è indifferente), nessun supporto del floating point, modello di memoria Tiny o Small (è indifferente), debug con Full Chip Simulator e Hardware Debugging (il primo permette la simulazione del dispositivo su PC, il secondo il debugging in-circuit e la programmazione). A questo punto verrà creata una sezione con i dettagli del progetto sulla sinistra e l’area per editare il codice sulla destra (visibile in figura 3).
Come si può notare nella finestra sulla sinistra sono raggruppati in maniera molto ordinata ed analitica i vari elementi utilizzati per compilare il programma, ed i file ausiliari utilizzati ad esempio per il debug. è possibile modificare manualmente tutti questi files, utilizzando tra l’altro gli strumenti avanzati per l’editing di testo/codice messi a disposizione del programma (che non saranno approfonditi qui per questioni di spazio). Una volta scritto e compilato il codice con il comando Make (selezionare prima il target P&E ICD nella finestra del progetto) sarà possibile scaricarlo nel microcontrollore ed eseguire il debug, selezionando la voce di menu o il pulsante Debug. Verrà aperta la finestra del debugger e quella del programmatore, che dopo avere chiesto conferma completerà il lavoro in pochi secondi. Dalla finestra di Debug (figura 4) sarà possibile avviare il programma sul target, ed eseguire tutte le azioni di debug descritte in precedenza.
Prima di passare alla descrizione di alcuni test da eseguire scheda, è il caso di ricordare che per attivare completamente le funzionalità di CodeWarrior è necessario installare un’apposita licenza. Questo file, chiamato License.dat può essere scaricato gratuitamente dal sito di Freescale/NXP (non è necessario richiederla via e-mail come spiegato nel manuale).
ALCUNI ESPERIMENTI
Una prima prova da fare, utile per prendere confidenza con l’ambiente di sviluppo e con la scheda e per accertarsi che tutto funzioni e sia configurato correttamente è il classico test di lampeggio di un LED. Data la sua semplicità il programma dovrebbe funzionare immediatamente se abbiamo fatto tutto correttamente. Il codice è riportato nel listato 1.
/***************************************************************************** * * Note: To be used on a MC9S08QG8 demo board. * LED and SW are active low. * Created using CodeWarrior 3.1 for HC(S)08. *****************************************************************************/ #include <MC9S08QG8.h> /* include peripheral declarations */ void main(void) { int cnt; //System init SOPT1_COPE = 0; //Disable watchdog ICSC2_BDIV = 3; //Bus freq. div = 8 //GPIO init PTBD_PTBD6 = 1; //LED1 off PTBDD_PTBDD6 = 1; //Set PTB6 as an output /* Infinite loop */ while(1) { //Toggle LED1 PTBD_PTBD6=~PTBD_PTBD6; //Delay for(cnt=0; cnt<10000; cnt++){ } } }
Listato 1 |
Il programma fa lampeggiare il LED1 invertendo continuamente lo stato del pin PTB6, configurato come uscita, ed utilizzando una routine di ritardo software (un semplice ciclo for vuoto). All’inizio viene impostata una frequenza del bus interno di circa 1MHz (gli 8MHz generati dall’FLL interno vengono divisi per 8) e viene disabilitato il watchdog (COP). Tutte le definizioni dei registri e dei loro campi sono dichiarate nel file header MC9S08QG8.h, che è stato incluso all’inizio (e che viene fornito con l’ambiente). Seguendo i passi descritti prima dovrebbe essere possibile compilare, caricare ed avviare il programma sul micro senza problemi.
Uso del timer e delle interruzioni
Vogliamo provare adesso a fare lampeggiare il secondo LED utilizzando il modulo MTIM (timer ad 8 bit) del micro. In particolare il timer verrà utilizzato per temporizzare il lampeggio utilizzando il meccanismo delle interruzioni. Ad ogni overflow del timer verrà richiamata l’apposta funzione di servizio (ISR), che provvederà a invertire lo stato del pin. Il timer è incrementato alla frequenza del bus (circa 1MHz in questo esempio) e dispone di un prescaler, in questo caso impostato per dividere per 256. La frequenza di overflow dovrebbe quindi essere di circa 15Hz (occorrono 256 cicli per avere l’overflow). La routine d’interruzione è dichiarata specificando nella funzione la keyword interrupt ed il numero di vettore corrispondente (12 per il modulo MTIM). La routine provvede soltanto a cancellare il flag di overflow e ad invertire lo stato del pin. Il resto del programma è identico al precedente (il LED1 continuerà a lampeggiare con il ritardo software). Il programma è visibile nel listato 2.
/***************************************************************************** * *****************************************************************************/ #include <hidef.h> /* for EnableInterrupts macro */ #include <MC9S08QG8.h> /* include peripheral declarations */ void main(void) { int cnt; EnableInterrupts; /* enable interrupts */ //System init ICSC2_BDIV = 3; //Bus freq. / 8 SOPT1_COPE = 0; //Disable watchdog //GPIO init PTBD_PTBD6 = 1; //LED1 off PTBD_PTBD7 = 1; //LED2 off PTBDD_PTBDD6 = 1; //Set PTB6 as an output PTBDD_PTBDD7 = 1; //Set PTB7 as an output //Modulo Timer init MTIMCLK_PS = 8; //Prescaler: /256 MTIMCLK_CLKS = 0; //Clock source: ICS MTIMMOD = 0; //Modulo: 0 (off) //Reset and start MTIM + enable IRQ MTIMSC = MTIMSC_TRST_MASK|MTIMSC_TOIE_MASK; /* Infinite loop */ while(1) { //Toggle LED PTBD_PTBD6=~PTBD_PTBD6; for(cnt=0; cnt<10000; cnt++){ } } } /*** MTIM ISR ***/ interrupt 12 void MTIM_ISR(void) { MTIMSC_TOF=0; //clear TOF PTBD_PTBD7=~PTBD_PTBD7; //toggle Port }
Listato 2 |
Uso del Keyboard Controller
È possibile associare ai pin della porta B del micro la possibilità di generare delle interruzioni in corrispondenza di cambiamenti specifici. Questa funzione è particolarmente adatta a gestire pulsanti o tastiere. Vogliamo provare a utilizzare il pulsante SW1 per fermare e riavviare il timer nel programma visto prima, in modo da fermare o riattivare il lampeggio del LED2 ad ogni pressione. Per fare questo è sufficiente aggiungere poche linee al codice visto prima. Occorre inizializzare propriamente il modulo KBI, in modo che possa generare interruzioni in corrispondenza alla pressione di SW1 e scrivere la ISR corrispondente in modo che neghi il bit di stop del timer. Le linee di codice da aggiungere sono riportate le listato 3.
// — Inserire nel main, prima del loop infinito — //KBI init (for SW1) KBIPE_KBIPE2 = 1; //Enable Keyboard Pin KBISC_KBIE = 1; //Enable Keyboard Interrupts KBISC_KBACK = 1; //Clear Pending Keyboard Interrupts PTAPE_PTAPE2 = 1; //Enable Pullup for Keyboard pin … // — Inserire fuori dal main — /*** KBI ISR ***/ interrupt 18 void KBI_ISR(void) { KBISC_KBACK = 1; //clear IRQ flag MTIMSC_TSTP=~MTIMSC_TSTP; //Start/Stop MTIM }
Listato 3 |
Uso del Comparatore Analogico
Il modulo ACMP può essere utilizzato per confrontare un segnale analogico applicato al piedino PTA1 con un altro applicato a PTA0, oppure con un riferimento interno, al fine di ottenere un valore binario che indica se uno è maggiore dell’altro. Sulla Demo Board questi due piedini sono collegati al potenziometro RV1 e al foto-transistor RZ1, quindi è possibile confrontare le tensioni fornite da questi due dispositivi, oppure l’uscita di RZ1 con il riferimento interno. Nel listato 4 il risultato del confronto viene utilizzato per accendere o spegnere il LED1.
/***************************************************************************** * *****************************************************************************/ #include <hidef.h> /* for EnableInterrupts macro */ #include <MC9S08QG8.h> /* include peripheral declarations */ /*** main ***/ void main(void) { EnableInterrupts; /* enable interrupts */ //System init ICSC2_BDIV = 3; //Bus freq. / 8 SOPT1_COPE = 0; //Disable watchdog //GPIO init PTBD_PTBD6 = 1; //LED1 off PTBDD_PTBDD6 = 1; //Set PTB6 as an output //ACMP init ACMPSC_ACME = 1; //Comparator on ACMPSC_ACBGS = 1; //Compare with bandgap ACMPSC_ACIE = 1; //IRQ enable ACMPSC_ACMOD = 3; //Fall&Rise edge /*** Infinite loop ***/ while(1) {} } /*** ACMP ISR ***/ interrupt 20 void ACMP_ISR(void) { ACMPSC_ACF = 0; //clear IRQ flag PTBD_PTBD6 = ACMPSC_ACO; //Turn on/off LED1 }
Listato 4 |
In particolare è stata utilizzata l’interruzione generata dal modulo ACMP in corrispondenza dei cambiamenti di stato. Nella ISR viene cancellato il flag di interruzione e viene assegnato all’uscita (LED1) proprio il valore del comparatore. Avviando il programma il LED1 si accenderà quando la quantità di luce rilevata da RZ1 scenderà sotto una certa soglia (si provi ad esempio a coprire il sensore con un dito). In questo caso la soglia è determinata dal riferimento interno, che è stato selezionato impostando ACMPSC_ACBGS a 1. Se si assegna 0 a questo campo il confronto verrà eseguito con la tensione presente su PTA0, cioè quella fornita dal trimmer. In questo caso sarà possibile variare manualmente la soglia.
Test dell’ADC
Per provare il convertitore analogico/digitale (ADC) è possibile utilizzare uno dei due segnali analogici presenti sulla scheda, ossia il segnale proveniente dal sensore ottico RZ1 o quello proveniente dal trimmer RV1. Sarà possibile selezionare il canale desiderato impostando il suo valore nel registro apposito (il campo ADCH di ADCSC1). Nel programma proposto non sono state utilizzate le interruzioni dal momento che le operazioni eseguite hanno tempi abbastanza rilassati. La maggior parte di registri relativi all’ADC sono impostati con i valori di default, soltanto il registro ADCSC1 ed il registro ADCRL verranno utilizzati per selezionare il canale, avviare la conversione e per leggere il valore convertito. La conversione eseguita in questo modo risulta ad 8 bit, avviata via software e sincronizzata dal clock del bus. Il programma in pratica utilizza il valore letto dall’ADC per regolare la velocità di lampeggio del LED1. Il valore letto dall’ADC, moltiplicato per 32, viene utilizzato come limite di conteggio del loop di ritardo. Più è grande il valore letto, più lunga sarà l’attesa. Proprio per il fatto che tra l’inizio della conversione e la lettura del valore c’è il loop di ritardo, non è necessario eseguire il polling per controllare se la conversione è completa (dovrebbe esserlo sempre). Il codice del programma è riportato nel listato 5.
/***************************************************************************** * *****************************************************************************/ #include <MC9S08QG8.h> /* include peripheral declarations */ /*** main ***/ void main(void) { int cnt, lim; //System init ICSC2_BDIV = 3; //Bus freq. / 8 SOPT1_COPE = 0; //Disable watchdog //GPIO init PTBD_PTBD6 = 1; //LED1 off PTBDD_PTBDD6 = 1; //Set PTB6 as an output lim=1000; /*** Infinite loop ***/ while(1) { ADCSC1_ADCH=0; // Sel channel/start ADC // Ch. 0 = Pot RV1 // Ch. 1 = Sensor RZ1 // Ch. 26 = temp sensor PTBD_PTBD6=~PTBD_PTBD6; //Toggle LED //Software delay for(cnt=0; cnt<lim; cnt++){ } lim=(ADCRL<<5); //Read from ADC and x32 } }
Listato 5 |
Si può notare che cambiando il valore assegnato ad ADCH è possibile selezionare differenti canali dell’ADC, in particolare il valore 0 seleziona il trimmer RV1, mentre altri valori selezionano gli altri piedini, i riferimenti di tensione o il sensore di temperatura interno del micro. Quest’ultima possibilità può risultare utile in alcune applicazioni sul campo, ma bisogna ricordare che il sensore in questione non è molto preciso e risente molto delle resistenze e della capacità termica del package.
Prova UART ed RTI
Come ultimo esperimento con la Demo Board HC08 presentiamo un’applicazione abbastanza realistica: un semplicissimo datalogger. Il programma utilizza il generatore di interruzioni Real-Time per campionare un valore analogico ad intervalli regolari e per inviarlo ad un computer via RS-232. La periferica UART (che nel datasheet è chiamata SCI, Serial Communication Interface) viene programmata per funzionare ad una velocità di 2400 baud, ed in modalità polling (interruzioni TX ed RX disabilitate), ed è usata soltanto per trasmettere i dati. La periferica RTI invece è programmata in modo da generare un’interruzione ogni 256 ms. Il loop principale del programma è vuoto e le uniche funzioni svolte sono contenute nella ISR relativa all’RTI. In questa routine viene cancellato il flag d’interruzione, invertito lo stato del LED1, inviato il dato al PC attraverso la UART e avviata la nuova conversione dell’ADC. Per convertire il dato binario in una stringa esadecimale viene utilizzata la routine bin2asc, mentre l’invio di tutti i caratteri ASCII è gestito dalla funzione SendMsg. Il canale ADC da convertire è impostato tramite la macro CH, in questo caso posta uguale a 0 (trimmer) ma facilmente modificabile. Il codice è riportato nel listato 6.
#include <hidef.h> /* for EnableInterrupts macro */ #include <MC9S08QG8.h> /* include peripheral declarations */ // Set ADC channel #define CH 0 // *** Function prototypes *** void SendMsg(char *msg); char * num2asc(byte n); char n_str[8]; //Hex string /*** main ***/ void main(void) { EnableInterrupts; /* enable interrupts */ //System init ICSC2_BDIV = 3; //Bus freq. / 8 SOPT1_COPE = 0; //Disable watchdog //GPIO init PTBD_PTBD6 = 1; //LED1 off PTBDD_PTBDD6 = 1; //Set PTB6 as an output //UART init SCIBD = 1000000/16/2400; // Baud rate divider SCIC1_M = 0; //Mode: start+8 bit+stop SCIC2_RIE = 0; //RX IRQ off SCIC2_TCIE = 0; //TC IRQ off //RTI init SRTISC_RTIS = 5; // RTI clk div: 256ms SRTISC_RTIE = 1; // RTI IRQ enable ADCSC1_ADCH = CH; //Start first ADC conversion /*** Infinite loop ***/ while(1) {}; } // *** RTI ISR *** interrupt 23 void RTI_ISR(void) { SRTISC_RTIACK = 1; //clear IRQ flag PTBD_PTBD6=~PTBD_PTBD6; //Toggle LED SendMsg(num2asc(ADCRL)); //TX value ADCSC1_ADCH=CH; //Start next conversion } // Send a string via RS-232 void SendMsg(char *msg) { byte ix=0; // String pointer byte dummy; // dummy var for reading SCIS1 byte nxt_char; SCIC2 = 0x08; // enable Tx dummy = SCIS1; // 1st half of TDRE clear procedure nxt_char = msg[ix++]; while(nxt_char != 0x00) { SCID = nxt_char; // 2nd half of TDRE clear procedure nxt_char = msg[ix++]; while(!SCIS1_TDRE){ }; } while(!SCIS1_TC){}; SCIC2_TE = 0; } // Convert a byte to an hex string char * num2asc(byte n) { n_str[0] = (n>>0x04)+0x30; // convert MSN ‘n’ to ascii if(n_str[0]>0x39) // if MSN is $a or larger... n_str[0]+=0x07; // ...add $7 to correct n_str[1] = (n&0x0f)+0x30; // convert LSN ‘n’ to ascii if(n_str[1]>0x39) // if LSN is $a or larger... n_str[1]+=0x07; // ...add $7 to correct n_str[2] = ‘\r’; n_str[3] = ‘\n’; n_str[4] = 0x00; // add null return n_str; }
Listato 6 |
Per visualizzare i dati sul PC è possibile utilizzare qualsiasi programma in grado di ricevere dati dalla porta seriale (va bene anche l’HyperTerminal di Windows), impostando 2400 baud, 1 bit di start, 8 bit dati, 1 bit di stop, nessuna parità e nessun controllo di flusso come mostrato in figura 5.
La velocità di trasmissione può essere anche maggiore di 2400 baud, però occorre fare i conti con la precisione della frequenza che si ottiene dividendo per un fattore intero la frequenza del bus, e con la precisione e la stabilità intrinseca di quest’ultima (che ricordiamo è generata da un riferimento interno oscillante a circa 32KHz). È possibile ovviare a questo inconveniente agendo sul registro di tuning dell’oscillatore interno. Per maggiori dettagli su questi aspetti è possibile consultare il datasheet del microcontrollore e studiare il codice di uno dei due esempi forniti con la scheda (DEMO9S08QG8_APP), che è stato appositamente pensato per esercitare tutti di dispositivi di cui è dotato il microcontrollore.
Conclusione
Gli esempi presentati dovrebbero fornire un’idea delle possibilità offerte dalla scheda di sviluppo, delle caratteristiche del potente ambiente di sviluppo della Metrowerks e delle potenzialità dei microcontrollori HCS08 di Freescale/NXP. I programmi presentati inoltre possono essere considerati un utile punto di partenza per chi decidesse di avvicinarsi all’utilizzo ed alla programmazione di questi dispositivi e possono essere utilizzati come template per iniziare a scrivere i propri.
Nell’ambito del micro MC9S08QG8 consiglio anche questo interessante articolo http://it.emcelettronica.com/zero-crossing-detection-con-freescale-mc9s08qg8