MENU

LINKS

INFO

 


Programmazione 19/11/2007

2. Giochiamo con i LED di Roberto

Primo esempio di programma in C18

Cominciamo con qualcosa di semplice accendere dei LED è un po' il classico "Hello World" per le MCU. Lo schema utilizzato è quello in figura, come potete vedere è quello minimo per il funzionamento dei PIC, ma analizziamolo nel dettaglio per chi non ha pratica con queste MCU.


Per prima cosa abbiamo l'alimetazione che deve essere a 5Volt sul pin 20 (VDD) del 18F2620, sul pin 19 (VSS) invece abbiamo il collegamento a massa, il più vicino possibile a questi due pin troviamo C1 condensatore da 100 nano Farad, utilizzato per filtrare l'alimentazione della MCU e scongiurare spike che potrebbero causare disturbi e mal funzionamenti. Anche il pin 8 (VSS) va collegato a massa.
Passiamo poi alla sezione del quarzo, che va collegato tra i pin 9 e 10 (OSC1 e OSC2), ognuno dei suoi capi va poi messo a massa tramite un condensatore (C2 e C3) da 22 pico Farad. Il quarzo è da 10MHz, ma come vedremo analizzando il codice, la frequenza viene ulteriormente tagliata da un circuito interno del PIC portando la frequenza di lavoro interna della CPU a 40MHz. Ricordo che con le impostazioni del programma è possibile utilizzare anche l'oscillatore interno, e sfruttare questi due pin come I/O.
Il Pin 1 (MCLR) deve essere obbligatoriamente messo a 5Volt, tramite una resistenzza da 10K ohm, altrimenti il PIC non funziona, vedremo che questa cosa può essere escluda da software e il pin può essere usato come un comune PIN di I/O. Il pulsante S1 non è necessario, l'ho inserito solo per completare lo schema, il suo scopo è semplicemente quello di eseguire il RESET dell'MCU.
Il connettore J1 viene utilizzato per la programmazione in Circuit, questa funzione offerta da alcuni programmatori, è molto commoda, perchè permette di programmare il PIC senza doverlo rimuove dal circuito.
Ed eccoci arrivati hai LED collegati ai pin 2 (RA0), 3 (RA1), 4 (RA2) e 5 (RA3) tramite resistenze da 470 ohm. Fate solo attenzione nel montaggio a mettere il Katodo dei LED verso massa.
Il circuito è molo semplice e può essere montato con varie tecniche, quindi scegliete voi quella che vi rimane più facile. Se invece siete ancora agli inizi e non avete dimestichezza con il saldatore vi consiglio di montare il circuito su una bread-board, come potete vedere anche dalla figura che segue, il cablaggio è molto semplice e veloce, ma sicuramente non molto stabile, quindi valido solo per circuiti di test. Il connettore volante che vedete, serve per collegare il PICkit2 per la programmazione ICSP (In Circuit Serial Programming), se usate un altro programmatore che non prevede la programmazione in Circuit potete anche farne a meno.


Cominciamo ad analizzare il codice del primo programma che, farà accedere i LED in sequenza prima in un verso e poi nell'altro in pieno stile SuperCar.

 1: #include <p18F2620.h>
 2: #include <delays.h>
 3: /*===========================================================================*/
 4: /*==== DEFINIZIONE PRAGMA ====*/
 5: /*===========================================================================*/
 6: #pragma config OSC = HSPLL // Usare con quarzo da 10MHz e PPL 4x
 7: #pragma config FCMEN= OFF // File-Safe clock monitor
 8: #pragma config IESO = OFF // Internal External OSC SwitchOver
 9: #pragma config PWRT = ON // Power Up timer
10: #pragma config BOREN= OFF // Brown Out reset
11: #pragma config BORV = 0 // Brown Out voltage
12: #pragma config WDT = OFF // WhatchDog timer
13: #pragma config WDTPS= 1 // WhatchDog postscaler
14: #pragma config MCLRE= ON // MCLR enable
15: #pragma config LPT1OSC = OFF // T1 Oscillator enable
16: #pragma config PBADEN = OFF // PORTB A/D enable
17: #pragma config CCP2MX = PORTC // CCP2 Multiplex with RB3 or RC1
18: #pragma config STVREN = ON // Stack Overflow reset
19: #pragma config LVP = OFF // Low Voltage ICSP
20: #pragma config XINST= OFF //
21: #pragma config DEBUG= OFF // Background Debugger enable
22: #pragma config CP0 = OFF // Code Protection Block 0
23: #pragma config CP1 = OFF
24: #pragma config CP2 = OFF
25: #pragma config CP3 = OFF
26: #pragma config CPB = OFF // Boot Block
27: #pragma config CPD = OFF // Data EEPROM
28: #pragma config WRT0 = OFF // Write Protection
29: #pragma config WRT1 = OFF
30: #pragma config WRT2 = OFF
31: #pragma config WRT3 = OFF
32: #pragma config WRTB = OFF // Boot Block
33: #pragma config WRTC = OFF // Configuration Register
34: #pragma config WRTD = OFF // Data EEPROM
35: #pragma config EBTR0= OFF // Table Read protection Block 0
36: #pragma config EBTR1= OFF
37: #pragma config EBTR2= OFF
38: #pragma config EBTR3= OFF
39: #pragma config EBTRB= OFF // Boot Block Table Read protection
40: /*===========================================================================
41: ==== DEFINE ====
42: ===========================================================================*/
43: #define LED_1 LATAbits.LATA0
44: #define LED_2 LATAbits.LATA1
45: #define LED_3 LATAbits.LATA2
46: #define LED_4 LATAbits.LATA3
47: //===========================================================================
48: //==== MAIN   ====
49: //===========================================================================
50: void main(void)
51: {
52: unsigned char counter;
53:
54:    // Impostazione dei registri di configurazione del PIC
55:    ADCON0=0b00000001;
56:    ADCON1=0b00001100;
57:    ADCON2=0b10101110;
58:    // Reset iniziale delle porte
59:    PORTA=0;
60:    PORTB=0;
61:    PORTC=0;
62:    // Definizione della funzione delle singole porte
63:    TRISA=0x00;
64:    TRISB=0x00;
65:    TRISC=0x00;
66:
67:    // Accende il primo LED
68:    LED_1=1;
69:    Delay10KTCYx(250);
70:    // Loop infinito
71:    for(;;)
72:    {
73:       // Cicla per eseguire lo shift a sinistra del bit/LED
74:       for(counter=0;counter<3;counter++)
75:       {
76:          LATA=LATA<<1;
77:          Delay10KTCYx(250);
78:       }
79:       // Cicla per eseguire lo shift a destra del bit/LED
80:       for(counter=0;counter<3;counter++)
81:       {
82:          LATA=LATA>>1;
83:          Delay10KTCYx(250);
84:       }
85:    }
86: }

Come abbiamo già visto le righe 1 e 2 indicano al compilatore che deve includere i file indicati nelle parentesi angolari, nel file p18F2620.h ci sono le definizioni di tutti i registri e porte di tale PIC, in pratica la stessa cosa che succede in assembler. Per ogni PIC avremo il suo specifico file .h da includere al nostro progetto, mentre nella riga 2 troviamo l'inclusione del file delay.h, questa è una libreria di funzioni del C18 per realizzare dei ritardi basati sul numero di cicli macchina, quindi dipendenti dalla frequenza di utilizzo dell'MCU.
Dalla riga 6 fino alla 39 sono indicate le impostazioni dei vari FUSE del PIC tramite la direttiva #pragma, per il significato di ognuno dei fuse vi rimando hai commenti nel codice e alla documentazione del C18, dove potete trovare un documento (directory_installazione_C18\DOC\hlpPIC18ConfigSet.chm) che indica i FUSE e i sui possibili valori per ogni PIC della serie 18. Ho notato che nelle ultime versioni il PICkit2 fa un po' di storie se non trova tutti i FUSE, quindi per essere certi che vengano settati in modo giusto vi consiglio di inserirli sempre tutti.
Nelle righe da 43 a 46 sono definite le etichette per LED_1, LED_2, ..., che vengono associate ai bit LATA0, LATA1, ..., del registro LATA. Una breve spiegazione sulle porte dei PIC è necessaria, ogni PORT è definita come un registro e atichettata con una lettera A, B e C, nel caso del 18F2620, ogni singolo bit di una porta può essere utilizzato indistintamente come input o output, dalla serie 18 è stato introdotto un nuovo registro oltre al PORT, che resta ancora funzionante, ed è il LAT, questo nuovo registro deve essere usato quando la porta viene impiegata come output, per ovviare ad alcuni problemi tipici delle MCU, per i dettagli vi rimando ai DS o ad una ricerca in rete. I registri PORT invece restano validi per gestire l'input delle porte. Nelle define del listato la sintassi LATAbits.LATA0 indica che si sta puntanto al bit 0 del registro LAT della porta A, vedremo il significato della sintassi in dettaglio in altre lezioni.
Finalmente siamo al programma vero e proprio, in C ANSI, come abbiamo già visto, il programma è contenuto in una funzione principale il cui nome è "main" (riga 50), fate attenzione perchè il C fa distinzione tra maiuscole e minuscole, quindi scrivere Main non è la stessa cosa di scrivere main, anzi vengono interpretate come due parole diverse, il consiglio è di usare sempre minuscole e di usare tutti i caratteri maiuscoli per le costanti, come ad esempio le etichette delle define.
Alla riga 53, c'è la definizione di una variabile di tipo "unsigned char", questa definizione sta ad indicare che la variabile rappresenta una locazione di memoria atta a contenere un numero rappresentato da 8 bit (char equivale ad un byte), senza segno, cioè un numero che può avere solo valori positivi, quindi facendo due rapidi calcoli la nostra variabile può contenere un numero da 0 a 255, sempre nella documentazione del C18 potete trovare tutti i tipi di variabili e i loro range di utilizzo. Apro una parentesi importante, stiamo lavorando su delle MCU che non dispongono di un grosso quantitativo di memoria, si parla di pochi KByte, quindi è buona norma non sprecare le risorse, nell'esempio di codice che stiamo analizzando la cosa ha poco senso viste le poche righe di codice, ma è bene abituarsi sin dal principio a risparmiare memoria la dove è possibile.
Passiamo ora alla configurazione del nostro PIC, dalla riga 56 fino alla 65 ci sono delle impostazioni dei registri, in particolare stiamo settando tutti i pin in modo da essere degli output digitali. Se guardate il datasheet per ogni porta sono riportati esempi (in assembler) di come devono essere impostati e quali registri vengono usati. Nel nostro caso utilizziamo 4 bit del PORTA che sono utilizzati per funzioni diverse tipo I/O digitali o ingressi analogici, quindi prima di utilizzarli è necessario configurarli in modo corretto, alla riga 56 ADCON1 è un registro di controllo per il convertitore Analogico Digitale, come potete vedere sempre dal datasheet, impostandolo a 0b00001111 tutte le porte che hanno funzionalità di A/D vengono impostate come digitali.
Alla riga 57 CMCON disabilita la funzione di comparazione sui pin da RA0 ad RA3, guarda caso proprio quelli utilizzati da noi. Le righe da 59 a 61 servono semplicemente a mettere a 0 i registri delle varie porte, questa è una buona abitudine e consigliato da tutte le parti, questa operazione va sempre fatta prima di impostare i registri TRISx, dove x sta per il nome della porta (righe 63,64 e 65). Questi registri servono ad indicare la direzione del segnale digitale di ogni singolo bit della porta, un bit valorizzato ad 1 indica input, mentre valorizzato a 0 indica output, nel nostro caso come potete vedere abbiamo settato tutto come output.
In queste righe ho volutamente utilizzato tre tipi diversi di annotazione per scrivere i numeri, proprio per farvi vedere i possibili modi per scrivere un numero:

  • 0b01010011 - Notazione binaria, si distingue dal prefisso "0b" che prevede il numero, naturalmente accetta come numeri solo 1 e 0
  • 128 - Notazione decimale, in questo caso non si utilizza nessun prefisso, ma si scrive semplicemente il numero utilizzando le cifre da 0 a 9
  • 0x2F - Notazione esadecimale, si distingue dal prefisso "0x" ed accetta tutte le cifre numeriche da 0 a 9 più i caratteri a, b, c, d, e, f.
E finalmente dopo aver impostato tutte le configurazioni siamo al codice che viene eseguito dal PIC per far lampeggiare i nostri LED. Alla riga 68 troviamo la prima istruzione che riporta:

LED_1=1;

Come abbiamo già visto LED_1 è una etichetta che abbiamo definito alla riga 43, e ora sappiamo che per il compilatore la riga 68 equivale a dire:

LATAbits.LATA0=1;

Vedete che oltre ad essere più semplice da scrivere, è anche più comodo da leggere LED_1, immediatamente capiamo guardando il codice cosa sta facendo il nostro programma, mentre LATAbits.LATA0 ci fa capire solo quale bit del registro LAT della porta A stiamo utilizzando, ma non ci aiuta a capire a quale parte del nostro circuito è collegata. Però in qualsiasi modo la scriviamo dopo la riga 68 il nostro primo LED risulterà acceso!
La riga 69 fa una chiamata ad una funzione di ritardo, questa è definita nel file delay.h e come potete vedere non restituisce nessun valore, ma prende un parametro in ingresso. La funzione Delay10KTCYx(numero_ripetizioni) in pratica attende 10000 * numero_ripetizioni colpi di clock prima di terminare, numero_ripetizioni è un unsgined char quindi al massimo possiamo inserire come valore 255. Per un PIC con quarzo da 10MHz e PLL attivato per portare la frequenza della CPU a 40MHz numero_ripetizioni indica il numero di millisecondi di pausa.
L'istruzione "for" [http://it.wikipedia.org/wiki/Ciclo_for] (righe 71,74,80) serve praticamente a ripetere una o più righe di codice per un numero definito di volte, la sintassi è la seguente:

for(;;) { /* Codice eseguito nel ciclo */ }

Il ciclo for si basa su di una variabile, in pratica in viene assegnato il valore iniziale che vogliamo dare a questa variabile, in invece va inserito il controllo da effettuare sulla variabile per stabilire quando dovrà essere interrotto il ciclo, mentre in bisogna indicare in che modo la variabile deve cambiare ad ogni iterazione. Nel nostro programma abbiamo due cicli for di questo tipo:

for(counter=0;conter<3;counter++) { .... }

Come si può vedere la variabile counter viene inizializzata a zero, e il controllo viene impostato a "counter<3", ultimo ed importante parametro "counter++" questa istruzione utilizza l'operatore "++" che in pratica indica l'incremento di uno della variabile counter, in pratica equivale a scrivere counter=counter+1; ci sono anche altri operatori con funzionamento simile ad esempio "--" decrementa di 1 la variabile, vi consiglio comunque di leggere la definizione di tutti gli operatori del C. Ma vediamo cosa succede nel ciclo, alla prima iterazione viene valorizzata la variabile (counter=0) e successivamente verificata la condizione, se questa è vera (0 è minore di 3, quindi è vera) viene eseguito il codice contenuto nelle parentesi graffe alla fine del quale viene eseguito l'incremento della variabile (counter=counter+1 ovvero 0+1=1). Ora viene di nuovo verificata la condizione (1<3) se è vera si ripete di nuovo il codice e poi viene incrementata la variabile, il tutto si ripete fino a quando l'incremento porta la variabile counter a 4 in questo caso avremo il controllo della condizione con i valori 4<3, che naturalmente risulta falsa e quindi il ciclo viene fermato, riprendendo il programma dalla prima istruzione che segue il blocco di codice delle parentesi graffe. L'istruzione for di riga 71 non presentando nessun parametro di controllo non impone nessuna condizione di arresto del ciclo, quindi si genera quello che viene definito un LOOP infinito, questo tipo di loop viene utilizzato nei programmi per MCU per far eseguire un programma in continuazione, se così non fosse dopo aver eseguito i due cicli for del nostro programma, questo continuerebbe senza eseguire nessuna istruzione, perché il Program counter della MCU continuerebbe ad essere incrementato fino ad arrivare all'ultimo indirizzo disponibile per poi ricominciare da zero, questo ci metterebbe in condizione di non poter controllare il nostro programma.
Alla riga 76 incontriamo un nuovo operatore "<<" la cui sintassi è byte<<numero_spostamenti, questo indica che verrà eseguito uno shift a sinistra di numero_spostamenti del byte indicato. Nel nostro caso alla prima iterazione del ciclo for il registro LATA è valorizzato a 0b00000001, la riga 76 dice che si deve eseguire uno shift a sinistra di 1 bit, quindi dopo averla eseguita il registro LATA conterrà 0b00000010, come potete vedere il bit che stava a livello alto si è spostato dalla posizione 0 alla posizione 1 e ad ogni iterazione del ciclo for questo bit si sposta di un'altra posizione. Alla riga 82 l'operatore ">>" è in pratica uno shift a destra.
In pratica l'algoritmo sfrutta l'operatore shift per far "scorrere" il led acceso da una parte all'altra.
Ho lasciato per ultimo, ma non perché meno importanti, i commenti ci sono due modi di scriverli, con due modi di funzionare diversi, il primo "//" è un commento su una sola riga, tutto quello che segue a destra non verrà interpretato dal compilatore, l'altro tipo di commento si apre con "/*" e si chiude con "*/" può essere utilizzato su una o su più righe come potete vedere alle righe 40,41 e 42 del nostro programma. Vi ricordo che scrivere i commenti è molto importante, può sembrare noioso e una perdita di tempo, ma prendere l'abitudine di scrivere buoni commenti aiuta, quando si andrà a rivedere il codice dopo tanto tempo, a capire cosa faceva il nostro programma.

Spero che le mie spiegazioni siano state abbastanza chiare, se trovate errori o cose poco chiare contattatemi anche all'indirizzo di posta indicato nell'introduzione.
Vi lascio con dei compiti per casa, per capire la differenza tra LAT e PORT, modificate il programma sostituendo LATA con PORTA nei punti dove viene fatto lo shift e cercate di capire la differenza di funzionamento, magari aiutandovi con il datasheet. Da questo LINK potete scaricare il progetto per MPLAB relativo al programma di esempio.

Precedente Indice Successivo


 

NEWS


NEWS


NEWS