In questo articolo si vedrà come gestire i numeri in virgola mobile, in un sistema sprovvisto di librerie matematiche decimali o floating-point. L’applicazione principale esaminata sarà quella della conversione di un valore digitale proveniente da un ADC ad un valore analogico in virgola mobile visualizzabile.
Nella programmazione dei microcontrollori, le operazioni matematiche (anche semplici) sono quelle più coinvolte. Se i dati numerici da trattare sono relativamente piccoli e, soprattutto, di tipo intero, non esistono particolari problemi nella gestione. Infatti, tutti i compilatori, di qualsiasi linguaggio, ben supportano le librerie di numeri interi che, peraltro, sono anche implementati nell’architettura interna del sistema.
Purtroppo non tutti i compilatori prevedono l’utilizzo delle operazioni in virgola mobile, mettendo quindi a disagio il programmatore. Per fortuna esistono delle metodologie per superare l’ostacolo
PROGETTO
Supponiamo di dover realizzare un piccolo voltmetro a microcontrollore, come quello mostrato in figura 2. Il sistema deve prevedere un’unità di acquisizione dati, di elaborazione e di presentazione dati all’esterno, ad esempio su un piccolo display LCD. E’ necessaria la presenza di un ADC per poter convertire il valore della tensione analogica esterna in uno digitale. Utilizzando un moderno microcontrollore dotato di ADC e di un buon compilatore con gestione delle variabili in virgola mobile, la realizzazione del sistema è molto semplice. Bastano, infatti, poche righe di codice, indipendentemente dal linguaggio utilizzato, per poter realizzare il tutto.
IL COMPILATORE
Il compilatore utilizzato è ormai il noto microBASIC della MikroElektronika (vedi figura 1). Benché tale compilatore sia provvisto di librerie matematiche e di variabili di tipo Floating Point, noi non le utilizzeremo, limitandoci ad usare le sole variabili intere. Tale metodo, oltre a fornire la soluzione al problema posto dall’articolo, dona al programma una portabilità praticamente totale.
SCHEMA ELETTRICO
Lo schema base è classico: un microcontrollore PIC 16F876 della Microchip, collegato ad una rete oscillatrice formata da un quarzo e da 2 condensatori. La portB è collegata ad un display alfanumerico LCD, formato da 2 righe e 16 colonne. Il piedino RA0, un ingresso analogico, è collegato ad un partitore resistivo variabile, che simula un generatore di tensione variabile, con range compreso tra 0V e 5V. Ruotando il potenziometro infatti si distribuisce, all’ingresso analogico, la tensione da misurare, successivamente visualizzata sul display.
LA CONVERSIONE A/D IN PRATICA
Vediamo come si può determinare il valore della tensione elettrica acquisita, eseguendo una semplice proporzione. Alcuni esempi finali sono mostrati in figura 3 e in figura 4. Come si “pesa” praticamente il valore in input e, soprattutto, come si confronta con altri di valore noto, al fine di far prendere al processore decisioni logiche e comportamenti conseguenti? Il primo parametro da conoscere è la risoluzione, ossia a quanti bit il processore deve campionare il segnale. Supponiamo che esso sia di 10 bit (come negli esempi riportati di seguito). E’ noto che, dalla numerazione binaria, con dieci bit si possono formare e rappresentare 1024 valori diversi, infatti 2^10 dà 1024 combinazioni formate dai simboli 0 e 1. Pertanto con questa risoluzione si possono ottenere 1024 “divisioni” della tensione massima (corrispondenti una risoluzione di ben 4,8 mV). Il secondo parametro da conoscere è il range della tensione da misurare: un valore Vmin determina la tensione minima teorica possibile. Solitamente è fissato a 0 Volt, ossia al potenziale di massa; un valore Vmax, definito anche potenziale di riferimento, o Vref, determina invece la tensione massima teorica possibile, solitamente fissata a 5 volt e, in ogni caso, compatibile con le specifiche del microcontrollore. Si supponga che la tensione incognita misurata generi (dopo un’operazione di Adc) una valore digitale (a 10 bit) pari a 432. Quale rappresentazione analogica avrà questo valore? In altri termini, qual è la tensione misurata ai capi dell’Adc? Basta applicare una semplice equazione:
Vref : VDigMax = x : Digin
dove:
Vref è il potenziale di riferimento o massimo, solitamente fissato a 5 Volt;
VDigMax è il numero massimo di grandezze rappresentabili dal dato digitale acquisito e con una risoluzione di 10 bit esso ammonta a 1024;
Digin è il valore digitale restituito dall’Adc.
da cui si ricava:
5 : 1024 = x : 432 quindi x = 2,109 volt
Pertanto 2,109 volt è il valore analogico corrispondente al valore digitale 432 (a 10 bit). Il programmatore deve riferirsi ad esso per creare condizioni di scelta all’interno del programma scritto.
PRIMO ESEMPIO: VARIABILI A VIRGOLA MOBILE
Prima di affrontare il problema, risolvendolo, vediamo come realizzare il sistema usando un compilatore dotato di operazioni e variabili virgola mobile. Questo per capire come esso, se contraddistinto da caratteristiche valide, aiuta parecchio il programmatore, diminuendogli non poco il lavoro da svolgere. Il fatto che si disponga di variabili a virgola mobile, permette di calcolare istantaneamente qualsiasi valore decimali, come da risultato di divisioni, il tutto con il minimo sforzo. Il listato 1 esegue normalmente la conversione AD a 10 bit da una tensione analogica ad un valore digitale.
rem Acquisizione ADC rem Utilizzando variabili FLOAT rem by Giovanni Di Maria program voltmetro rem Dichiarazione LCD dim LCD_RS as sbit at RB4_bit LCD_EN as sbit at RB5_bit LCD_D4 as sbit at RB0_bit LCD_D5 as sbit at RB1_bit LCD_D6 as sbit at RB2_bit LCD_D7 as sbit at RB3_bit LCD_RS_Direction as sbit at TRISB4_bit LCD_EN_Direction as sbit at TRISB5_bit LCD_D4_Direction as sbit at TRISB0_bit LCD_D5_Direction as sbit at TRISB1_bit LCD_D6_Direction as sbit at TRISB2_bit LCD_D7_Direction as sbit at TRISB3_bit main: dim volt_digitali as word dim volt_analogici as float dim txt as string[16] rem —————-Inizializza LCD——- Lcd_Init() ‘ Initialize Lcd Lcd_Cmd(_LCD_CLEAR) ‘ Clear display Lcd_Cmd(_LCD_CURSOR_OFF) ‘ Cursor off Lcd_Out(1,1,”Voltmetro”) ADC_init() while true volt_digitali = ADC_Read(0) ‘ Conversione AD a 10 bit volt_analogici = volt_digitali * 5 / 1024 FloatToStr(volt_analogici,txt) Lcd_Out(2,1,txt) delay_ms(250) wend end.
Listato 1 |
La semplicità computazionale sta nel fatto che la ri-determinazione del valore analogico da visualizzare è affidata alla variabile in floating-point, pertanto il calcolo della proporzione è eseguito direttamente, senza strategie particolari. L’unico aspetto negativo nell’utilizzo nelle variabili float sta nel fatto che esse “consumano” troppa memoria RAM, per cui non conviene farne un grande utilizzo dei sistemi minimali.
SECONDO ESEMPIO: VARIABILI INTERE
E veniamo al nocciolo dell’articolo. Come visualizzare un valore decimale della tensione (quindi con virgola) se abbiamo a disposizione solamente variabili intere? Semplicemente ricorrendo ad alcuni piccoli accorgimenti della matematica. Anzi, a pensarci bene, un numero con virgola non è altro che una semplice grandezza espressa in una certa base e rappresentata da simboli posizionali. Vediamo quindi come risolvere e superare l’ostacolo. Il problema principale è quello di dover non solo calcolare ma anche di visualizzare un valore della tensione misurata. Data la grande risoluzione dell’Adc, probabilmente tale valore sarà quasi sempre rappresentato da un numero con virgola. Gestione che, con variabili intere, sarebbe impossibile.
DIVISIONE E RESTO
La soluzione, peraltro abbastanza semplice, consente di ottenere la parte intera e la parte decimale del calcolo, in modo che possa essere trattata separatamente e che, soprattutto, possa essere visualizzata sul display. Occorre un metodo che consenta di calcolare la parte intera e la parte decimale della tensione misurata, senza coinvolgere alcuna operazione con virgole (vedi formule di figura 6).
La procedura è la seguente:
ParteIntera = Int(ValoreDigitaleMisurato ÷ 205)
ParteDecimale = Int( (ValoreDigitaleMisurato MOD 205) ÷ 20)
Si nota subito che, con queste due semplici formulette, è possibile ricavare rispettivamente la parte intera e la parte decimale della tensione misurata, partendo dall’unico dato disponibile che è il valore digitale a 10 bit fornito dall’Adc. Relativamente alla prima formula, quella per calcolare la parte intera, c’è da dire che il risultato della divisione è automaticamente intero, dal momento che si utilizzano variabili intere. Non è quindi necessario usare l’operatore INT. Riguardo la seconda formula invece, quella per calcolare la parte decimale, è sufficiente calcolare il resto (MOD) della divisione tra il valore digitale misurato e 205. Il tutto poi diviso 20. Non spieghiamo in questa sede la motivazione matematica, che deriva in ogni caso dall’aritmetica modulare. Ci basta sapere che con questo metodo si risolve brillantemente il problema della visualizzazione dei numeri decimali. Si supponga, come l’esempio precedente, che la tensione incognita misurata generi (dopo un’operazione di Adc) una valore digitale (a 10 bit) pari a 432. Quale rappresentazione analogica avrà questo valore?
Qual è, in altre parole, la tensione misurata ai capi dell’Adc, utilizzando questo secondo metodo con le sole variabili intere?
Sostituendo i valori alle formule otteniamo:
ParteIntera = Int ( 432 ÷ 205 ) [risultato = 2]
ParteDecimale = Int ( (432 MOD 205 ) ÷ 20 ) [risultato = 1]
Pertanto la tensione analogica misurata ai capi dell’Adc equivale a 2,1 V. Implementiamo quindi l’algoritmo in microBASIC e, quindi, sul microcontrollore. Il listato 2, anche se leggermente più complesso del precedente, è abbastanza semplice da capire.
rem Acquisizione ADC rem Utilizzando variabili INTERE rem by Giovanni Di Maria program voltmetro rem Dichiarazione LCD dim LCD_RS as sbit at RB4_bit LCD_EN as sbit at RB5_bit LCD_D4 as sbit at RB0_bit LCD_D5 as sbit at RB1_bit LCD_D6 as sbit at RB2_bit LCD_D7 as sbit at RB3_bit LCD_RS_Direction as sbit at TRISB4_bit LCD_EN_Direction as sbit at TRISB5_bit LCD_D4_Direction as sbit at TRISB0_bit LCD_D5_Direction as sbit at TRISB1_bit LCD_D6_Direction as sbit at TRISB2_bit LCD_D7_Direction as sbit at TRISB3_bit main: dim volt_digitali as word dim parte_intera as word dim parte_decimale as word dim txt_int as string[5] dim txt_dec as string[5] dim volt_definitivi as string[3] rem —————-Inizializza LCD——- Lcd_Init() ‘ Initialize Lcd Lcd_Cmd(_LCD_CLEAR) ‘ Clear display Lcd_Cmd(_LCD_CURSOR_OFF) ‘ Cursor off Lcd_Out(1,1,”Voltmetro INTERO”) ADC_init() volt_definitivi = “xxx” while true volt_digitali = ADC_Read(0) ‘ Conversione AD a 10 bit parte_intera = volt_digitali / 205 parte_decimale = (volt_digitali MOD 205) / 20 WordToStr(parte_intera,txt_int) WordToStr(parte_decimale,txt_dec) volt_definitivi[0] = txt_int[4] volt_definitivi[1] = “.” volt_definitivi[2] = txt_dec[4] Lcd_Out(2,1,volt_definitivi) Lcd_Out(2,5,”volt”) delay_ms(250) wend end.
Listato 2 |
Dopo le dichiarazioni delle variabili e delle porte funzionali del display LCD, in un ciclo infinito viene effettuata la conversione A/D. Il risultato di essa è trasformato rispettivamente nella parte intera e decimale della tensione di ingresso, grazie alle due formule sopra esposte. Quindi esse sono convertite in stringhe (vedi figura 5), per permetterne la gestione a carattere. Infine, viene costruita la stringa finale di visualizzazione, composta da 3 caratteri, tramite assegnazione a singolo byte. La sua visualizzazione e relativa pausa d’attesa completano il programma.
CONCLUSIONI
Come si è potuto vedere, le soluzioni che sembrano insormontabili diventano a portata di mano, utilizzando i metodi e gli stratagemmi più disparati. La programmazione dei microcontrollori aguzza l’ingegno, poiché lo spazio d’ambiente e la memoria sono molto limitati, pertanto occorre far di necessità, virtù.