Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

7
voti

Iniziare coi PIC in C: LED con ritardi fissi e in interrupt del timer0

Per muovere i primi passi coi PIC ho comprato una decina di PIC12F683 per pochi spiccioli. Questi sono i miei appunti per iniziare a programmarli col PICkit3 da zero, avendo solo generiche basi sui microcontrollori.
Verrà fatto lampeggiare un LED prima con ritardi fissi e poi in interrupt col timer0. Occorre che il lettore abbia qualche base di C e sappia cos'è un interrupt.

Indice

Software da installare

MPLAB: è l'IDE ufficiale della Microchip, la ditta che produce i PIC. La versione odierna (15 settembre 2016) è MPLAB X, scaricabile sul sito della Microchip.
XC8: è il compilatore per microcontrollori a 8 bit della Microchip. NON viene installato assieme a MPLAB, per cui va scaricato e installato separatamente. Installatelo dopo MPLAB.

Hardware

PICkit3, Breadboard, jumpers vari, un PIC12F683, un LED, un resistore per la programmazione da 10 kOhm ed uno per il LED da 330 Ohm (o 470 Ohm o 560 Ohm, più il valore è alto meno sarà luminoso il LED).
Alimenteremo il circuito con il PICkit3. In alternativa andrebbero bene un alimentatore da banco o 2/3 pile AA in serie. Attenzione a non alimentare il PIC12F683 con tensioni superiori a 5V.

Caratteristiche del PIC12F683

È un microcontrollore a 8 bit con 3.5 kB di memoria flash, 128 B di RAM e 256 B di EEPROM per i dati.
Arriva a 20 MHz con oscillatore esterno. Dispone di due oscillatori interni:

  • 8 MHz, con postscaler per scendere a 4MHz/2MHz/1MHz/500kHz/250kHz/125kHz.
  • 31 kHz

Lo si può alimentare da 2 V a 5.5 V, assorbe al massimo qualche centinaio di uA.
Ha 6 pin di I/O, comparatore analogico con input/output accessibili dall'esterno, riferimento di tensione interno programmabile, convertitore A/D da 10 bit e 4 canali. Dispone di 3 timer che possono fungere anche da contatori:

  • Timer0, 8 bit con prescaler 1, 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128, 1/256.
  • Timer1, 16 bit con prescaler 1, 1/2, 1/4, 1/8.
  • Timer2, 8 bit con prescaler 1, 1/4, 1/16 e postscaler 1, 1/16.

Ha un modulo di input capture da 16 bit (risoluzione massima 12.5 ns) e di input compare con risoluzione massima 200 ns. Dispone di un'unità PWM da 10 bit, fino a 20 kHz.
Viene venduto in package PDIP, SOIC, DFN, DFN-S, tutti da 8 pin.

Connessioni per la programmazione e l'alimentazione del PIC12F683

Pin del PIC12F683. Omesse diverse funzioni inutilizzate nell

Pin del PIC12F683. Omesse diverse funzioni inutilizzate nell'articolo.

Sarà il PICkit3 ad alimentare il circuito; in alternativa bisognerebbe collegare il PIN 1 a VDD (il + dell'alimentatore o delle batterie) e al PIN 8 GND (il -).

Creare un nuovo progetto su MPLAB, configurandolo per il PICkit3 e il PIC12F683

Assicuratevi di aver collegato il PICkit3 al computer. Effettuate tutti i collegamenti del paragrafo precedente; attenzione, ricordatevi il resistore da 10 kOhm.
Con File, New Project, create un nuovo progetto con MPLAB, cliccate su Microchip Embedded, Standalone project, Next. Nella schermata successiva cercate il PIC12F683 nella tendina Device. Fate attenzione, ci sono diversi modelli con numeri simili. Cliccare su Next. Proseguite lasciando None in Support Debug Header.
Apparirà un elenco col titolo Select Tool, cliccate sul PICkit3, poi su Next.
Apparirà la schermata Select Compiler, cliccate sull'XC8, poi su Next.
Date un nome al progetto in Project Name e concludete; ho chiamato il mio MiniPIC.

Osservate la finestra Projects, sulla sinistra. Se nella cartella Source Files non c'è un main, createne uno così:

Creazione di un main.

Creazione di un main.

Nel resto dell'articolo tutti i codici verranno scritti in questo file.

Bit di configurazione del PIC

C'è un menù molto utile: in MPLAB, aprite Run/Set Configuration Bits. Verrà fuori una schermata in cui si potranno settare alcune opzioni di configurazione. Cliccando su Generate Source Code To Output MPLAB scriverà corrispettivo codice C, da copiare e incollare sopra al main. Impostate così i bit:

Configuration Bits.

Configuration Bits.

Configurare il PICkit3 per alimentare il circuito

Come impostazione predefinita MPLAB non utilizza il PICkit3 come alimentatore.
Da MPLAB aprire Run/Set Project Configuration/Customize. Si aprirà questa schermata, cliccare su PICkit 3 e selezionare Power dalla tendina Option Categories.

Spuntare Power target circuit from PICkit3 e impostare un Voltage level di 4 V. Impostare più di 4.25 V a me ha causato problemi: il pickit non riusciva ad alimentare il PIC12F683 nonostante la corrente assorbita fosse minima.

GPIO del PIC12F683

Il capitolo 4 GPIO PORT del datasheet tratta le porte di Input/Output. Inizia a pagina 33.

Posizioni delle porte GP sul PIC12F683.

Posizioni delle porte GP sul PIC12F683.

Il PIC12F683 ha 6 porte di GPIO. Si può agire indipedentemente su ogni porta, alzandola a VDD ("1") o abbassandola a GND ("0").
Per alzare a VDD una porta il programmatore deve impostare a 1 il bit del registro GPIO che corrisponde alla porta. Tale registro ha 8 bit, l'MSB e l'(MSB-1) sono inutilizzati; il bit 0 si riferisce alla porta GP0, il bit 1 a GP1 e via dicendo, fino a GP6.

Registro GPIO, pagina 33 del datasheet.

Registro GPIO, pagina 33 del datasheet.

In C trattiamo i bit del registro GPIO come variabili già istanziate in precedenza. Non dobbiamo dichiararle, sono "già presenti" e possiamo modificarle. Varrà la stessa cosa per tutti i bit degli altri registri. Alcuni bit in particolare non saranno modificabili dal programmatore.

Alzare a 1 (VDD) la porta GP5

Scrivendo

GP5 = 1;

Alziamo a VDD la porta GP5 al pin 2, perché mettiamo 1 nel bit GP5 del registro GPIO.
Possiamo anche scrivere 1 nell'intero registro GPIO con

GPIO = 1;

ma in tal modo alzeremmo a VDD tutte le porte siccome metteremmo 1 in tutti i bit del registro.

Mascherare GPIO con una OR

Guardando la struttura del registro nell'immagine soprastante si nota come GP5 sia il pin 6 a partire da destra.
Possiamo, come alternativa allo scrivere GP5 = 1, alzare a 1 il sesto bit del registro GPIO con

GPIO = GPIO | 0b00100000;

questa sintassi fa l'operazione di OR bit-a-bit tra il precedente contenuto di GPIO ed il numero binario 00100000. Il numero binario 00100000 è stato scritto con il prefisso 0b, indicandone la base binaria al compilatore. Facendo la OR alziamo a 1 il bit 6 del registro GPIO, sia nel caso in cui fosse già stato 1 sia nel caso in cui fosse 0. Gli altri bit di GPIO non vengono modificati siccome li mettiamo in OR con degli zeri. Porta OR su Wikipedia.
Il numero binario 00100000 corrisponde al numero decimale 32. Avremmo potuto scrivere

GPIO = GPIO | 32

rendendo però il codice meno leggibile. (Perlomeno da me. Penso in base 10).

Abbassare a 0 (GND) la porta GP5

Scrivendo

GP5 = 0;

Abbassiamo a 0 la porta GP5 al pin 2, perché mettiamo 0 nel bit GP5 del registro GPIO.
Valgono gli stessi discorsi fatti in precedenza - ma si maschera in modo diverso:

Mascherare GPIO con una AND

Ricordare che GP5 è il sesto bit del registro GPIO a partire da destra. Possiamo, come alternativa allo scrivere GP5 = 0, abbassare a 0 il sesto bit del registro GPIO con

GPIO = GPIO & 0b11011111;

facciamo la AND bit-a-bit tra GPIO e il numero binario 11011111. Il sesto bit di GPIO viene sicuramente messo a 0, gli altri rimangono del valore che avevano in precedenza perché vengono messi in AND con degli 1. Porta AND su Wikipedia.


Configurare le porte come output o input col registro TRISIO ed registro ANSEL

Registro TRISIO, pagina 34 del datasheet.

Registro TRISIO, pagina 34 del datasheet.

I primi 6 bit del registro TRISIO impostano la direzionalità delle corrispondenti porte. Col bit 1 la porta è configurata come Input tri-state, con 0 come output. Scriveremo

TRISIO = 0;

Per impostare tutte le porte come output, eccetto GP3 che è sempre un input. Notare come i primi due bit del registro TRISIO siano inutilizzati. Attenzione a GP3 che può fungere solo da input.
Nel caso si voglia utilizzare come I/O digitale le porte GP0, GP1, GP2 e GP3 bisogna settare a 0 i bit ANS0, ANS1, ANS2, ANS3 del registro ANSEL:

Registro ANSEL, pagina 35 del datasheet.

Registro ANSEL, pagina 35 del datasheet.

Configurare l'oscillatore interno a 8 MHz

Il datasheet parla del clock alla sezione OSCILLATOR MODULE (WITH FAIL-SAFE CLOCK MONITOR), da pagina 21. Per configurare il clock bisogna impostare i bit del registro OSSCON:

OSCCON, pagina 22.

OSCCON, pagina 22.

Il PIC12F683 funzionerà con l'oscillatore interno a 8 MHz impostando OSTS = 0, SCS = 1, IRCF0 = 1, IRCF1 = 1, IRCF2 = 1.

L'immagine mostra lo scherma che fa arrivare il clock al sistema: i bit IRCF impostano il MUX del prescaler.

Schema dell

Schema dell'origine del clock, pagina 21 del datasheet.

Far lampeggiare il LED con ritardi fissi sulla porta GP5

Connettere il led e il resistore come nello schema, lasciando inalterate tutte le connessioni per la programmazione fatte in precedenza. Attenzione, ricordarsi di abilitare l'alimentazione dal PICkit3, non è un'opzione predefinita di MPLAB.

Il pin 8 del PIC12F683 è GND. Sul pin 2 vi sono GP5, T1CKI, OSC1, CLKIN. Useremo il pin 2 per GP5.
Circuito equivalente con GP5 ad 1 (VDD):

Su un LED rosso cadono circa 1.8 V quand'è acceso, la corrente nel LED sarà
I_{LED} = \frac{V_{DD}-1.8\ V}{330\ \Omega} = \frac{4\ V -1.8\ V}{330\ \Omega} \simeq 7\ mA.

Il programma

#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select bit (MCLR pin function is MCLR)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown Out Detect (BOR enabled)
#pragma config IESO = ON        // Internal External Switchover bit (Internal External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>

void main(int argc, char** argv) {
    OSTS = 0; //A 0 il dispositivo funziona con l'oscillatore interno.
    SCS = 1; //A 1 l'oscillatore è quello interno.
    IRCF0 = 1; IRCF1 = 1; IRCF2 = 1; /*Con 1 1 1 il mux del prescaler
    dell'oscillatore interno prende 8 MHz.*/
    
    TRISIO = 0; //Tutte le porte GPIO come output (Eccetto GP3).
    
    while(1){
        GP5 = 1;
        _delay(1000000);
        GP5 = 0;
        _delay(1000000);
    }
}

La parte sopra al main è stata scritta con lo strumento di configurazione dei bit.
Il microcontrollore esegue ciclicamente tutti i comandi presenti nel ciclo while(1) che va avanti all'infinito. La funzione _delay pone in attesa il microcontrollore per il numero di micro secondi che riceve in input; 1000000 us = 1 s. Con GP5 = 1 accendiamo il LED, con GP5 = 0 lo spegniamo.
Forma d'onda sul pin 2 del PIC12F683:

Far lampeggiare il LED in interrupt col timer0

Il timer0 ha 8 bit, incrementa da 0 a 255. Conta con frequenza

f_{instruction\ cycle} = \frac{f_{oscillatore}}{4} = \frac{8\ MHz}{4} = 2\ MHz..

Ha quindi un periodo di 500 ns. Va in overflow quando finisce di contare, ogni

T_{overflow} = 500\ ns \cdot 256 = 128\ \mu s.

A pagina 43 dal datasheet inizia il capitolo sul timer0. Alla pagina 44 il paragrafo TIMER0 INTERRUPT spiega che ogniqualvolta il timer0 va in overflow viene alzato a 1 il bit T0IF del registro TMR0. Il paragrafo continua specificando che T0IF rimarrà ad 1 finchè il programmatore non lo abbasserà manualmente a 0.
Quindi quando passa un periodo di tempo pari a Toverflow il bit T0IF va ad 1. Se gli interrupt sono abilitati il programmatore può scrivere una ISR che viene richiamata peridicamente con periodo Toverflow.
Attenzione, al termine della ISR il bit T0IF dovrà essere riportato a 0.

Impostare il timer0 e il suo prescaler

Registro OPTION_REG, pagina 45 del datasheet.

Registro OPTION_REG, pagina 45 del datasheet.

Impostiamo il bit T0CS = 0 del registro OPTION_REG in modo che il timer0 non funga da contatore esterno. Con T0CS = 0 conta con il clock del sistema.

Il prescaler

Il prescaler serve a ritardare il timer0. Quando è impostato a 1:2 il timer0 conterà con frequenza dimezzata; il suo periodo di overflow sarà doppio, T_{overflow} = 2 \cdot 128 \mu s = 256 \mu s. Possiamo impostare il prescaler fino a 1:256, ottenendo un periodo di overflow massimo di 32.768 ms. Per impostare il prescaler dobbiamo agire sul registro OPTION_REG: Con il bit PSA = 0 assegnamo il prescaler al timer0, già raddoppiando il periodo di overflow.
Poi impostando i bit PS0, PS1 e PS2 selezioniamo il valore del prescaler desiderato. Già attaccandolo al timer0 con PSA = 0 è come se impostassimo PS0 = 0, PS1 = 0 e PS2 = 0, ottenendo un rapporto 1:2.

Calcolo delle tempistiche col prescaler a 1:256

Con f_{oscillatore} = 8\ MHz il periodo di overflow del timer0 varia a seconda di come impostiamo il prescaler, nel range

128\ \mu s < T_{overflow} < 32.768\ ms

Per far passare 1 s prima di accendere/spegnere il LED bisogna aspettare per forza più di un Toverflow. Potremmo cambiare la frequenza dell'oscillatore ma teniamola a 8 MHz per questi calcoli.
Impostiamo il prescaler a 1:256 con PSA = 0, PS0 = 1, PS1 = 1, PS2 = 1.
Otteniamo un periodo di overflow di 32.768 ms. Nell'arco di 1 s il timer andrà in overflow per

\frac{1\ s}{32.768\ ms} = 30.518\ volte

Una ISR verrà richiamata ad ogni overflow: istanziamo una variabile contatore, aumentiamola di 1 ad ogni ISR ed aspettiamo che arrivi a 30. Appena sale a 30 accendiamo o spegniamo il LED e azzeriamo contatore per poter contare di nuovo. Quindi ogni 30 overflow del timer il LED si accenderà o si spegnerà.
Il LED non resterà acceso (o spento) esattamente per 1 s però il nostro PIC12F683 sarà libero di fare altre cose tra un interrupt e l'altro.

Abilitare gli interrupt

A pagina 15 del datasheet viene descritto il registro INTCON:

Registro INTCON a pagina 15 del datasheet.

Registro INTCON a pagina 15 del datasheet.

Col bit GIE = 1 abilitiamo tutti gli interrupt, con T0IE = 1 abilitiamo quello del timer0.

Il programma

La ISR del programma controlla con if(T0IF == 1 && T0IE == 1) che la fonte dell'interrupt sia il timer0. Con T0IE = 1 gli interrupt del timer0 sono abilitati, con T0IF = 1 sappiamo che è stato il timer0 a far scattare l'interrupt.
È importante aggiornare il contatore all'interno dell'if(T0IF == 1 && T0IE == 1), in tal modo lo incrementeremo soltanto se l'interrupt sarà dovuto al timer0.
Il nome della ISR, Blink_ISR, non è importante nè scelto per un motivo particolare.

#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select bit (MCLR pin function is MCLR)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown Out Detect (BOR enabled)
#pragma config IESO = ON        // Internal External Switchover bit (Internal External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)

#include <xc.h>

void interrupt Blink_ISR(void);

int contatore = 0;

void main(int argc, char** argv) {
    //IMPOSTAZIONI DEL CLOCK
    OSTS = 0; //A 0 il dispositivo funziona con l'oscillatore interno.
    SCS = 1; //A 1 l'oscillatore è quello interno.
    IRCF0 = 1; IRCF1 = 1; IRCF2 = 1; /*Con 1 1 1 il mux del prescaler
    //dell'oscillatore interno prende 8 MHz.*/
    
    //IMPOSTAZIONI DEGLI INTERRUPT
    GIE = 1; //A 1 abilita tutti gli interrupt non mascherabili.
    T0IE = 1; //A 1 abilita gli interrupt del timer0.
    
    //IMPOSTAZIONI DEL TIMER0 E DEL SUO PRESCALER
    T0CS = 0; //A 0 il timer0 come timer (anzichè contatore).
    PSA = 0; //A 0 Assegna il prescaler al timer0 (anzichè al watchdog).
    PS0 = 1;  PS1 = 1;  PS2 = 1; //bit del prescaler
    T0IF = 0; /*Viene alzato a 1 con un overflow del timer0. Messo a 0
    per sicurezza*/
    
    TRISIO = 0; //Tutte le porte GPIO come output (Eccetto GP3).
    
    while(1){
    }
}

void interrupt Blink_ISR(void){
    if(T0IF == 1 && T0IE == 1){
        contatore = contatore +1;
        if(contatore == 30){
            contatore = 0;
            if(GP5 == 0){
                GP5 = 1;
            }
            else {
                GP5 = 0;
            }
        }
        T0IF = 0; //Ripristina il registro.
    }
}

La forma d'onda in uscita sul pin 2 del PIC12F683:

Il periodo è di 1.972 s.

Il periodo è di 1.972 s.

Il LED rimane acceso per 986 ms.

2

Commenti e note

Inserisci un commento

di ,

Grazie dell'appunto, ho provveduto a correggere. Ricordavo male la definizione di polling e non ho pensato a controllarla.

Rispondi

di ,

Il polling non ha niente a che vedere con il lampeggio del LED. Il primo programma usa dei ritardi fissi (da evitare come la peste) punto e basta. Il polling è una tecnica per acquisire gli stati degli ingressi: si tratta di leggere gli ingressi per riconoscerne una transizione invece di usare una interrupt. Per queste gravi imprecisioni non ti ho dato un punto positivo, ma non ti ho dato il punto negativo solo per premiare la buona volontà.

Rispondi

Inserisci un commento

Per inserire commenti è necessario iscriversi ad ElectroYou. Se sei già iscritto, effettua il login.