Corso programmazione PICMicro in C – Approfondimenti – Leggere piu´ canali analogici insieme ed inviare i risultati su seriale

Abbiamo visto in una lezione precedente come utilizzare il convertitore AD, ma alcuni, purtroppo, trovano ancora difficoltà nel capire la questione della selezione del canale da cui effettuare la lettura.

Ricordo difatti che il modulo AD sul pic è uno solo ma abbiamo più canali dai quali poter eseguire la lettura. Il PIC ha in pratica una serie di deviatori che permettono di collegare all’ingresso del modulo AD l’uno o l’altro canale. Vi rimando alla lezione 11 per ulteriori informazioni su questo argomento.

Cercherò quindi con questo esempio di chiarire questa funzionalità eseguendola in una maniera molto efficiente e cioè utilizzando gli interrupt. In aggiunta, dal momento che con la seriale siamo diventati bravi, invieremo i risultati delle nostre letture analogiche sulla RS232 in maniera da leggerli su Hyperterminal o in un programma simile.

In questo esempio utilizzerò come sempre la FreedomII con il PIC16F877A quarzato a 20MHz e collegandovi 3 potenziometri. Una volta capito il concetto è facile aggiungere altre sorgenti analogiche da leggere.

Per facilitarmi le cose ho montato i 3 potenziometri su una scheda a parte come potete vedere nella foto sopra. In cima alla scheda ho montato uno strip di contatti maschio al quale fanno capo i due fili per l’alimentazione e le uscite dei 3 potenziometri. Lo schema di collegamento dei potenziometri è il seguente:

Io ho utilizzato 3 potenziometri da 10KΩ, voi potete usare qualsiasi cosa avete tra le mani, anche 3 trimmer. Non usate valori troppo bassi perchè vi assorbono molta corrente. Come vedete dallo schema i 3 potenziometri hanno le 3 estremità collegate in parallelo verso l’alimentazione. Il pin centrale del potenziometro o del trimmer è quello collegato al cursore e dal quale eseguiamo la lettura del valore di tensione. I cursori dei 3 potenziometri li ho collegati alle porte analogiche RA0, RA1 e RA2.

Sulla Freedom II queste 3 porte sono già utilizzate rispettivamente dal trimmer, dalla fotoresistenza e dalla sonda di temperatura, per cui prima di eseguire questo esperimento abbiate cura di disattivare queste 3 sorgenti spostando i jumpers contrassegnati con LIGHT, TEMP e ANALOG verso destra.

Il sistema che adotterò per eseguire la lettura di 3 input analogici è concettualmente semplice. Sfrutto essenzialmente due sorgenti di interrupt: la fine conversione analogica e l’overflow sul Timer0.

Il codice eseguito alla fine conversione analogica è il seguente:

101
102
103
104
105
106
107
108
109
110
111
112
113
114
if (ADIF)
	{
	valore[canale] = ADRESL + (ADRESH<<8); // valore AD del canale corrente
	canale++; // passo al canale successivo
	if (canale==MAXCHANNELS) // abbiamo finito i canali da leggere
		{
		stampa=1; // abilito la stampa dei valori nel main
		canale=0; // ritorno al primo canale
		}
	ADCON0 &= ADZEROMASK; // azzero il canale
	ADCON0 |= (canale << 3); // seleziono il canale successivo da leggere
	ADIF=0; // resetto il flag di fine conversione A/D
	calcola=1; // abilito il conteggio nel T0IF
	}

La prima cosa che faccio è memorizzare il valore attuale letto dal convertitore in una variabile, più esattamente in un array. Ho infatto creato un array di 3 elementi, chiamato valore[], nel quale memorizzo i valori letti dai 3 canali analogici. Abbiamo quindi un contatore che ci permette di eseguire il passaggio al canale successivo. Quando ho letto tutti e 3 i canali, riparto dal primo e abilito la routine di stampa su seriale che si trova nel main, sfruttando il flag stampa.

Sul PIC16F877A, usato in questa prova, la selezione del canale da cui leggere viene eseguita nel registro ADCON0 (fate riferimento al datasheet del pic in vostro possesso) e precisamente usando i bit 3, 4  e 5 identificati anche come CHS0, CHS1 e CHS2. La prima cosa che faccio è  quindi quella di azzerare lo stato di questi 3 bit, e lo faccio eseguendo un AND con il valore:

#define ADZEROMASK 0b11000111;

Per capire come funziona questa operazione, fate riferimento a questo articolo

Avendo scelto 3 canali in sequenza (per i quali i valori da impostare nei bit CHSx valgono 0,  1 e 2) non faccio altro che eseguire una somma (con l’operatore OR) proprio con il valore numerico del canale, ovviamente spostato (shiftato) a sinistra di 3 bit per riportare il valore nella posizione corretta.

Fate riferimento a questo articolo per capire come si eseguono le operazioni di spostamento dei bit

Fatto questo ho eseguito le mie due operazioni chiave: ho memorizzato il valore letto dal canale precedente e ho impostato il registro di selezione canale per eseguire la lettura sul canale successivo. Infine azzero il flag di interrupt ed abilito un altro flag che ho chiamato calcola.

A cosa serve quest’altro flag? Ho fatto in modo che la temporizzazione necessaria alla scansione successiva dei canali sia eseguita sempre tramite interrupt, sfruttando il caro Timer0. Non dimentichiamoci che le conversioni non devono essere eseguite una dietro l’altra ma bisogna dare un certo tempo necessario affinchè il condensatore di campionamento, CHold, possa caricarsi correttamente. Abbiamo quindi nell’ISR questa parte di codice che si occupa appunto di riavviare la conversione dopo un certo tempo:

86
87
88
89
90
91
92
93
94
95
if (calcola) // possiamo eseguire il conteggio
	{
	timerad++; // counter per avvio conversione
	if (timerad>=TIMEAD) // arrivati a TIMEAD millisecondi
		{
		timerad=0; // azzero il counter
		ADGO=1; // avvio la conversione
		calcola=0; // devo fermare il conteggio fino a che il modulo AD non è pronto di nuovo
		}
	}

Quando l’interrupt di fine conversione mi abilita questa routine, incremento un contatore. Quando tale contatore ha raggiunto un valore prefissato (che ho impostato a 10mS che è il valore della costante TIMEAD), ecco che la conversione viene riavviata (e sarà eseguita sul canale analogico precedentemente impostato nella routine scritta per ADIF).

Nel main abbiamo quindi la stampa su UART dei valori letti che pure eseguo con un ciclo:

43
44
45
46
47
48
49
50
51
52
53
54
if (stampa)
   {
   for (a=0; a<MAXCHANNELS; a++)
     {
     // %d è lo specificatore di formato per i numeri decimali
     // %04d è sempre uno specificatore ma tiene conto di un numero
     // di 4 cifre, le cifre mancanti vengono riempite con zeri
     printf ("Canale %d: %04d&nbsp;&nbsp; ", (a+1), valore[a]);
     }
    printf ("\r"); // solo ritorno carrello, in maniera da ristampare sulla stessa riga
    stampa=0; // azzero il flag di stampa
    }

Diciamo innanzitutto che ho fatto in modo da stampare i valori sempre sulla stessa riga, per questo alla fine della stringa da stampare ho incluso solo il ritorno carrello (\r) e non anche il carattere di line feed (\n). Come vedete ho usato due specificatori di formato (in realtà si tratta sempre dello stesso). La funzione printf, che abbiamo già incontrato molte volte, sostituirà ai simboli speciali preceduti dal carattere % i valori indicati dopo le virgole (al primo simbolo %d sostituirà il valore (a+1) e al secondo simbolo %04d sostituirà valore[a]).

(a+1) identifica il numero di canale letto (scrivo 1 al posto di zero e così via) e valore[a] è il valore letto dal convertitore AD per il canale a-esimo.

Il %d è lo specificatore di formato che permette di stampare un numero decimale, il valore %04d, invece, esegue la stessa cosa ma riempie gli spazi vuoti con zeri fino a formare un numero composto dalle cifre specificate (4 in questo esempio). In pratica il numero 8 sarà stampato come 0008, il numero 12 come 0012 e così via, in maniera da ottenere sempre 4 cifre e mantenere quindi l’allineamento della stringa stampata a video. Impostate il vostro programma terminale per lavorare a 9600,8,n,1 ed otterrete una schermata del genere:

I valori cambieranno man mano che girate i potenziometri e fluttueranno leggermente (per evitare questo potete allungare il tempo tra una lettura e l’altra). Vedrete inoltre il cursore muoversi freneticamente da sinistra verso destra per ristampare la riga: come detto ho incluso solo un ritorno carrello in maniera tale che sia stampato tutto sempre sulla stessa riga. Nel frattempo il led collegato su RD0 lampeggerà.

Nel codice ho incluso anche una routine, vuota, che esegue la lettura dei caratteri ricevuti su interrupt. Come vedete questo programma gira quasi tutto su interrupt e vengono sfruttare 3 diverse sorgenti: il pic non ha nessun problema ad eseguire tutto purchè sia scritto in maniera efficiente.

Curiosità

Ho provato a compilare il sorgente con due diverse versioni dell’ Hitec-C. Premetto che entrambi gli hex prodotti mi funzionano correttamente  senza apportare modifiche al codice.  La versione vecchia (9.65PL1) mi fornisce un codice di 1152 words:

L’ultima versione dell’Hitec-C attualmente disponibile già inclusa con MPLAB, la 9.80, invece mi occupa più memoria programma! (ma un po’ meno memoria dati):

Stranezze della vita!

Downloads

I sorgenti sono abbondantemente commentati.

Leggere 3 canali analogici e inviare i risultati su seriale (552 download)
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 :)