Un PIC16F ad 8 pin : programmiamolo per fare un effetto luminoso
Indice dei contenuti
Premessa
In questo articolo riprendo un po’ in mano i microcontrollori PIC® ai quali devo il fatto di essermi fatto conoscere nell’ambiente di programmazione dei microcontrollori. Abbiamo già esplorato in passato, con precedenti articoli, le differenze che sono state introdotte con MPLAB® X ide rispetto al vecchio MPLAB. In questo articolo vedremo un po’ qualche altra recente innovazione: la prima è il fatto che ora per la configurazione semplificata dei microcontrollori abbiamo un tool che si chiama Melody, rinominato in MCC Melody, che sostituisce il “vecchio” MCC (MPLAB Code Configurator). Fin’ora Melody era appannaggio dei PIC32, ora è stato esteso a tutte le famiglie sebbene si possa continuare (anche se non è consigliato) ad utilizzare l’MCC che ora viene definito classic. In realtà non ci sono differenze così esagerate tra i due tools: sembrano quasi lo stesso strumento solo con le varie cose disposte in maniera diversa, forse più organizzata in maniera logica e compatibile. In aggiunta ho scoperto da poco tempo (chi segue il canale telegram lo sa), che anche la famiglia dei PIC16 adesso giova di alcuni modelli nel formato ad 8pin, così come tutti i PIC12 e alcuni PIC10 (che normalmente sono in un formato SMD a 6 pin). Dato che poi al Maker Faire Rome ho incontrato il buon vecchio Boris di Futura Elettronica e ho preso un po’ di roba interessante tra cui questi “filamenti led” che sono davvero bellissimi… ho deciso di unire le tre cose: questo nuovo PIC16 ad 8 pin che ho preso da Microchip direct (Il PIC16F15214), questo filamento, e il nuovo MCC melody per assemblare questo tutorial che andrete a leggere, nella speranza di fare felici quelli che come me amano i microcontrollori PIC®.
Alla fine avremo questo filamento led che lampeggia con un effetto glowin questo reel su instagram controllato da un segnale PWM il cui duty cycle varia da solo nel tempo (si tratta in pratica di come ottenere quello che ho fatto vedere ). Procediamo per gradi andando a vedere dapprima come si compone il codice.
Componiamo il codice con Melody
Avviamo MPLAB® X IDE e iniziamo un nuovo progetto: File > New Project. Nella finestra che compare lasciamo selezionati Microchip Embedded e Application Project(s), clicchiamo sul tasto Next. Nella finestra successiva (Select Device) lasciamo selezionato All Families e nel campo Device scriviamo a mano PIC16F15214. Nel campo Tool se abbiamo collegato un programmatore, come il PICkit™5, selezioniamolo, altrimenti lasciamo No Tool, Premiamo quindi Next.
Per questo PIC® in particolare è richiesto come minimo il PICkit™4
Nella finestra successiva viene chiesto di scegliere il compilatore, io ho installato l’ XC8 v2.50 e l’ho selezionato:
Premiamo quindi Next. Ora dobbiamo dare un nome al progetto, chiamiamolo ad esempio “16F15214_glow_example” e controlliamo che la cartella in cui verrà salvato il progetto sia quella giusta (generalmente una cartella chiamata MPLABXProjects nella cartella utente):
Controlliamo che sia selezionato Set as main project (che farà in modo, tra le altre cose, che il progetto corrente sia quello “attivo”, ovvero premendo i tasti compila e programma sarà questo il progetto ad essere compilato) e Open MCC on Finish che aprirà in automatico Il tool di configurazione (che ora si chiama più precisamente MCC Melody) per permetterci di settare tutte le periferiche ed avere già a disposizione delle funzioni di base per esse. Premiamo quindi il tasto Finish.
Attualmente compare una finestra che in qualche modo pubblicizza l’MCC Melody dal momento che fin’ora questo tool era disponibile solo per la famiglia PIC32 mentre ora è stato esteso a tutte le famiglie, probabilmente questa finestra in futuro non comparirà più. Anche se sarebbe possibile utilizzare la vecchia versione di MCC (cliccando il link più piccolo dove c’è scritto For MCC Classic Content: click here) direi che sia ora di iniziare ad utilizzare gli strumenti nuovi dato che i vecchi prima o poi non saranno più supportati, per cui premiamo Next per procedere con l’MCC Melody.
Dopo vari caricamenti e finestre che appaiono e scompaiono, compare l’application builder che ci permette in questo momento di settare la configurazione di base mostrando un’organizzazione a blocchi dei vari elementi. Clicchiamo sul blocco chiamato Clock Control:
Dopo aver cliccato sul tasto, si apre sulla destra una finestra con i settaggi: vediamo che il clock interno ad alta frequenza è già settato a 32MHz: lasciamo tutto così: ve l’ho fatto aprire giusto per prendere familiarità con il tool. Chiudiamo questa finestra e clicchiamo sul tasto Configuration Bits. Dato che non vogliamo utilizzare il quarzo esterno ma solo l’oscillatore interno, nel campo External Oscillator Mode Selection Bits selezioniamo Oscillator not enabled. Dal momento che ormai tutti i nuovi microcontrollori PIC® hanno la possibilità di avere un doppio clock (che cambia dopo la partenza in attesa che quello che vogliamo come principale si stabilizzi oppure che cambia in caso di malfunzionamenti o che vogliamo cambiare nel corso dell’esecuzione del programma per altre ragioni), il significato della voce Power-Up Default value for COSC bits (dove COSC è l’acronimo di Current OSCillator, oscillatore attuale) è appunto quello di definire la sorgente di clock all’avvio: di default è selezionato il valore HFINTOSC (32MHz) e rimarremo questo perchè andremo ad utilizzare un’unica sorgente di clock.
Il campo Clock Out Enable Bit serve per avere su un pin il segnale di clock interno di sistema, ovvero il valore FOSC/4 (nel nostro caso, in cui l’oscillatore è a 32MHz, vale 8MHz): io utilizzo spesso questa funzione quando ho l’impressione che il microcontrollore non stia eseguendo il programma a causa di qualche parametro sbagliato: controllando sull’oscilloscopio la presenza del segnale di clock in uscita almeno sono sicuro che la configurazione è andata a buon fine e posso spostare l’attenzione su altro: sembra una fesseria ma non sapete quante volte mi ha aiutato a capire se qualcosa non andava già allo start-up. Selezioniamo quindi CLKOUT Function is enabled; FOSC/4 appears on RA4 : frase che più chiara di così non si può… sul pin RA4 avremo quindi un segnale più o meno sinusoidale ad 8MHz.
Per quanto riguarda il settaggio di Master Clear Enable Bit devo dire che c’è un bel po’ di confusione e non è facile capire come va interpretato il valore da assegnare: io ho stesso ho fatto varie prove per vedere cosa succedeva. Partiamo dal fatto che questo settaggio serve per utilizzare il pin di MCLR (Master Clear – che normalmente utilizziamo sia per dare reset al microcontrollore dall’esterno, sia per mettere il PIC® in modalità programmazione usando la “vecchia” programmazione ad alta tensione), come normale I/O nel cui caso prenderebbe il nome di RA3. Diciamo subito che quel pin si può utilizzare come I/O, e quindi come RA3, solo nel caso in cui NON sia abilitata la programmazione a bassa tensione (chiamata LVP – Low Voltage Programming – settaggio che viene scelto più in basso) e che in questo punto sia selezionato il secondo settaggio che recita If LVP=0 MCLR pin is port defined function, if LVP=1 RA3 function is MCLR. In tutti gli altri casi quel pin ha sempre la funzione di MCLR (basta abilitare la funzione LVP per non averlo più come I/O).
Adesso la questione è questa: se state utilizzando come programmatore l’MPLAB® SNAP, non avete alternative: dovete abilitare per forza la programmazione a bassa tensione (che ormai è sempre di default) e rinunciare ad usare quel pin come I/O. Se invece vogliamo usarlo com I/O bisogna utilizzare un programmatore che supporti la programmazione classica e dobbiamo pure stare attenti a cosa colleghiamo su RA3 se programmiamo il microcontrollore a bordo del circuito di destinazione dal momento che li, in fase di programmazione, arriveranno dai 12 ai 13V. Più in basso parlo meglio delle limitazioni dello SNAP. Lasciamo tutti gli altri settaggi da default e arriviamo in fondo per fare la selezione di Low Voltage Programming Enable Bit come abbiamo detto fin’ora in base alle esigenze e disponibilità. Il mio consiglio è comunque quello di lasciare abilitata la LVP e rinunciare quindi ad RA3: questa modalità è quella più compatibile con il futuro.
Abbiamo finito con la configurazione di base. Adesso chiudiamo questa finestra e andiamo ad inserire le periferiche che vogliamo utilizzare. Dalla parte sinistra dell’IDE, in Device Resources, dalla lista Drivers espandiamo la voce PWM:
Compaiono i due moduli PWM di questo PIC: PWM3 e PWM4 (i PWM1 e PWM2 non sono moduli a se stanti ma sono una parte del modulo CCP – Capture Compare PWM), clicchiamo sul tasto + verde affianco a PWM3 per aggiungerlo. Il modulo PWM3 sarà quindi aggiunto alle risorse attuali e in automatico viene aggiunto anche Timer2 dal momento che questo modulo PWM necessita di quel Timer per poter funzionare:
Sempre da Device Resources espandiamo la voce Timer e aggiungiamo anche il Timer0, che ci servirà per avere un interrupt tramite il quale andremo a variare di continuo il Duty Cycle del PWM.
Abbiamo finito di aggiungere le periferiche che ci interessano: adesso bisogna configurarle. Vediamo che nello spazio dell’application builder adesso sotto al blocco Main abbiamo anche i blocchi del Timer0 e del PWM3. Clicchiamo su Timer0 e impostiamo come nell’immagine seguente:
In particolare notate che ho abilitato l’interrupt e ho impostato Clock Prescaler a 1:32 e Clock Source a FOSC/4. Nel campo del Requested Period va impostato il numero di secondi dopo il quale il Timer0 va in overflow (momento in cui, se è abilitato, possiamo intercettare l’interrupt): qui ho scritto 0.001 ovvero 1 millisecondo (vediamo che sotto, nella casella disabilitata, compare il tempo reale: 1000 microsecondi, che è 1millisecondo ok… ma nel caso in cui avessimo scritto un tempo che non è possibile ottenere in maniera precisa, li sotto sarebbe comparso il tempo reale).
In questo frangente ho notato un passo indietro rispetto all’MCC Classico: nella casella del requested period non ero costretto a scrivere il tempo in secondi ma potevo scriverlo nell’unità di misura che volevo, specificando poi l’unità di misura. Ovvero potevo scrivere 1ms oppure 1000uS… Mi piacerebbe che questa cosa venisse ripristinata perchè era molto comoda.
Chiudiamo quindi questa finestra. Adesso, anche se il blocco del Timer2 non compare nell’application builder (non saprei dire se è un bug o è una cosa voluta dal momento che in questo specifico caso il Timer2 è al completo servizio del modulo PWM), dobbiamo comunque settarlo dal momento che la gran parte della funzionalità del PWM dipende dalle impostazioni di tale Timer. Semplicemente dalla finestra Project resources clicchiamo una volta sulla voce TMR2
Andiamo quindi nella finestra a destra, dove sono comparse le impostazioni per il Timer2 e impostiamo in questo modo:
Qui abbiamo un’incongruenza e sono sicuro sia un bug: per poter utilizzare il PWM l’unico settaggio possibile come sorgente di clock per il Timer2 è FOSC/4 come specificato sui datasheet mentre di default compare HFINTOSC: lasciando HFINTOSC potete scordarvi di vedere un segnale PWM in uscita (e credetemi, ho sofferto in passato perchè da datasheet mi era sfuggita questa cosa). Cambiamo quindi Clock Source impostandolo a FOSC/4. Nel settaggio di Timer Period ho messo il valore di 32µS (0.000032) : questo valore è utilizzato per impostare il periodo del PWM, per cui con 32µS avremo una frequenza di PWM pari a 31250Hz. Andando ad aprire la configurazione per il PWM vedremo infatti che la frequenza è di 31250Hz, ad ogni modo per il PWM non imposteremo nulla qui.
Abbiamo quasi finito: settiamo gli interrupt cliccando sull’apposito blocco nell’Application Builder. Nella finestra che compare abilitiamo l’interrupt sul Timer0 e disabilitiamo quello sul cambio di stato dei pin (IOCI):
Bene. Non ci resta che configurare gli I/O, che abbiamo lasciato per ultimi. Questo si fa dalla griglia dei pin, Pin Grid View che si trova in basso: cliccando sul lucchettino nella casella che incrocia il nome della funzione con il pin, si fa in modo che quella funzione sia assegnata a quel pin. Se il lucchetto non c’è vuol dire che quella funzione non può essere assegnata a quel pin o a nessuno se è una funzione che non prevede input/output. Nel mio caso ho impostato la griglia in questo modo:
Notiamo innanzitutto che ho selezionato PDIP8 nel campo Package dato che è questo il formato che andrò ad utilizzare (in questo caso specifico i numeri di pin del formato PDPIP8 coincidono con quelli degli altri 2 formati a disposizione, ma per altri PIC® questo non è sempre vero, per cui fate sempre attenzione a selezionare il package che avete intenzione di utilizzare, anche se questo influisce soltanto sul numero di pin visualizzato in alto e sull’immagine del pinout, non sul codice ovviamente). Le impostazioni dei pin, ricapitolando, sono:
- Uscita del modulo PWM3 su pin RA0
- Funzionalità di Clock Out su RA4 (questo è stato impostato automaticamente dalla configurazione)
- RA5 impostato come uscita (sull’input il lucchetto è su sfondo arancio per indicare che lo stesso pin non può essere sia ingresso che uscita)
RA5 l’ho voluto mettere per far lampeggiare un eventuale led come test di funzionamento. Vedremo anche che, per quanto detto prima relativamente alla questione di MCLR, se proviamo a chiudere il lucchetto tra RESET e RA3 in automatico viene cambiata anche la configurazione per fare in modo che questa condizione sia possibile.
Per la configurazione, per così dire, “fine” dell’assegnazione delle porte, clicchiamo sul modulo Pins dell’application builder:
Si presenta una finestra in cui è possibile assegnare un nome a piacere ai pin utilizzati come I/O in modo che le funzioni che andranno ad essere generate avranno quel nome in testa al nome di funzione, facilitandoci le cose, e settare altre funzionalità che per ora non andremo a spiegare/utilizzare:
Vedete che per RA5 ho scritto “LED” nella colonna Custom Name mentre ho lasciato il nome di default per RA0 sul quale uscirà il segnale PWM, dal momento che quel nome comunque non sarà utilizzato. Ho quindi disattivato la funzione analogica dato che a volte viene messa di default.
Altra cosa che manca rispetto all’MCC classico è il fatto che nella finestra del package view, cioè dove compare il pinout del microcontrollore, non viene visualizzato il nome personalizzato affianco al pin. Queste sono tutte cose che, comunque, sono certo che verranno implementate dal momento che l’MCC Melody per le famiglie di PIC® diverse dalla 32 è una cosa abbastanza recente.
Fatto questo possiamo premete il tasto Generate nella finestra delle risorse di progetto:
L’MCC (melody) provvederà quindi a creare tutta una serie di headers, files c, nonchè il main.c sfruttando le informazioni fornite. Possiamo quindi chiudere l’MCC premendo sull’apposito tasto:
Apriamo il main.c: dalla finestra Projects espandiamo Source Files e diamo doppio click su main.c
Vediamo che la funzione main ha soltanto attiva uan funzione SYSTEM_Initialize() che richiama le varie funzioni per settare I/O e periferiche:
int main(void) { SYSTEM_Initialize(); // If using interrupts in PIC18 High/Low Priority Mode you need to enable the Global High and Low Interrupts // If using interrupts in PIC Mid-Range Compatibility Mode you need to enable the Global and Peripheral Interrupts // Use the following macros to: // Enable the Global Interrupts //INTERRUPT_GlobalInterruptEnable(); // Disable the Global Interrupts //INTERRUPT_GlobalInterruptDisable(); // Enable the Peripheral Interrupts //INTERRUPT_PeripheralInterruptEnable(); // Disable the Peripheral Interrupts //INTERRUPT_PeripheralInterruptDisable(); while(1) { } } |
Leggiamo che le note riportano dei suggerimenti riguardo gli interrupt: dato che ci interessa utilizzare l’interrupt sul Timer0, andremo a togliere il commento dalla funzione INTERRUPT_GlobalInterruptEnable() che farà in modo da abilitare gli interrupt globali e toglieremo il commento anche da INTERRUPT_PeripheralInterruptEnable() dato che il Timer0 è una periferica e dobbiamo abilitare gli interrupt per il sottosistema delle periferiche.
Per scrivere la funzione di gestione dell’interrupt, rispetto alle precedenti versioni dell’MCC, adesso c’è un sistema più raffinato che utilizza le strutture. Andiamo ad aprire il file in cui sono state generate le funzioni per il Timer0: Source Files > MCC generated Files > Timer > src > tmr0.c
Si, avete notato? Rispetto alle versioni precedenti le cartelle ora sono più annidate: ora c’è una cartella per ogni periferica
Vediamo che all’inizio del file è definita una struttura che ci permette di trattare la periferica un po’ come se fosse un oggetto, munito di proprietà:
const struct TMR_INTERFACE Timer0 = { .Initialize = TMR0_Initialize, .Start = TMR0_Start, .Stop = TMR0_Stop, .PeriodCountSet = TMR0_Reload, .TimeoutCallbackRegister = TMR0_OverflowCallbackRegister, .Tasks = NULL }; |
Come si usa sta roba? All’inizio del nostro main.c, prima della funzione main, quindi all’esterno di qualsiasi altra funzione, scriviamo:
static void Timer0_Callback(void); static const struct TMR_INTERFACE *Tmr0 = &Timer0; |
La prima riga è un prototipo di funzione, ovvero una funzione vuota che serve per indicare al parser che prima o poi ci sarà una funzione con quel nome, che andremo a definire più in basso. Nella seconda riga sto definendo una struttura a cui ho dato il nome di Tmr0 e questo oggetto è di tipo TMR_INTERFACE (che è una struttura definita nel file timer_interface.h contenuto in Header Files > MCC Generated Files > Timer ed è chiaramente valida per qualsiasi Timer, non solo lo zero). In aggiunta gli dico di ricopiare pari pari gli attributi dalla struttura, già definita da MCC, che si chiama Timer0. In questo modo avrò un mio oggetto Tmr0 che ha di default le stesse funzioni dell’oggetto Timer0 che potrò personalizzare. In particolare andremo a personalizzare proprio la funzione di interrupt, per questo motivo nel main andremo a scrivere:
Tmr0->TimeoutCallbackRegister(Timer0_Callback); |
Ovvero assegno alla proprietà TimeoutCallbackRegisterdel mio Tmr0 la funzione Timer0_Callback che per ora abbiamo definito solo come prototipo. Come/dove viene definita la funzione di interrupt per il Timer0? Tutto parte dal file interrupt.c (Source Files > MCC generated files > system > src):
void __interrupt() INTERRUPT_InterruptManager (void) { // interrupt handler if(INTCONbits.PEIE == 1) { if(PIE0bits.TMR0IE == 1 && PIR0bits.TMR0IF == 1) { Timer0_OverflowISR(); } |
Vediamo che l’interrupt viene intercettato come si faceva una volta utilizzando la parola chiave __interrupt e facendo un controllo dei flag: nel caso in cui sia stato rilevato un interrupt sul Timer0 dovuto all’overflow (TMR0IF) e sia stato abilitato l’interrupt su di esso (TMR0IE => si fa questo controllo perchè l’interrupt potrebbe essere stato generato da altro dato che i PIC16 hanno un solo vettore per tutte le interruzioni), viene richiamata la funzione Timer0_OverflowISR(): questa funzione è contenuta in timer0.c:
void TMR0_OverflowISR(void) { //Clear the TMR0 interrupt flag PIR0bits.TMR0IF = 0; if(TMR0_OverflowCallback) { TMR0_OverflowCallback(); } } |
E vediamo che questa già resetta il flag di interrupt, dopodichè se l’utente ha definito una propria funzione (una callback) da eseguire al verificarsi dell’interrupt (TMR0_OverflowCallback) allora viene richiamata. In realtà TMR0_OverflowCallback è solo un segnaposto che viene definito, sempre in tmr0.c, qui:
void TMR0_OverflowCallbackRegister(void (* CallbackHandler)(void)) { TMR0_OverflowCallback = CallbackHandler; } |
Ovvero verrà impostata, tramite la funzione TMR0_OverflowCallbackRegister una funzione di callback che viene messa al posto di TMR0_OverflowCallback. Di default questa funzione di callback viene impostata nell’inizializzazione del timer0 (funzione TMR0_Initialize(void)) ad un valore predefinito:
TMR0_OverflowCallbackRegister(TMR0_DefaultOverflowCallback); |
Ovvero esiste la funzione TMR0_DefaultOverflowCallback che è una funzione richiamata di default, che attualmente è vuota (questo perchè nei settaggi del Timer0 non abbiamo impostato un parametro che avrebbe fatto in modo che la nostra funzione personalizzata di interrupt anzichè essere richiamata ad ogni singolo overflow, poteva essere richiamata ogni numero x di overflows. In pratica, quando andiamo ad impostare la proprietà TimeoutCallbackRegister della struttura del timer,:
.TimeoutCallbackRegister = TMR0_OverflowCallbackRegister |
di fatto andiamo a sovrascrivere l’impostazione di default (TMR0_OverflowCallbackRegister) con la nostra funzione personalizzata.
Ammetto che questo ghirigoro possa apparire molto ostico per chi è abituato a programmare alla vecchia maniera, e non posso biasimarlo: ha perfettamente ragione, ma una volta capito il meccanismo vi assicuro che consente davvero di scrivere molto meno codice dal momento che la gran parte è già stata fatta in maniera sicuramente eccelsa e senza errori e poi qui io vi ho solo spiegato da dove viene fuori tutta quella roba, ma voi sostanzialmente dovrete solo definire la vostra funzione, tra l’altro nemmeno più preoccupandovi di resettare i flag di interrupt dato che viene fatto già nelle funzioni generate dall’MCC, per cui tutto questo spiegone potete anche saltarlo.
Tutto il codice strutturato in automatico in questa maniera è (dovrebbe essere – non ho ancora provato) compatibile con qualsiasi famiglia di PIC: se vogliamo portare il codice su un’altra architettura andremo semplicemente a ricopiare le parti di codice che abbiamo creato noi facendo ricreare all’MCC tutta sta roba, per questo, anche per questioni di manutenzione/upgrade del nostro codice non dobbiamo MAI andare a modificare il codice generato dall’MCC perchè vuol dire ritrovarsi con programmi che non funzionano.
In un punto qualsiasi del main andrò finalmente a scrivere la mia funzione da eseguire durante l’interrupt. Innanzitutto faccio lampeggiare un led ogni 500mS, perchè, a parte che il led che lampeggia è l’hello world per eccellenza, io lo uso proprio per vedere che il programma in qualche modo è vivo. Definisco quindi come sempre un contatore (come static in modo che sia inizializzato solo la prima volta) e lo imposto a 100 per poi decrementarlo ad ogni interrupt e quindi invertire lo stato del led:
static uint16_t tmrled=500; tmrled--; if (tmrled==0) { LED_Toggle(); tmrled=500; } |
Avete visto che c’è quel LED_Toggle()? Questa è una funzione creata in automatico dall’MCC per il pin che ho chiamato LED (le funzioni usabili con gli I/O si trovano tutte nel file pins.h): altro codice risparmiato! Nella funzione di interrupt mi definisco altre due variabili:
static uint16_t DelayForChangeDutyCounter=DELAYFORCHANGEDUTY; static bool DutyDirection=true; |
La prima è un contatore che utilizzo per cambiare il duty cycle del PWM ogni DELAYFORCHANGEDUTY millisecondi (stesso sistema che uso per far lampeggiare il led insomma, questo valore costante l’ho definito in testa come 5). La seconda è una variabile che uso per fare l’effetto ping-pong ovvero quando il valore è true decremento il duty cycle, al contrario quando è false lo aumento:
DelayForChangeDutyCounter--; if (DelayForChangeDutyCounter==0) { DelayForChangeDutyCounter=DELAYFORCHANGEDUTY; if (DutyDirection) { DutyCycleVal-=5; } else { DutyCycleVal+=5; } if ((DutyCycleVal<=DUTYMIN) || (DutyCycleVal>=DUTYMAX)) { DutyDirection^=1; } PWM3_LoadDutyValue(DutyCycleVal); } |
Quando il contatore arriva a zero, per prima cosa lo reimposto e poi vario il valore di duty cycle in base alla direzione. Quando il valore di duty cycle è arrivato al valore minimo oppure al valore massimo, allora inverto la direzione. La funzione PWM3_LoadDutyValue() è stata definita dall’MCC in pwm3.c e non fa altro che caricare il valore a 16 bit nei due registri (e pure questa ci ha risparmiato di impazzire su come convertire il numero per infilarlo nei registri: imparerete ad apprezzare l’MCC come ho fatto io, che pure ero molto scettico!).
Ricapitolando: abbiamo un interrupt ogni millisecondo sul Timer0. Ogni 500ms cambia stato il led su RA5 e ogni 5millisecondi variamo il valore di duty cycle: aggiungiamo 5 se il flag di direzione è falso, sottraiamo 5 se è vero. Quando il valore di duty cycle raggiunge il minimo o il massimo impostato, invertiamo il flag di direzione: così facendo il duty cycle aumenta, fino ad arrivare al massimo e quindi diminuisce fino ad arrivare al minimo e così via. Il progetto MPLAB X è comunque allegato alla fine dell’articolo.
Programmare il PIC16F15214
Anche questo nuovo PIC16F ad 8 pin segue la stessa piedinatura dei PIC12F, per cui i pin destinati alla programmazione si trovano tutti nella stessa identica posizione: io sto difatti ancora utilizzando il mio stravecchissimo adattatore che vi feci vedere ben 15 anni fa!!! Questa è una ulteriore dimostrazione di come i microcontrollori PIC® siano ancora qui nonostante gli anni che passano e la valanga di dispositivi che continuano ad uscire: perchè la Microchip® ha sempre comunque saputo rinnovarsi e stare al passo coi tempi senza mai perdere di vista anche una tradizione che ha fornito basi solide.
Ad ogni modo è possibile programmare questo PIC® in particolare con i programmatori a partire dal PICkit™4 (già il 3 non può), quindi PICkit™4, PICkit™5 e MPLAB® Snap
Vi riporto, per semplicità, il collegamento da effettuare:
Questi programmatori hanno il connettore ad 8 pin per cui, tenendo conto che il primo pin, quello indicato dalla freccia, è sempre MCLR, gli ultimi 2 pin del programmatore non saranno connessi.
Una buona alternativa per poter programmare i PIC® nuovi, se non volete spendere troppo, è appunto l’MPLAB® Snap (viene a costare attualmente circa 30 euro compresa IVA spedizione direttamente dal sito della Microchip) che ha però, come già detto, delle limitazioni:
- Dovete fornire voi la VDD dall’esterno con un alimentatore per alimentare il dispositivo da programmare
- Non si può programmare con la vecchia modalità ad alta tensione (VPP su MCLR), per cui va sempre scelta la programmazione LVP
- Non si può programmare un PIC® che è stato precedentemente programmato con la modalità ad alta tensione
- Non si possono programmare i vecchi PIC® che non supportano la modalità LVP
Schema
I componenti che ho utilizzato sono i seguenti:
- PIC16F15214 I/P (microchip direct)
- Filamento led 3V 130mm (futura)
- Mosfet canale N BS170 (amazon)
- Resistenza da 100Ω (futura)
- Resistenza da 220Ω (futura)
- Led rosso (futura)
Per quanto riguarda il programmatore, se avete intenzione di utilizzare i microcontrollori PIC® in maniera seria, non posso che consigliare il PICkit™5, so che non costa poco e una buona alternativa, come già detto alcune limitazioni, è l’MPLAB® Snap.
Il filamento a led non ha indicazioni su quale siano Anodo e Catodo per cui dovreste provarlo prima di metterlo nel circuito o semplicemente se vedete che non funziona, invertitelo. La resistenza da 100Ω nel mio caso, che ho utilizzato il filamento verde, è forse un po’ troppo alta dato che il filamento può ancora salire come luminosità: il mio consiglio è di fare prima delle prove a parte con un alimentatore a 5V testando varie resistenze a partire da 220Ω e vedere quale meglio si adatta al vostro gusto senza bruciare il filamento.
Considerazioni finali
I più attenti si saranno accorti che alla fine questo progetto è ripreso pari pari da un esempio che feci giusto 12 anni fa per una schedina di sviluppo basata su PIC10F322. Potrete dire che ora la quantità di codice generato è enorme rispetto a quello che si faceva prima: vero, occupa qualche kb di spazio in più sul disco, ma all’atto della compilazione si ottiene sempre la stessa cosa: abbiamo il vantaggio che la gran parte del codice è stata generata in automatico e possiamo riutilizzare il codice in maniera semplice su un altro PIC trasportando solo il nostro main (ed eventuali altri file nostri) lasciando generare tutto il resto dall’MCC Melody.