Introduzione alla programmazione di microcontrollori (seconda parte) Giuseppe Dal Cero dcg@dei.unipd.it [Prima parte] [Terza parte] [Quarta parte]
Diamo uno sguardo ad un particolare mondo, quello dei microcontrollori, per capire cosa sono, come si usano e come si programmano, analizzando similitudini e differenze con la programmazione di PC. Questa volta vediamo come realizzare un piccolo progetto con un PIC16F84 in modo da prendere confidenza con gli aspetti pratici della programmazione di un microcontrollore. I PIC 16C84 e 16F84 Sono i 2 più diffusi microcontrollori della Microchip. L’unica differenza tra i due è che uno usa come memoria programma della EEPROM, mentre l’altro della flash, quindi dal punto di vista dell’architettura interna, della piedinatura e della stesura di programmi sono identici. La famiglia dei microcontrollori PIC è molto vasta e comprende 4 famiglie principali tutte RISC ad 8bit: la 12xx (con pochi piedini), la 15xx (low-range), la 16xx (mid-range) e la 17xx (high-range). Tra tutti comunque solo i 16x84 sono dotati di memoria non volatile riscrivibile, sono disponibili in formato DIP 18 pin perciò si possono usare in basette sperimentali o millefori ed infine possono essere programmati in-circuit. Tutte queste cose spiegano il motivo della loro enorme diffusione I PIC sono RISC nel vero senso della parola: i 16x84 hanno solo 35 istruzioni a lunghezza fissa (14 bit) che vengono eseguite quasi tutte in un ciclo che dura 4/(frequenza di lavoro in Hz) secondi: con una frequenza di lavoro di 4Mhz si arriva a 1 mips di potenza di calcolo ad 8 bit. L’architettura dei PIC è Harvard (cioè con bus separati per dati e istruzioni) e basata su un unico registro di lavoro denominato W(Working register) e array di SRAM vista come 36 registri. Sia i registri delle periferiche sia quelli SRAM sono divisi in 2 banchi e questo richiede switching attraverso un apposito bit dello status register. Lo stack è hardware ad 8 livelli e non controllabile da istruzioni del tipo PUSH o POP, così diventa impossibile usare tecniche quali la ricorsione o il multitasking. Le caratteristiche principali dei 16x84 sono: 36 registri SRAM ad 8 bit (64 nel caso del 16F84), 1000 istruzioni memorizzabili, 64 registri EEPROM per mantenere dati in maniera non volatile, 2 porte (5 pin porta A, 8 porta B), 1 timer ad 8 bit, 4 sorgenti di interrupt (pin INT, overflow del timer, cambio di stato sui pin 4-7 della porta B, scrittura in EEPROM avvenuta), 20mA erogabili da ciascun pin delle porte, watchdog timer. IL PRIMO PROGRAMMA PER PIC Quando si impara un nuovo linguaggio di programmazione il classico primo programma è quello che scrive sullo schermo un testo tipo "Ciao mondo!", ma di solito quando si ha a che fare con un microcontrollore non si ha a disposizione una periferica di visualizzazione alfanumerica. Inoltre noi vogliamo costruirci da soli anche la parte hardware, perciò il nostro primo progetto sarà qualcosa di estremamente semplice: un led da accendere. Scegliamo il PIC16F84 come microcontrollore da usare; lo schema elettrico, è semplicissimo e il circuito può essere realizzato in pratica su una basetta sperimentale, senza bisogno di realizzare un circuito stampato. Il PIC16F84 ha 4 modi di oscillazione: uno per realizzazione il circuito di generazione del clock con una resistenza ed un condensatore (RC), uno per quarzi a bassa frequenza, uno per quarzi a media ed uno per quarzi ad alta frequenza. Nel nostro caso, con un condensatore da 1nF e resistenza da 33Kohm, la frequenza di lavoro risulta di circa 6,7KHz. Adesso è venuto il momento di scegliere il linguaggio di programmazione da usare. Per il nostro semplice esempio va benissimo il C, tuttavia, come scritto nella scorsa puntata non è pensabile evitare di imparare l’assembly e l’architettura interna del microcontrollore da usare e questo per almeno due buoni motivi: il primo è spesso non si hanno a disposizione sistemi operativi o librerie di supporto che si occupino dell’interazione con l’hardware, il secondo è che l’architettura del microcontrollore può essere radicalmente diversa da quella del PC, come è appunto nel caso del PIC, che ha uno stack hardware rigido. Esistono vari compilatori C per PIC, tra i quali i più famosi sono l’MPLAB-C della Microchip e quello della HiTech. Per i nostri scopi scegliamo di usare il demo dell’HiTech C ( www.htsoft.com), perché è il compilatore più efficiente e di più facile reperibilità, comunque le problematiche che incontreremo sono comuni a tutti i compilatori, cambierà solo leggermente la soluzione e chi usa compilatori diversi non incontrerà particolari difficoltà a fare i dovuti adattamenti. L’Hitech C è, per quanto possibile, conforme allo standard ANSI C, mentre altri compilatori come l’MPLAB-C non lo sono: in particolare con quest’ultimo gli int sono a 8 bit e ci sono limitazioni negli argomenti delle funzioni. Per entrambi, comunque, non è posssibile ovviare ai limiti hardware dello stack del PIC e quindi non supportano la ricorsione. Con microcontrollori come il PIC il vantaggio di usare il C quindi non è tanto una facile potabilità, bensì una facile manutenzione di un programma a distanza di mesi dalla stesura iniziale. Per altri microcontrollori ci sono meno limitazioni e sono diffuse librerie e veri e propri sistemi operativi real-time multitasking scritti in assembly e ANSI-C. Il problema più grosso, in questo caso, è rappresentato dall’occupazione di memoria, che di solito è la risorsa più limitata e preziosa nell’ambito della programmazione di microcontrollori. Bene, vediamo allora come si presenta il nostro primo programma: #include <pic1684.h> #include "libxpic.c" void main() { inizializza(); /* inizializza l’hardware */ PORTB=1; /* accendi il led collegato al pin 1 della porta B */ while(1); /* loop infinito */ } L’header file incluso è quello fornito con il compilatore, vi sono definiti i nomi di alcuni indirizzi di registri hardware, come PORTB; libxpic.c invece contiene delle routine che useremo anche in altri programmi e che per questo sono state messe su un file a parte. Eccole: void aspetta(unsigned int nvol); void aspettabase(); void timeraspetta(unsigned char np, unsigned char nt); void inizializza(); void inizializza() { TRISB=0; /* porta B: tutti i pin come Output */ PORTB=0; } void aspettabase() { unsigned int ib; for(ib=0;ib!=0xff;ib++); } void aspetta(unsigned int nvol) { unsigned int i; for(i=0;i<nvol;i++) aspettabase(); } void timeraspetta(unsigned char np, unsigned char nt) { unsigned char mask; mask=OPTION; mask=mask&(0xF8); np=np&(0x07); mask=mask|np; /* setta il prescaler */ OPTION=mask; TMR0=nt; do { mask=TMR0; } while (mask!=0); } Questo piccolo programma da già l’idea di come si programmi un microcontrollore senza il supporto di un sistema operativo: a meno che non vi provveda il modulo di startup del compilatore occorre infatti ogni volta inizializzare l’hardware e alla fine non c’è alcun ambiente a cui tornare, perciò per evitare effetti imprevisti si termina il programma con un loop infinito. Notate come l’accesso ai registri hardware avvenga come per una normale variabile C, a differenza di quanto avviene con il PC in cui si usano le funzioni inp() e outp(): questo perché di solito nei microcontrollori l’hardware è memory-mapped e quindi non esistono istruzioni specifiche diverse da quelle per l’accesso alla memoria. Le funzioni per far passare il tempo utilizzano un ciclo e quindi il loro effetto dipende dalla frequenza a cui opera il PIC. L’Hitech C è fornito di un ambiente IDE in cui si possono scrivere i programmi: nella versione dimostrativa si invoca con hpdpic. Una volta scritto il programma si può avviare la compilazione con l’apposita voce dei menù. La prima volta verranno chieste le opzioni di compilazione (ottimizzazioni, processore target e formato dell’output): noi sceglieremo come target il pic16c84 e come formato di output Intel HEX. Se tutto è andato bene dovremmo avere un file .HEX pronto per essere scaricato su un PIC tramite un programmatore. Esistono molti programmatori per PIC, tutti comandati tramite parallela o seriale e di solito con un driver TSR per interfacciarsi ad un programma di gestione dalla programmazione standard: il PIP02 della Silicon Studio . Una soluzione che non fa uso del PIP02 è il programmatore parallelo presentato dalla rivista Progetto in occasione dell’inizio del suo corso sui PIC. Per chi non ne ha già uno, comunque, consiglio il programmatore seriale LUDIPIPO (LUDwIng PIc PrOgrammer, prelevabile su Inernet all’indirizzo:ftp.ai.uga.edu/pub/microcontrollers/pic/ludipippo.zip ) di semplice costruzione anche su basetta sperimentale. Supponendo di avere costruito il LUDIPIPO, di averlo collegato alla porta COM2 e di aver preparato un PIC16C84 da programmare si tratta di dare questi comandi dal DOS: com84 com2 (invio) pip02 (invio); a questo punto comparirà la schermata del programmatore e bisognerà settare il tipo di PIC (16C84 o 16F84 nel nostro caso) nel menu select/device, il file contenente il programma (primo.hex) nel menu file/load e la programmazione dei fusibili nel menu select/fuse word, infine si potrà programmare il chip con device/program. A proposito dei fusibili è importante ricordarsi di usare l’oscillatore RC e di disabilitare il watchdog timer, altrimenti il microcontrollore si bloccherà. Il watchdog timer (timer cane da guardia) è una particolare timer che serve a garantire un ripristino delle condizioni di esecuzione del programma in caso di blocco. Il timer raggiunge la condizione di time-out ogni circa 18ms e il programma deve reinizializzarlo periodicamente: se non lo fa il watchdog timer assume che il programma sia bloccato e resetta il microcontrollore. Alla fine della programmazione si potrà estrarre il microcontrollore e inserirlo nel circuito; una volta data tensione, se tutto ha funzionato correttamente, si dovrebbe vedere il led acceso. IL PRIMO PROGETTOVeniamo adesso al piccolo progettino di questo mese: 8 luci sequenziali alla KIT (l’automobile "intelligente" del telefilm Supercar). Il programma è una semplice estensione di quello visto prima: #include <pic1684.h> #include "libxpic.c" #define NLED 8 void main() { int n,val; inizializza(); /* inizializza l’hardware */ val=1; while(1) { for (n=(NLED-1);n;n--) { /* a sx */ aspetta(1); PORTB=val; val<<=1; } for (n=(NLED-1);n;n--) { /* a dx */ aspetta(1); PORTB=val; val>>=1; } } } Questo progetto realizza già qualcosa che può essere utile, ma chi è interessato a farlo potrebbe usare altri 5 led alimentandoli attraverso i 5 piedini della porta A. L’accesso a questa porta avviene attraverso i registri TRISA e PORTA, che hanno analogo funzionamento di quelli corrispondenti alla porta B, cioè il primo di configurare in uscita i bit a 0 ed in ingresso quelli a 1 ed il secondo è un latch contenente lo stato del livello logico dei piedini. ALTRI PASSIIl prossimo passo è quello di ricevere un input per rendere "interattivi" i programmi. Come detto sopra si tratterà di configurare alcuni pin come ingressi, dopodiché in C basta una normale assegnazione ad una variabile per prendere un’istantanea dello stato della porta. Il problema generalmente è che gli input provengono da qualche tasto od interruttore che apre o chiude e questo comporta il verificarsi di fenomeni transitori che causano disturbi: i cosiddetti rimbalzi. La conseguenza di ciò è che un pulsante può apparire premuto quando invece non lo è e viceversa e quindi bisogna tenerne conto e sviluppare un filtro software che annulli l’effetto. Ci sono varie maniere di implementare questo filtro: per esempio con un loop che prenda lo stato del pin finche non assuma per n volte consecutive lo stesso valore. Il numero n di volte dipende dal circuito che collega il pin al pulsante, dal pulsante stesso e dalla frequenza operativa del microcontrollore ed il modo più sicuro per determinarlo è quello di provare e riprovare. La porta B del PIC16x84 ha una particolare caratteristica che rende più facile l’interfacciamento di un pulsante: resistenze di pull-up interne. Infatti i tasti e gli interruttori non fanno altro che aprire o chiudere una linea elettrica, per esempio se la linea è quella di massa ad interruttore aperto il pin di ingresso che dovrebbe presentare un valore di tensione corrispondente all’uno logico è in realtà floating, cioè fluttuante dal momento che niente gli impone una determinata tensione. Per evitare ciò basta mettere nel circuito una resistenza tra il pin del microcontrollore e i +5V, così la tensione sarà determinata sia a circuito aperto che chiuso. E’ evidente quindi l’utilità delle resistenze di pull-up integrate nella porta B, soprattutto in situazioni in cui sia necessario un contenimento delle dimensioni del circuito. Per renderle attive bisogna resettare il 7° bit del registro OPTION, per esempio con un OPTION=OPTION&(0x8F); Il resto dei bit del registro OPTION riguarda principalmente il timer: bit 2-1-0: rappresentano un numero n da 0 a 7. 2^(n+1) è il valore del prescaler (divisore di frequenza) del timer (RTCC), 2^(n) è il valore del prescaler per il watchdog timer (WDT). In pratica se la frequenza di lavoro è di 8KHz un valore di n uguale a 3 implica che il timer avanza ogni 2^(4)/8000 secondi. bit 3: assegna il prescaler al timer se uguale a 0, altrimenti al WDT. bit 4: se uguale a 1 incrementa il timer nelle transizioni da alto a basso del pin PA4, altrimenti viceversa. bit 5: se uguale a 1 il timer conta le transizioni sul pin PA4, altrimenti avanza con la velocità di lavoro interna bit 6: se uguale a 1 l’interrupt viene generato sui fronti di salita, altrimenti su quelli di discesa. Il timer del PIC16x84 può dunque essere utilizzato sia per misurare il tempo sia per contare eventi esterni. Nel caso di lunghi intervalli di tempo è utile usare il prescaler, poiché il timer è a soli 8bit. Per esempio, per aspettare 1 secondo con una frequenza di lavoro di 6.7Khz si potrebbe settare il prescaler a 32 (quindi n=4) e il timer a 256-218=38. L’attesa sarà generata da un ciclo che continui a testare il timer (registro TMR0) finche non raggiunge lo zero. Anche così, comunque, non è possibile contare piu’ di alcuni secondi, soprattutto se si aumenta la frequenza di lavoro. La soluzione da adottare è simile a quella usata per la funzione aspetta, solo che questa volta sarà chiamata una funzione che usi il timer per aspettare un tempo fisso (per esempio 1 millisecondo). Sul CD trovate un semplice esempio d’uso del timer. Con queste informazioni dovreste essere in grado di creare vostre piccole applicazioni. Tenete presente che per fare moltiplicazioni e divisioni dovete includere, dopo il file pic1684.h, il file math.h e che se vi serve l’hitech C supporta addirittura il tipo di dato float. Buon divertimento! Download dei listati |