Corso programmazione PICMicro in C – Lezione 6 – Collegamento di pulsanti, pilotare un led in on/off

Aggiornamento Ottobre 2017
La Microchip ha rilasciato nuovi tool per lo sviluppo: MPLAB X IDE e i compilatori XC. Per far fronte alle novità e per non riscrivere tutti gli articoli daccapo, ho scritto delle lezioni integrative per consentire il passaggio dai vecchi strumenti a quelli nuovi. Le nozioni teoriche riportate negli articoli “vecchi” sono ancora valide. Per quanto concerne la scrittura del codice, l’utilizzo dell’IDE, i codici di esempio ecc, fate riferimento alle nuove lezioni che si trovano nella categoria PICmicro nuovo corso.

led_e_pulsantiIn questa lezione vedremo come vanno collegati (e rilevati) correttamente i pulsanti al nostro microcontrollore. Scriveremo un semplice programma per pilotare 2 led mediante 2 pulsanti: un pulsante accenderà un led non appena premuto (e quindi il led si spegnerà non appena lo si rilascia).

L’altro pulsante, invece, piloterà il secondo led in on/off: premendolo la prima volta, il corrispondente led rimarrà acceso, premendolo la seconda volta, il led si spegnerà.

Anche se questa applicazione sembra una cosa banale, tutto ciò che c’è dietro è molto importante e costituisce la base da cui partire per innumerevoli applicazioni: come si collega un pulsante, il perchè di tale collegamento e il perchè di come se ne legge lo stato logico. Il led è ovviamente una scusa, come in tutte le altre lezioni, per verificare lo stato di funzionamento del programma: è ovvio che un pin che eroga un segnale logico può essere utilizzato per pilotare il relè che accende le luci della stanza o che attiva l’elettrovalvola dell’acqua. Vedremo poi come poter collegare un relè, nel frattempo cerchiamo di capire tutti i retroscena per non ritrovarci con circuiti che si comportano in maniera “bislacca”.

Diamo uno sguardo allo schema che andremo a realizzare:

schema_lezione_6_thumbnail

Come vedete, abbiamo due led (led 1 e led 2), collegati rispettivamente alle porte RD2 e RD3 con le loro resistenze di limitazione di corrente: fin qui nulla di strano, abbiamo già visto questo nelle lezioni precedenti.

Nello schema vediamo anche due pulsanti (del tipo normalmente aperto), identificati come BTN1 e BTN2, e collegati rispettivamente alle porte RD0 e RD1.

Ovviamente, se voi utilizzate una particolare scheda di sviluppo, potete collegare led e pulsanti alle porte che volete, basterà poi modificare il codice sorgente per poterlo adattare al vostro circuito. Lo scopo di avere un file header con unicamente i settaggi serve proprio a questo: avere sott’occhio tutti i parametri di configurazione in un unico luogo, in maniera tale da sapere subito dove mettere mano per fare degli adattamenti, lasciando invariato il programma principale: per adattare il programma all’una o all’altra scheda o all’uno o all’altro chip, basterà selezionare un settings.h diverso o modificare quello esistente.

Cerchiamo di capire come sono collegati questi due pulsanti e soprattutto perchè sono collegati così e non in un altro modo.

Prendiamo in analisi BTN1 (le stesse considerazioni varranno ovviamente anche per l’altro pulsante): vediamo che in condizioni di riposo (pulsante non premuto), il pin RD0 riceve uno stato logico alto tramite la resistenza R4 (da 1KΩ); premendo il pulsante tale linea viene portata a massa: pertanto in condizioni di pulsante premuto, il pin RD0 si troverà a livello logico basso.

La resistenza R4 (così come anche la R5, ma anche la R1 che tiene in condizione di livello logico alto il pin MCLR), viene chiamata resistenza di pull-up. Pull-Up vuol dire letteralmente: tirare verso l’alto. Tale resistenza, difatti, tiene a livello logico alto il pin a cui è collegata.

In alcune applicazioni si trovano anche le resistenze di pull-down, ovvero che tengono un pin in condizione di livello logico basso.

Tenere un pin di input a livello logico alto è importante per tanti motivi: innanzitutto si evita di rimanere il pin in condizioni “volanti”: la porta che utilizziamo come ingresso deve sempre ricevere uno stato logico ben definito: non lo si può lasciare collegato al nulla perchè non si sa come si comporterebbe; inoltre potrebbe captare disturbi che causerebbero comportamenti inaspettati e in alcuni casi anche dannosi reset.

Si capisce, inoltre, che il pin (RD0 nel nostro esempio), per essere tenuto in condizioni di livello logico alto, non può essere  collegato direttamente all’alimentazione: in tal caso, premendo il pulsante, l’alimentazione sarebbe cortocircuitata a massa (distruggendo tutto il circuito), inserendo invece una resistenza (R4) sulla linea che porta il livello alto, tale problema non sussiste in quanto la resistenza limita la corrente di cortocircuito quando il pulsante viene premuto.

Questo è pertanto il modo corretto di collegare un pulsante su un pin:  in condizioni di riposo, arriva sempre livello logico alto tramite una resistenza di pull-up, quando il pulsante viene premuto, questi fa arrivare il livello logico basso , per cui nel software che andremo a scrivere, per verificare se il pulsante è stato premuto, dovremo intercettare un livello logico basso sul pin al quale il pulsante risulta collegato.

Parlando del registro OPTION nella lezione precedente, abbiamo accennato al bit RBPU che abilita le resistenze di pull-up sulle porte B: se intendiamo utilizzare dei pulsanti collegati ad una o più porte B (attenzione: questo discorso  sul PIC16F877 vale solo per le porte B),  non sarà necessario inserire le resistenze di pull-up come in questo schema: basterà semplicemente collegare il pulsante da un lato al pin e dall’altro a massa e quindi abilitare tale bit nel registro option (oppure scrivere: RBPU=0;).

Dal momento che le porte D non hanno questa caratteristica (resistenze di pull-up integrate nel microcontrollore), siamo costretti ad inserire noi le resistenze nel circuito.

Vediamo ora come realizzare il software che si prefigge di fare quanto spiegato nell’introduzione. Come nella lezione precedente, anche in questa i settaggi saranno inclusi in un file header (settings.h) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define BTN1 RD0 // pulsante 1
#define BTN2 RD1 // pulsante 2
#define LED1 RD2 // led 1
#define LED2 RD3 // led 2
 
void settings(void)
 {
 TRISA=0; // Tutte output
 TRISB=0;
 TRISC=0;
 TRISD=0b00000011; // Le porte RD0 e RD1 devono essere input (1) perchè vi sono collegati i pulsanti, le altre output (0)
 TRISE=0;
 
 // All'avvio i 2 led devono essere spenti
 LED1=0;
 LED2=0;
 }

Tramite le direttive #define, abbiamo assegnato nuovi nomi ai pin che ci interessano, in maniera tale che sarà più facile ricordarseli.

Vi è quindi la funzione settings (che verrà poi richiamata nel main) che imposta porte e registri. In questa applicazione non abbiamo bisogno di nessun registro particolare a parte i tristato per le porte. Vediamo che, come sempre, settiamo tutte le porte non utilizzate come uscita, ma questa volta per le porte D dovremo fare un’eccezione: le porte RD0 (primo bit del registro tristato D, ovvero il bit più a destra) e RD1 (secondo bit del registro tristato D) dovranno essere impostate come ingressi (input) dal momento che devono ricevere il segnale inviato dal pulsante, pertanto per queste due porte, il relativo bit nel registro tristato deve essere impostato a 1:

11
TRISD=0b00000011; // Le porte RD0 e RD1 devono essere input (1) perchè vi sono collegati i pulsanti, le altre output (0)

Come vedete i bit 1 (RD0) e 2 (RD1) li impostiamo a 1 (input), gli altri a 0 (output).

Infine, facciamo in modo che all’avvio del programma (momento in cui la funzione settings viene eseguita), i due led si trovino spenti, pertanto mettiamo a zero i relativi bit (le relative porte) :

15
16
LED1=0;
LED2=0;

Bene, passiamo al programma principale (main.c):

Nota: questa parte è stata aggiornata grazie ai preziosi suggerimenti sull’antirimbalzo forniti da Mauro Laurenti.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define XTAL_FREQ 20MHZ // questo è utilizzato dalle routine di ritardo contenute in Delay.C
#include  <pic.h>// contiene i nomi mnemonici di registri e porte
__CONFIG (HS & WDTDIS & PWRTEN & BORDIS & LVPDIS & DUNPROT & WRTEN & DEBUGDIS & UNPROTECT);
#include "delay.c" // routine per ritardi
#include "settings.h" // settaggi del picmicro
 
void main(void)
 {
 settings(); // eseguo la funzione settings contenuta nel file header settings.h, così imposto le porte e i registri
 while(1)
    {
    // controllo pulsante 1
    if (!BTN1) // se pulsante1 premuto (quando è premuto, porta il pin allo stato logico basso)
	{
	DelayMs(100); // ritardo per antirimbalzo
	if (!BTN1) // se dopo 100ms il pulsante è ancora premuto, non si tratta di un rimbalzo
		{
		LED1=1; // accendo led 1
		}
	}
   else
	{
	LED1=0; // se pulsante 1 non premuto, spengo led 1
	}
 
   // controllo pulsante 2
   if (!BTN2) // se pulsante 2 premuto
	{
	DelayMs(100); // antirimbalzo
	if (!BTN2)
	        {
	        LED2=LED2^1; // inverto lo stato del led
		}
	}
    }// Fine ciclo continuo
 } // Fine main

Partiamo a spiegare  dalla riga 13 (penso che tutto ciò che viene prima abbiate imparato a capire a cosa serve): qui stiamo controllando lo stato della porta RD0 (BTN1). Il segno ! davanti a un bit indica negazione, in pratica scrivere

13
if (!BTN1)

equivale a scrivere:

13
if (BTN1==0)

è la stessa, identica, cosa.

Allo stesso modo, quando si deve verificare una condizione logica vera (livello logico alto), anzichè scrivere:

if (port==1)

in genere si utilizza la forma compatta:

if (port)

Ricordo, inoltre, ancora una volta (se non avete letto il manuale Tricky C) che nel linguaggio C, la verifica di uguaglianza viene effettuata col doppio uguale (==) e non con l’uguale singolo come in molti altri linguaggi. In C l’uguale singolo è un operatore di assegnazione e viene utilizzato unicamente per assegnare un valore ad una variabile, se vogliamo confrontare due variabili allora dobbiamo usare il doppio uguale.

Come detto prima, quindi, per verificare che il pulsante sia stato premuto, dobbiamo controllare se la porta a cui è collegato si trova a livello logico basso: se è così, il led1 viene acceso (LED1=1;).

Il ritardo ( DelayMS(100); ) inserito prima ha una funzione importantissima: si tratta di un antirimbalzo software. Cos’è un antirimbalzo?

Bisogna sapere che, quando si preme un pulsante, essendo esso costituito da un meccanismo a molla che mette in contatto due lamelle (che chiudono il contatto), quando lo si preme e poi lo si rilascia, le lamelle continueranno a vibrare (rimbalzare) per alcuni millisecondi andando in on/off centinaia, migliaia di volte nell’arco di un tempo brevissimo: in queste condizioni, essendo il nostro programma molto veloce, rileverebbe continuamente queste transizioni on/off, accendendo e spegnendo il led migliaia di volte al secondo, con il risultato che, alla fine di questo “trambusto” non si saprà se il led rimarrà acceso o spento (si verifica quindi una situazione random). Per tale motivo viene inserito un piccolo ritardo software il quale ci eviterà di ricontrollare lo stato del pulsante prima che i rimbalzi siano finiti. Spesso al posto del termine antirimbalzo si utilizza il termine anglosassone antibounce.

Generalmente un antirimbalzo di 100millisecondi è sufficiente (dipende comunque molto dalla qualità, dal meccanismo e dallo stato di usura del pulsante), molto spesso conviene aumentare tale ritardo per lavorare in sicurezza, oppure ricorrere ad espedienti hardware per evitare i rimbalzi del pulsante.

Come vediamo, dopo l’attesa di 100millisecondi, si ritorna a controllare che il pulsante sia premuto, se è così, allora il led si accenderà.

Per come è strutturata questa parte del programma, il led1 si accenderà non appena si preme il pulsante BTN1, appena lo si rilascia, il led1 si spegnerà, dal momento che abbiamo messo la condizione alternativa (else) che verifica la “non pressione” del pulsante.

Passiamo alla riga 24 nella quale andiamo a controllare lo stato del pulsante BTN2: qui ci comportiamo in maniera differente: invertiamo lo stato del led ad ogni pressione del pulsante, in tal modo premendo BTN2 la prima volta, il led 2 si accenderà e rimarrà acceso (difatti non andiamo a verificare la condizione di “pulsante non premuto” come abbiamo fatto per l’altro pulsante), premendo la seconda volta il pulsante, il led2 si spegnerà e rimarrà spento fino alla prossima pressione.

Come vedete è molto semplice interfacciare un pulsante: si utilizzano le resistenze di pull-up, oppure, se la porta a cui colleghiamo il pulsante ha la possibilità di attivare delle resistenze di pull-up interne, le cose sono ancora più facili a livello circuitale. Inoltre non dobbiamo mai dimenticarci di inserire un piccolo ritardo software per evitare i rimbalzi del pulsante.

Segue un video che illustra il funzionamento del circuito (led rosso=led1, led blu=led2):

come potete notare, al minuto 52, si nota chiaramente che il led2, che deve rimanere acceso, invece si spegne: è chiaro che l’antirimbalzo di 100mS non è stato sufficiente, in questo caso avremmo dovuto aumentarlo un po’ (es: 200/250mS : i pulsanti che ho utilizzato nel video in effetti sono un po’ scadenti).

Downloads

Nota: i programmi di esempio sono stati sviluppati con una versione precedente dell’Hitec-C Compiler, per cui compilati con la nuova versione, restituiscono errori. Fate riferimento a questo articolo per maggiori informazioni su come adattare i vecchi programmi. Consiglio spassionato se volete davvero imparare a programmare: non utilizzate l’include legacy headers, ma imparate a cambiare i nomi mnemonici.

Scarica i sorgenti, programma compilato e schema elettrico:

[download#53]

Lasciate commenti, segnalate errori e/o suggerimenti, e offriteci un caffè se ne avete la possibilità.

» Vai all’ indice delle lezioni e delle risorse del corso di programmazione

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)