Indice |
Dove eravamo rimasti?
Nel precedente articolo avevamo disegnato uno schema elettrico(grazie a Paolino che lo ha corretto!) il quale,
dopo aver programmato il nostro PIC, ci permette di accendere due LED alla pressione di due pulsanti. Un progetto davvero molto
semplice, molto facile, in modo da poter essere realizzato da tutti!
L'IDE MPLAB
Come detto nell'articolo precedente la scelta tra la versione 8.83 e la versione X non è vincolante ai fini del progetto! Possiamo utilizzarle
entrambe se vogliamo.
Projectda qui poi
Project Wizardsi aprirà una schermata che ci avvisa che questo "wizard" ci aiuterà a creare il nostro progetto, premiamo su
avantiOra la nuova schermata(chiamata step One) che ci chiede di scegliere il PIC che intendiamo utilizzare, selezioniamo(nel mio caso)
PIC16F887chi ha un altro PIC ovviamente scelga un altro PIC :)
Dopo aver scelto il PIC che intendiamo utilizzare premiamo avanti e una nuova schermata(step Two) ci chiede principalmente due cose, analizziamole con calma:
- Alla voce Active ToolSuite scegliamo(tra le tante) Microchip MPLAB XC8 ToolSuite se abbiamo installato il compilatore MPLAB XC8
oppure scegliamo il compilatore che preferiamo, l'importante è che sia "adatto" al linguaggio C e non per il linguaggio Assembler. In pratica questa voce ci permette di scegliere quale compilatore utilizzare
- Alla voce Location lasciamo tutto com'è, nel 99% dei casi non bisogna modificare nulla
avantie scegliamo la cartella tramite il tasto Sfoglia(consiglio di creare una cartella per ogni progetto realizzato, in modo da tenere in ordine tutti i progetti) e su Nome file mettiamo un nome a nostro piacere; alla voce sottostante(Salva come) scegliamo MPLAB IDE Project files(*.mcp) se non è selezionato di default. Alla voce Jump to non modifichiamo niente e premiamo
salvae poi
avantiNella schermata successiva(step Four) che ci chiede di aggiungere files al nostro progetto non aggiungiamo niente e clicchiamo direttamente su
avantie ora clicchiamo su
Fine
Perfetto, abbiamo creato il nostro progetto! C'è un problema, non ha file(s) al suo interno, è vuoto! Dove lo scriviamo il nostro programma ora? Beh, se il file dove scrivere non è presente lo aggiungiamo noi, che problema c'è? Per aggiungere questo file(che sarà il nostro file sorgente)
clicchiamo suProjectpoi su
Add new file to projectNella finestra che compare, ci viene chiesta la cartella dove posizionare il nuovo file. Scegliamo dunque la cartella creata prima(step Two), nella quale sono già presenti 2 o 3 files creati da MPLAB IDE, e diamo un nome al file che finisca con estensione .c(Esempio: Prog.c) in questo modo diciamo al compilatore che si tratta di un file che contiene istruzioni in linguaggio C(estensione ".c"). Attenzione che quando salviamo il file con estensione ".c" dobbiamo accertarci che nella voce sottostante sia selezionato All Files(*.*). Fatto questo clicchiamo su
salvae saremo riportati nella schermata iniziale però noteremo due cose diverse:
- Abbiamo "in bella vista" un foglio bianco chiamato "prog.c"(o come avete chiamato voi il vostro programma)
- Di default vengono visualizzati i files di progetto(se così non fosse cliccate su View e poi Project), nella cartella Source Files è presente il nostro bel file con estensione ".c"
OK! Ora siamo davvero pronti per scrivere il nostro codice :)
Il programma
Scriveremo questo programma in due passi, così da capire perchè abbiamo utilizzato certe istruzioni e non altre, perchè abbiamo chiamato così una variabile e non in un altro modo ecc... Nel primo passo scriveremo il programma in Pseudocodice, cioè un codice "fittizio" che ci permette di rappresentare meglio l'algoritmo prima di tradurlo direttamente in linguaggio C. Un'ultima cosa FONDAMENTALE quando si programma: la cosiddetta indentazione, cioè l'utlizzo di tabulazioni per scrivere parti di codice annidate. Esempio brevissimo:
Funzione1(int a,int b){ int c=a+b; if(c>0) printf("Positivo"); else if(c<0) printf("Negativo"); else if(c==0) printf("Nullo"); return c; }
E' una funzione stupidissima, ma ci serve per capire che è già difficoltosa la comprensione e la manutenzione di questo codice, molto breve e che abbiamo scritto noi! Immaginate se dovessimo "mettere mano" al codice scritto da qualcun'altro in questo modo... Ora invece guardate questa stessa funzione riscritta così:
Funzione1(int a,int b){ int c=a+b; if(c>0) printf("Positivo"); else{ if(c<0) printf("Negativo"); else{ if(c==0) printf("Nullo"); } } }
Così già visivamente è possibile capire dove iniziano e finiscono i costrutti di programmazione e le funzioni.
Benissimo, ora iniziamo a scrivere il nostro pseudocodice tenendo bene a mente una cosa fondamentale nella programmazione dei PIC: Un programma che deve essere eseguito su di un PIC deve essere incluso in ciclo infinito. Questo perchè?
Perchè se così non fosse il PIC eseguirebbe una volta sola il programma e poi starebbe fermo(inattivo) fino a che è alimentato, e questo è una situazione quasi sempre(tranne casi rarissimissimi) da evitare. Se noi scriviamo un programma per accendere dei LED, vogliamo che si accendano ogni volta che premiamo il bottone giusto, non solo per la durata del programma(qualche microsecondo!).
In altre parole, se io voglio far lampeggiare un LED, voglio che lampeggi sempre(fino a che il PIC è alimentato), non solo una volta e basta! Quindi questo è il motivo per cui si richiude tutto il programma principale in un ciclo infinito, in modo che il PIC continui ad eseguirlo sempre, non solo la prima volta!
Che dite, ci "armiamo" di blocco note(virtuale), la nostra tabella e voglia di scrivere questo pseudocodice?
Iniziamo! Credo che verrebbe fuori qualcosa del genere:
// Questa riga è un commento, cioè viene ignorata dal compilatore e aiuta a rendere leggibile il codice INCLUDI librerie_necessarie INIZIO_PROGRAMMA IMPOSTA_PIEDINO(19,INGRESSO); IMPOSTA_PIEDINO(20,INGRESSO); IMPOSTA_PIEDINO(21,USCITA); IMPOSTA_PIEDINO(22,USCITA); CICLO_INFINITO_INIZIO //Se il piedino è messo al riferimento(tasto chiuso) "vale" 0 logico CONTROLLA_SE(Piedino19==0) INIZIO_ISTRUZIONI_CONTROLLA_SE IMPOSTA_LIVELLO_LOGICO_PIEDINO(22,LIVELLO_ALTO); FINE_ISTRUZIONI_CONTROLLA_SE //Se non premo il tasto corrispondente spengo il LED ALTRIMENTI IMPOSTA_LIVELLO_LOGICO_PIEDINO(22,LIVELLO_BASSO); //Se il piedino è messo al riferimento(tasto chiuso) "vale" 0 logico CONTROLLA_SE(Piedino20==0) INIZIO_ISTRUZIONI_CONTROLLA_SE IMPOSTA_LIVELLO_LOGICO_PIEDINO(21,LIVELLO_ALTO); FINE_ISTRUZIONI_CONTROLLA_SE //Se non premo il tasto corrispondente spengo il LED ALTRIMENTI IMPOSTA_LIVELLO_LOGICO_PIEDINO(21,LIVELLO_BASSO); CICLO_INFINITO_FINE FINE_PROGRAMMA
Analizzando questo pseudocodice notiamo che:
- Librerie_Necessarie? E cosa sono? Semplicissimo: Le librerie in generale sono(perdonate la leggerezza della definizione) raccolte di codice e definizioni che rendono il lavoro del programmatore più semplice. Cioè? Immaginate se al posto di usare i nomi dei registri usassimo il loro indirizzi effettivi! Verrebbe fuori qualcosa del tipo: IMPOSTA_PIEDINO(@0x027,LIVELLO_ALTO); IMPOSTA_PIEDINO(@0x047,LIVELLO_ALTO); ecc...In pratica sarebbe un guazzabuglio di codici esadecimali, il caos più totale! Invece grazie alle librerie possiamo dire semplicemente piedino19 al posto di "chiamarlo" con il suo indirizzo, ad esempio @0x27... Immaginate la comodità :)
- Il programma non fa altro che controllare continuamente lo stato dei due ingressi(bottoni) e si "comporta" di conseguenza
- Lo stato del è LED sempre l'opposto dello stato del bottone corrispondente, uhm...
Benissimo, traduciamo tutto in linguaggio C!
Ne verrebbe fuori(Spiegheremo tutto dopo):
#include <htc.h> #define LED_0 PORTDbits.RD2 #define LED_1 PORTDbits.RD3 #define BTN_0 PORTDbits.RD0 #define BTN_1 PORTDbits.RD1 __CONFIG(CONFIG1&FOSC_INTRC_NOCLKOUT&WDTE_OFF&PWRTE_OFF&MCLRE_ON&CP_OFF&CPD_OFF&BOREN_OFF&IESO_OFF&FCMEN_OFF&LVP_OFF&DEBUG_OFF); __CONFIG(CONFIG2&WRT_OFF); void main(void){ TRISD=0b11110011; // Uguale a scrivere TRISD=0xF3, cioè tutti ingressi tranne RD2/3 uscite while(1){ LED_0=!BTN_0; LED_1=!BTN_1; } }
La programmazione tramite PicKit2/3
Dopo aver scritto il codice, possiamo compilarlo cliccandoProjecte dunque su
Build(F10)In questo modo nella cartella verrà creato un file(tra i tanti) con estensione .hex(ad esempio Leds.hex dato che io ho chiamato il mio progetto Leds).Questo file è quello importante perchè contiene le istruzioni direttamente eseguibili dal PIC.
Prima "carichiamo"(cioè programmiamo) il PIC con il file ".Hex" creato prima e poi spieghiamo quelle istruzioni oscure. Per caricare il programma in memoria bisogna collegare il PicKit2/3 al PIC(ma anche al PC tramite il cavo USB) come indicato nel circuito precedente e aprire il programma PicKit 2/3 v2.xx (dove xx indica un numero a due cifre. Es: v2.61) e scegliere in alto
Device_Familye poi
Midrange>StandardPer alcuni potrebbe cambiare la procedura(ad esempio si dovrebbe scegliere baseline) e poi cliccare su
Import Hexe da qui scegliere la cartella contenente il file ".hex".
Se i collegamenti sono stati effettuati correttamente si vedrà la scritta rassicurante PicKit2/3 found and connected! e si potrà procedere(dopo aver importato il file .hex) alla programmazione del PIC, eseguibile premendo l'apposito bottone
writeIn questo modo, dopo qualche secondo comparirà una scritta verde che ci rassicura sulla corretta programmazione del PIC. Ora possiamo alimentarlo dal PicKit2/3 stesso e provarne il funzionamento. Per alimentare il PIC bisogna spuntare la casella con scritto ON sulla destra(controllate che la tensione sia maggiore di 4.5 V, in caso contrario sistematela voi). Per resettare il PIC possiamo spuntare la casella sottostante /MCLR che non fa altro che porre al livello logico basso il piedino MCLR.
Spieghiamo il Programma
Nel programma abbiamo incluso una "libreria tuttofare" diciamo così, cioè una libreria che si occupa per noi di includere il giusto file relativo al PIC utilizzato. Poi abbiamo assegnato ai piedini RD0 e RD1(impostati successivamente come ingressi) il nome simbolico di BTN_0 e BTN_1 dove BTN indica appunto un bottone! Perchè? Per il semplice motivo che è più facile scrivere BTN_0 e non PORTDbits.RD0(oltre che più chiaro) e soprattutto perchè se un giorno decidessi di cambiare il piedino a cui collegare il bottone non devo riscrivere da capo tutto il programma, mi basta solo cambiare quella riga, con evidenti vantaggi!
Ora arriva il divertimento, la "funzione" __CONFIG! Allora, con molta calma proviamo a capire intanto perchè questa funzione la utilizziamo per ben due volte e soprattutto perchè contiene tutte quelle abbreviazioni incomprensibili!
Prima cosa: questa funzione va chiamata prima del main(che è la funzione principale, quella da cui deve iniziare il nostro programma) perchè il PIC si configura una volta sola e all'inizio! Non vi sognate di provare a ri-configurarlo durante la normale esecuzione del programma, otterreste solo errori :)
Seconda cosa: Il primo parametro di questa funzione cambia, e la prima volta che la utilizziamo il primo parametro è CONFIG1, la seconda CONFIG2, questo non è un caso. CONFIG1 non è altro che un indirizzo di memoria nel quale noi "scriviamo" la prima parte di configurazione, CONFIG2 è un altro indirizzo che contiene la seconda parte di configurazione. Più tecnicamente sono due Config Word, cioè parole(insieme di bits) che permettono di configuare il PIC in vari modi a seconda dei bit che vi impostiamo.
Terza cosa: Se apriamo il datasheet possiamo leggere che tutti questi acronimi oscuri hanno un significato ben preciso!
<Illegalità>
NON DOVREI FARLO( :) ), ma voglio farvi davvero capire(la verità è che mi piacerebbe, ma mi limito a provarci) cosa sono questi acronimi, questi registri, queste cose dando un'occhiata "dietro le quinte"...
Ora copierò e incollerò qui il codice preso dalla libreria che contiene le definizioni riguardanti il PIC16F887, il file in questione(nel mio computer) si trova all'indirizzo assoluto C:\Programmi\Microchip\xc8\include\pic16f887.h.
Il file è ben commentato, quindi non dovrebbero esserci problemi a comprenderlo. Meglio di qualsiasi spiegazione io possa trovare!
/* * Configuration Mask Definitions */ // Config Register: CONFIG1 #define CONFIG1 0x2007 // Oscillator Selection bits // RC oscillator: CLKOUT function on RA6/OSC2/CLKOUT pin, RC on RA7/OSC1/CLKIN #define FOSC_EXTRC_CLKOUT 0xFFFF // RCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, RC on RA7/OSC1/CLKIN #define FOSC_EXTRC_NOCLKOUT 0xFFFE // INTOSC oscillator: CLKOUT function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN #define FOSC_INTRC_CLKOUT 0xFFFD // INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN #define FOSC_INTRC_NOCLKOUT 0xFFFC // EC: I/O function on RA6/OSC2/CLKOUT pin, CLKIN on RA7/OSC1/CLKIN #define FOSC_EC 0xFFFB // HS oscillator: High-speed crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN #define FOSC_HS 0xFFFA // XT oscillator: Crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN #define FOSC_XT 0xFFF9 // LP oscillator: Low-power crystal on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN #define FOSC_LP 0xFFF8 // Watchdog Timer Enable bit // WDT enabled #define WDTE_ON 0xFFFF // WDT disabled and can be enabled by SWDTEN bit of the WDTCON register #define WDTE_OFF 0xFFF7 // Power-up Timer Enable bit // PWRT disabled #define PWRTE_OFF 0xFFFF // PWRT enabled #define PWRTE_ON 0xFFEF // RE3/MCLR pin function select bit // RE3/MCLR pin function is MCLR #define MCLRE_ON 0xFFFF // RE3/MCLR pin function is digital input, MCLR internally tied to VDD #define MCLRE_OFF 0xFFDF // Code Protection bit // Program memory code protection is disabled #define CP_OFF 0xFFFF // Program memory code protection is enabled #define CP_ON 0xFFBF // Data Code Protection bit // Data memory code protection is disabled #define CPD_OFF 0xFFFF // Data memory code protection is enabled #define CPD_ON 0xFF7F // Brown Out Reset Selection bits // BOR enabled #define BOREN_ON 0xFFFF // BOR enabled during operation and disabled in Sleep #define BOREN_NSLEEP 0xFEFF // BOR controlled by SBOREN bit of the PCON register #define BOREN_SBODEN 0xFDFF // BOR disabled #define BOREN_OFF 0xFCFF // Internal External Switchover bit // Internal/External Switchover mode is enabled #define IESO_ON 0xFFFF // Internal/External Switchover mode is disabled #define IESO_OFF 0xFBFF // Fail-Safe Clock Monitor Enabled bit // Fail-Safe Clock Monitor is enabled #define FCMEN_ON 0xFFFF // Fail-Safe Clock Monitor is disabled #define FCMEN_OFF 0xF7FF // Low Voltage Programming Enable bit // RB3/PGM pin has PGM function, low voltage programming enabled #define LVP_ON 0xFFFF // RB3 pin has digital I/O, HV on MCLR must be used for programming #define LVP_OFF 0xEFFF // In-Circuit Debugger Mode bit // In-Circuit Debugger disabled, RB6/ICSPCLK and RB7/ICSPDAT are general purpose I/O pins #define DEBUG_OFF 0xFFFF // In_Circuit Debugger enabled, RB6/ICSPCLK and RB7/ICSPDAT are dedicated to the debugger #define DEBUG_ON 0xDFFF/* * Configuration Mask Definitions */ // Config Register: CONFIG2 #define CONFIG2 0x2008 // Brown-out Reset Selection bit // Brown-out Reset set to 2.1V #define BOR4V_BOR21V 0xFEFF // Brown-out Reset set to 4.0V #define BOR4V_BOR40V 0xFFFF // Flash Program Memory Self Write Enable bits // 0000h to 0FFFh write protected, 1000h to 1FFFh may be modified by EECON control #define WRT_HALF 0xF9FF// 0000h to 07FFh write protected, 0800h to 1FFFh may be modified by EECON control #define WRT_1FOURTH 0xFBFF // 0000h to 00FFh write protected, 0100h to 1FFFh may be modified by EECON control #define WRT_256 0xFDFF // Write protection off #define WRT_OFF 0xFFFF
Non pretendiamo di capirlo tutto subito, ma ci rendiamo conto che ogni acronimo utilizzato indica una funzione specifica! Confrontando questi valori con quelli utilizzati nel nostro programma capiremo che "feature" del PIC abbiamo utilizzato. N.B.:Se qualche termine non è chiaro(tipo WDT, WatchDog Timer) riferirsi al sacro datasheet!
</Illegalità>
Passiamo ora alla definizione del main(la funzione principale) di tutto il nostro programma, di tipo void, cioè non ritorna nessun valore(dal momento che non avrebbe senso ritornarne uno e poi il programma non finisce mai...).
Dopo troviamo la scritta TRISD che indica praticamente il registro "D" che permette di scegliere la configurazione dei pin del registro(TRIS). La scritta TRIS è una "storpiatura" di Three state. In poche parole se il bit di questo registro(i bit sono 8, uno per ogni piedino) vale "1" allora quel pin è configurato come ingresso digitale, altrimenti come uscita digitale. Il bit meno significativo(Ordine dei bit) è quello più a destra. In questo modo impostiamo tutti(anche se ne utilizziamo la metà) i bit come ingresso eccezion fatta per RD2/3 che impostiamo come uscita azzerando l loro bit.
A questo punto, dopo aver notato che lo stato dei LED è sempre opposto a quello dei bottoni a loro associati, possiamo risparmiarci letteralmente tutti controlli e dire semplicemente che il LED_0 è l'opposto del BTN_0 e il LED_1 è l'opposto del BTN_1. Questo lo "diciamo" al compilatore utilizzando l'operatore ! che in C indica la negazione. E' come se dicessimo che il LED_0 è uguale a NOT(BTN_0), dove NOT(x) ritorna 0 se x vale 1, altrimenti ritorna 1 se x vale 0.
Il while(1) che contiene tutto è il nostro ciclo infinito. Ci tenevo a ricordare che i costrutti di programmazione in C iniziano con { e finiscono con }. Detto questo non credo ci sia molto da dire ancora, se non invitarvi a modificare il programma a vostro piacimento e "smanettarvi sopra"! E' un ottimo metodo per imparare :)
Ringraziamenti
Non potevo astenermi dal ringraziare coloro che hanno avuto la pazienza di leggere fino a qui e di aspettare una settimana per queste quattro righe, Grazie! Inoltre un grazie va ad admin e webmaster senza i quali il sito non esisterebbe, a tutti coloro che hanno scritto gli articoli da cui ho attinto informazioni, questa meravigliosa community che offre sempre validissimi spunti e articoli di altissimo livello(senza contare i miei "3 debutti")! Beh, grazie e al prossimo articolo :)