Newsgroup: it.hobby.elettronica.digitale
Titolo: Usare un ADC con un microcontrollore

Intervenendo nella discussione precedente da me aperta ("Resistenza di 
carico di un ADC"), mi è venuto qualche dubbio sull'utilizzo di un ADC 
mediante un microcontrollore.
Tutti sappiamo la teoria di base del campionamento a frequenza fissa 
(teorema di Nyquist per evitare l'aliasing).
Questo presuppone che la frequenza di campionamento sia costante, magari 
generata da un clock e pilotata da una FPGA o DSP.
In applicazioni più semplici dove bisogna indicare a video alcuni 
segnali lentamente variabili (temperature, tensioni, ecc), mi trovo 
spesso ad utilizzare un comune microcontrollore con ADC interno o ADC 
esterno.
In tutti questi casi non ho mai pensato di andare a campionare su 
interrupt, ma sempre tramite un task del programma che ovviamente viene 
eseguito in maniera diversa a seconda del carico della CPU.
In pratica utilizzo una frequenza di campionamento variabile, sempre 
"sufficientemente" alta rispetto alla banda passante delle misure che 
devo campionare.
Quello che mi chiedo è se tutti i calcoli che si fanno sul filtro di 
antialiasing, sui filtri numerici, ecc. continuino a valere in queste 
condizioni.
Voi come vi comportate quando siete in una situazione del genere? Vi 
preoccupate di campionare a frequenza fissa (mediante interrupt), 
complicando notevolmente il codice (con gli interrupt non si scherza), 
oppure vi limitate come me a campionare ciclicamente in un task non 
real-time?

real-time?
Io personalmente preferisco campionare con interrupt con frequenza di 50  (60)Hz
o multipli della stessa per ridurre l'influenza del rumore di rete che è spesso
la causa maggiore delle imprecisioni di misura, più che la precisione nominale
dell' ADC.
--
ciao
  Stefano

> complicando notevolmente il codice (con gli interrupt non si scherza)
Non si scherza ma se vuoi fare qualcosa di serio c'è poco da fare, 
bisogna usarli. Peraltro non mi sono mai sembrati così tragici :-)
-- 
bikerfab
Viaggi in bicicletta, report foto e informazioni
Travelling by bicycle, report, pics and information
http://bikerfab.altervista.org/

 bisogna usarli. Peraltro non mi sono mai sembrati cos� tragici :-)
E' chiaro, anche io utilizzo spesso gli interrupt per creare timer e 
comunicazioni mediante UART.
Stavo pensando a come fare un campionamento tramite interrupt, per 
esempio alla frequenza di 50Hz (o multipli) come suggerito da SB.
Nell'ISR del timer che governa la conversione A/D dovr� spostare il 
risultato della precedente conversione analogico/digitale da un registro 
ad una locazione in RAM e far ripartire la nuova conversione.
Supponendo temp la variabile intera dichiarata globale che contiene il 
risultato della conversione del segnale di temperatura, nell'ISR ci sar� 
qualcosa del genere:
    temp = ADC_RESULT();
    ADC_START();
Sicuramente da qualche parte del codice la variabile temp viene usata 
per fare qualcosa, per esempio la visualizzazione su un display:
    printfxy(0, 0, "Temp=%d", temp);
Come fare per evitare che conflitti tra l'accesso alla variabile temp 
fatta dalla printfxy() e quello fatto dall'ISR?
Il problema, come al solito, � l'atomicit� dell'operazione di copia 
della variabile temp sullo stack per il passaggio del parametro alla 
funzione printfxy(). Se questa copia sullo stack non � un'operazione 
atomica, potrei essere sfortunato di avere una ISR in mezzo e rovinare 
il valore di temp.

 il valore di temp.
dipende da che tipo di interrupt hai a disposizione. Se l'ADC può 
generare un int di fine conversione (EOC), la cosa più pulita secondo me 
è avere un timer che genera un interrupt alla frequenza di campionamento 
(ad esempio 20ms per 50Hz), nella isr del timer mettere ADC_START() e 
nella isr dell'ADC mettere temp = ADC_RESULT().
Se nella stessa isr dell'adc metti anche la printf hai la garanzia di 
non rovinare niete. Ovviamente la printf deve durare meno del periodo 
dell'interrupt (i 20ms del timer).
interrupt void timer_isr(void)
{
	ADC_START();	// l'EOC lancerà adc_int()
}
interrupt void adc_int(void)
{
	temp = ADC_RESULT();
	//  <<-- se int non innestabili
	//   	 qui in mezzo non si infilerà nessuno.
	// 	 E neanche durante la copia dal registro adc a temp
	printf(temp);
}
Se gli interrupt non sono innestabili (cioè un altro int non può 
interrompere una isr in corso, oppure puoi usare int a priorità diverse) 
hai la garanzia che nessuno entrerà a gamba tesa tra la lettura dell'ADC 
e l'aggiornamento del display (vd. codice sopra).
Se l'adc non ha l'EOC, ma ha ad esempio un flag di fine conversione 
(qualcosa dovrà averlo per dirti che ha finito...) metti tutto nel timer:
// se int non nested, tutto questo codice è "atomico"
interrupt void timer_isr(void)
{
	ADC_START();
	while(!EOC_flag);	// aspetti che l'adc abbia finito
	temp = ADC_RESULT();
	printf(temp);
}
anche in questo caso tutto il codice deve durare meno del periodo del 
timer e hai la garanzia dell'integrità dei dati.
Se metti la printf nel main (o in altro codice che non parte da una isr) 
non puoi avere la garanzia di integrità a meno di non metterla tra un 
disable_int e un enable_int. Lo stesso se gli int sono innestabili. 
Soluzione un po' meno pulita, a mio avviso.
Peraltro, rispetto alla tua affermazione che usare gli int complica 
notevolmente il codice, secondo me invece semplifica.


 Lo stesso se gli int sono innestabili. 
Cosa sono gli interrupt innestabili?

 disable_int e un enable_int.
Sinceramente vorrei evitare di disabilitare/abilitare gli interrupt ogni 
volta che devo utilizzare uno dei valori ottenuti dalla conversione ADC.
Immagina un programma con 10 misure che devono essere usate per vari 
scopi all'interno del programma... ogni volta mi devo ricordare di 
disabilitare gli interrupt... mi sembra troppo "pericoloso"... cosa 
succede se me ne dimentico?

 Cosa sono gli interrupt innestabili?
Immagino intenda interrupt a priorità, dove un evento (per esempio, il 
timer) può interrompere l'esecuzione di una ISR di un altro interrupt 
(per esempio, l'ADC).

 condizioni.
Non bisogna fare confusione: il teorema di Nyquist e tutto quello
che ne consegue si riferisce alla "ricostruzione" del segnale a
partire dai campioni. Se tu vuoi leggere un valore puntuale
(o fare altre cose strane tipo downconversione) non ti interessa
affatto, quindi non hai bisogno di porti problemi di banda e
frequenza di campionamento. Anche se comunque la banda
(analogica) ha poi influenza sul rumore, quindi ti conviene limitarla
il piů possibile in ogni caso se campioni lentamente.
A.D.

 piů che la precisione nominale dell' ADC.
Sei sicuro che questa cosa sia efficace? Lo sarebbe solo se
il tuo riferimento di tempo e di frequenza fosse identico a
quello della rete, ma visto che sia la frequenza di rete sia
l'oscillatore hanno delle derive, e che comunque non saranno
mai sincronizzati, alla fine campioni il battimento tra la tensione
di rete e i tuoi 50 (o 60)Hz interni!
A.D.

 (per esempio, l'ADC).
Non è proprio così. Gli interrupt vengono disabilitati automaticamente,
quando si esegue un interrupt. Se il programmatore decide di farlo, può
riabilitare gli interrupt, permettendo quindi l'esecuzione di handler per
interrupt a priorità più alta.
Non ricordo CPU in cui si possa decidere di non disabilitare
automaticamente gli interrupt, né casi in cui, riabilitandoli, vengano
processati anche interrupt a priorità più bassa. Anche perché ciò potrebbe
permettere di processare lo stesso interrupt in modo rientrante, operazione
foriera di disastri.

 l'oscillatore hanno delle derive
Ci mancherebbe ancora che la frequenza di rete avesse derive.
Sai che corti tra una centrale e l'altra!

 partire dai campioni.
Esattamente. Quindi se mandi all'A/D una frequenza più alta hai il famoso
aliasing, che ti fa prendere lucciole per lanterne senza alcuna possibilità
di distinguere fra le due.

 foriera di disastri.
Giusto un esempio, la famiglia PIC18:
"The PIC18F2320/4320 devices have multiple interrupt
sources and an interrupt priority feature that allows
each interrupt source to be assigned a high-priority
level or a low-priority level. The high-priority interrupt
vector is at 000008h and the low-priority interrupt vec-
tor is at 000018h. High-priority interrupt events will
interrupt any low-priority interrupts that may be in
progress."

 progress."
Salvo che gli interrupt non siano disabilitati.
Lo fa automaticamente rispondendo ad un interrupt, immagino, o no?

 Cosa sono gli interrupt innestabili?
nested interrupt (forse ho tradotto male :) )
Quando una isr ne può interrompere un'altra (io li ho sempre visti 
chiamare così).


 chiamare così).
Annidati. O, di solito, nested. :-)

 succede se me ne dimentico?
non DEVI dimenticartene :-)
Altrimenti facciamo come in Java che c'è la garbage collection, così se 
ti dimentichi di liberare la memoria prima o poi ci pensa lui. Troppo 
facile :-)
Detto questo, mi sono reso conto che è un po' tardi, spero di non aver 
scritto troppe stupidaggini :-)


 Annidati. O, di solito, nested. :-)
si, proprio quelli :-)


F. Bertolazzi <TOGL...@MAIUSCOLEtdd.it> wrote in message
1lh8ijl3l6mzo.g...@40tude.net...

La rete è perfettamente sincronizzata con se stessa (per le ragioni
che dici tu), ma se la confronti con uno standard di tempo o frequenza
vedrai che ha delle derive e anche notevoli, ossia la frequenza può
spostarsi leggermente in avanti e indietro. Questo è dovuto al fatto
che i generatori stessi sono influenzati dalle condizioni di
funzionamento
della rete, ad esempio dal carico che si ha in un certo periodo.
Ma perfino nell'ipotesi che la frequenza fosse perfettamente stabile,
riuscire a tenere sincronizzato un'oscillatore indipendente è
praticamente
impossibile.
A.D.



Hai tagliato la frase seguente che spiegava il concetto (per lo meno 
come l'ho capito io):
"Se tu vuoi leggere un valore puntuale [...] non ti interessa
affatto, quindi non hai bisogno di porti problemi di banda e
frequenza di campionamento"
L'aliasing lo vedi quando cerchi di ricostruire l'andamento del segnale 
convertito. Ma l'A/D legge puntualmente l'ingresso e ti restituisce 
correttamente il valore presente. Stop.
Quindi se vuoi sapere il valore di vin al tempo t, vai e ti prendi il 
campione al tempo t, indipendentemente dalla frequenza di campionamento 
e da quella di ingresso.
Marco

di rete e i tuoi 50 (o 60)Hz interni!
Diciamo che in teoria � vero che se c'� una piccola differenza tra la frequenza
di rete e la frequenza di campionamento avr� comunque un battimento, per�
all'atto pratico meglio usare questo sistema che fare misurazioni non
sincronizzate alla rete come dice l'op.
Anzi, in un applicazione dove non potevo filtrare troppo, facendo un
acquisizione a 400Hz e dividendo per 8 ho ottenuto una discreta reiezione del
rumore di rete con poco spreco di risorse e di tempo.
Lo so che non � una situazione perfetta, ma funziona abbastanza bene.
--
ciao
  Stefano

 Lo so che non è una situazione perfetta, ma funziona abbastanza bene.
In alternativa, si può anche derivare il sincronismo direttamente dalla 
rete con un rilevatore di passaggio per lo zero.
Marco

 Lo fa automaticamente rispondendo ad un interrupt, immagino, o no?
Penso di no, leggendo l'ultima frase.

rete con un rilevatore di passaggio per lo zero.
In quel caso tutto dipende dalla precisione del rilevamento di zero, che può a
sua volta essere influenzato dai disturbi.
Se hai avuto un orologio sincronizzato con la rete sai di cosa parlo.
Io ne costruii uno in kit di NE tanti anni fa, questo inevitabilmente avanzava
di qualche secondo al giorno nonostante un sincronismo di rete fatto con un
monostabile per ridurre la possibilità di beccare delle spurie, e avanzava non
per la poca stabilità della frequenza di rete.
In ogni caso se il µC è sincronizzato con un quarzo la stabilità del segnale è
garantita in poche parti per milione, nel breve termine le cose migliorano
addirittura per l'inerzia temica del sistema, quindi è una questione soprattutto
teorica o di lana caprina, come si usa dire.
Difatti se teoricamente è vero che si può avere un battimento, nella pratica non
ho mai notato effetti nefasti.
--
ciao
  Stefano

 Penso di no, leggendo l'ultima frase.
A me non sembra una deduzione legittima.
Immagino che altrove, nello stesso DS o in una AN a parte siano più chiari.
Oppure fai una prova.

 praticamente impossibile.
Dipende per quanto tempo e con quale precisione. ;-)

 frequenza di campionamento"
Gi�.

 ho mai notato effetti nefasti.
Altrimenti non si capirebbe perch� facciano degli A/D sigma delta con
reiezione molto forte a 50 e/o 60 Hz, il tutto addirittura basato sul loro
oscillatore RC interno.

 frequenza di campionamento"
Ammetto di esse3re stato preso dalla pigrizia: avevo iniziato a scrivere
una cosa del genere, poi ho visto che l'ha scritta CG (il suo precedente
intervento nel thread "resistenza di carico di un ADC" me lo sono salvato e
ti esorto a fare altrettanto) poco sotto:
------------------------------------
Il rumore incombe sempre, purtroppo... quindi il filtro, alla fine, serve 
non molto a limitare la banda del segnale utile ma a ripulire un po' tutto 
quello che arriva per via "condotta". Se acquisisci un segnale da una 
termocoppia, dove variazioni di uV causano variazioni di temperatura 
importanti, puoi notare molto meglio quanto sia importante l'antialiasing 
(il valore in Vrms del rumore, che spesso non è solo rumore bianco
gaussiano a media nulla, potrebbe essere confrontabile col segnale)
------------------------------------

 sono scritti i risultati di uno o piů interrupt.
Anche in questo caso, perň, bisogna disabilitare gli interrupt quando si va
a decrementare questo contatore. Salvo che il contatore non sia in un
registro della CPU.

 registro della CPU.
Scusassero, ma quello che faccio io � eresia?
Ovvero, posto che la necessit� sull'isocronia del campionamento � 
limitata al campionamento stesso (e non sull'elaborazione), io 
acquisisco dall'A/D nell'isr (es. timer) poi abilito un flag che mi 
indica il termine dell'acquisizione stessa.
Nel main, con calma, mi prendo la variabile dell'ADC e ci faccio quello 
che voglio: tipicamente filtro e visualizzazione o trasmissione.
In soldoni:
#define ADC_READY	0
volatile int flag;
volatile int adc;
isr timer() {
	adc = read_adc();
	flag |= (1 << ADC_READY);
	/* meglio controllare anche il valore di flag
	e segnalare se ancora attivo */	
}
int main() {
.
.
.
	for (;;) {
		if (flag & (1 << ADC_READY)) {
			flag &= ~(1 << ADC_READY);
			mia_var = adc;
			.
			.
			.
		}
	}
}
In questo modo acquisisco il segnale a intervalli regolari, mantengo la 
isr molto breve, eseguo le operazioni lunghe in tutta comodit� e non 
disabilito gli interrupt.
Naturalmente deve valere la condizione che il main loop sia (ben) pi� 
rapido dell'intervallo di campionamento. Ma in caso contrario ci 
sarebbero problemi ben pi� gravi da risolvere :)
C'� qualche controindicazione in questo modo di operare che non vedo?
Marco

 indica il termine dell'acquisizione stessa.
Io faccio sempre cos�. Ma pozz, se ti ricordi, parte da un punto di vista
paranoico. Porello, usa i PIC... ;-)
Se non sono certo che la main sia *sempre* abbastanza veloce controllo,
nell'interrupt handler, se il flag � ancora settato. In tal caso ritorno
senza far nulla.
Anzi, ora che ci penso, � meglio cos�: la main setta un flag di richiesta
nuovi dati, l'interrupt handler lo resetta quando ha fatto o non 
fa nulla se � gi� resettato, la main quando lo trova resettato processa il
dato e poi setta il flag.

 dato e poi setta il flag.
Mumble, e perché mai?
A meno che l'acquisizione non sia one-shot (cioè appunto a richiesta) ma 
periodica non vedo molto senso nel doppio flag.
Nel senso, se l'acquisizione è periodica è un evento dell'applicazione 
non un capriccio del main.
Marco

 non un capriccio del main.
Ebbeh, se la main nun j'a fà a processarlo (nell'esempio sopra devo fare
moltiplicazioni e divisioni float!), nun j'a fà.

 moltiplicazioni e divisioni float!), nun j'a f�.
E acc�ttati un ARM a sto punto! :) :) :)
Marco

 Oppure fai una prova.
In realt� io non uso questo PIC. L'ho usato come esempio di un 
microcontrollore che ha gli interrupt a priorit�, argomento tra l'altro 
introdotto nella discussione non da me, ma da bikerfab.
Quindi evito di andare a controllare bene cosa faccia questo micro :-)

 paranoico. Porello, usa i PIC... ;-)
Hmmm... dici a me? In realtà non uso i PIC (forse ti riferisci 
all'esempio di architettura con interrupt a priorità?).
E comunque, di quale "punto di vista paranoico" parli? Forse quello di 
non voler disabilitare gli interrupt ad ogni accesso a variabili 
modificabili in una ISR?
L'approccio di Marco Trapanese mi sembra in effetti semplice ed efficace 
e, tra l'altro, sarebbe compatibile con la mia paranoia :-)
Infatti non si disabilitano mai gli interrupt con questo approccio.

 E acc�ttati un ARM a sto punto! :) :) :)
Ess�, cos� poi me li devo saldare a manina pin a pin.
E comunque non mi serve che sia pi� veloce. E, se un giorno capitasse anche
a te, ora sai come fare.

 Infatti non si disabilitano mai gli interrupt con questo approccio.
Sempre che la main ce la faccia. Il mio sistema costa pochissimo di più e
ti garantisce da eventuali imprevisti (ad esempio una modifica custom che
richiede un output più elaborato o su di un canale più lento).
Anzi, a pensarci meglio, boccio la soluzione di Marco.
E' comunque meglio la mia.
Al costo di quattro istruzioni hai la garanzia di non leggere minchiate.
E comunque la tua domanda originale era diversa.

oscillatore RC interno.
Giusto, è solo una questione di lana caprina.
--
ciao
  Stefano

 Cosa sono gli interrupt innestabili?
"Nested", immagino. Quando sei dentro un int può essere scatenato un 
altro int; da qualche parte assegni la priorita'.  

 E comunque la tua domanda originale era diversa.
In effetti ...