Uso avanzato dell'ADC

I LED sulla scheda demo

In questa pagina viene descritto come utilizzare l'ADC interno al PIC18 insieme al generatore di interrupt e al Timer1 per misure di tensione a frequenza costante. Si consiglia di leggere prima la pagina che descrive il convertitore Analogico Digitale.

Interrupt

In alcuni casi è utile far eseguire una conversione mentre il processore è impegnato nell'eseguire altro codice, al fine di permettere l'esecuzione di più operazioni "contemporanee".

Un primo approccio è mostrato nel codice che trovate a fondo pagina, scritto per PIC18F14K50, ma facilmente adattabile per tutti i PIC18. Possiamo schematizzare l'idea in tre punti:

Sono utilizzati i seguenti clock:

Esaminiamo le tre parti in cui è diviso il codice:

Inizializzazione del convertitore e della gestione delle interrupt

Per quanto riguarda l'inizializzazione dell'ADC, la descrizione è praticamente uguale a quanto già scritto in questa pagina. Una differenza la si trova nella scelta della frequenza del clock dell'ADC:

ADCON2bits.ADCS = 0b110; // A/D Conversion Clock = FOSC/64 (1.33 us @ 48 MHz)

Anche la parte sull'inizializzazione delle interrupt è già stata descritta. Le uniche particolarità sono evidentemente relative al convertitore ADC:

PIE1bits.ADIE = 1; // Enable interrupt from ADC
IPR1bits.ADIP = 1; // Set ADC high priority interrupt
PIR1bits.ADIF = 0; // Clear interrupt status to avoid automatic interrupt at "boot time"

Il main()

Visualizza l'ultimo valore letto dall'ADC, leggendo l'ultimo valore memorizzato nell vettore. Si noti che tale processo:

NB: non è stato implementato alcun meccanismo di sincronizzazione (ma non è questo l'oggetto dell'esempio).

Viene utilizzata una delle funzioni oggetto dell'esercizio 1

La ISR

La ISR legge il valore prodotto dall'ADC e lo memorizza in un vettore. Esaminiamo il codice:

La prima operazione è, come sempre, la verifica della periferica cha ha generato l'interruzione, testando l'apposito bit:

if (PIR1bits.ADIF) // Interrupt from ADC?

Viene quindi letto il risultato della conversione (qui in versione a 8 bit) e memorizzato nel vettore. Si noti che non viene verificata la fine della conversione, in quanto questo evento è implicito nella generazione dell'interrupt.

buffer[samples_in_buffer] = ADRESH; // Read ADC value - 8 bit

Viene quindi avviata una nuova conversione:

ADCON0bits.GO = 1; // Start new conversion

Segue, sempre all'interno della ISR la gestione del vettore: in questo codice banalmente viene incrementato il "puntatore" alla cella successiva, fino al riempimento. Sono mostrate due opzioni alternative per evitare un overflow del vettore: sospensione delle acquisizioni piuttosto che riscrittura dell'inizio.

samples_in_buffer++;                 // Point to next element
if (samples_in_buffer >= SAMPLES) {  // Overflow? - Buffer full?
  //PIE1bits.ADIE = 0;               // Disable interrupt from ADC
  samples_in_buffer = 0;             // "Empty" it!
}

Da non dimenticare, al termine, la cancellazione del flag che ha causato l'interruzione:

PIR1bits.ADIF = 0; // Clear interrupt status

Le prestazioni

Le prestazioni sono state misurate con la stessa tecnica utilizzata per misurare il tempo di conversione. A fondo pagina il codice, in cui le operazione di timing sono attivate lasciando o meno la riga #define MyDEBUG.

Sono stati utilizzati tre bit:

ADC con interrupt

L'ultima riga illustra la sintesi delle attività in corso, istante per istante.

La frequenza di campionamento è poco più di 45 kHz. Tale valore può essere aumentato aumentando la frequenza di funzionamento dell'ADC. Per esempio dividendo il clock del processore per 32 anziché per 64, si arriva a 80 kHz. Si noti che, con questa scelta, la frequenza del clock dell'ADC è, sebbene di pochissimo, fuori dalle specifiche.

Il tempo che rimane per l'esecuzione del main() è pari a circa 2/3 del tempo totale. Tale valore peggiora con la diminuzione della frequenza di clock del processore e con l'aumento della frequenza di clock del convertitore.

ADC, Timer1 e ECCP

Questo esempio utilizza Timer 1 ed il modulo ECCP per avviare automaticamente la conversione dell'ADC. Il fatto che sia l'hardware e non il software a determinare i tempi ha tre vantaggi:

Gli ultimi due punti sono particolarmente importanti nei casi in cui si è interessati al contenuto spettrale di un segnale.

Il codice completo è, al solito, a fondo pagina.

Attenzione! Prima di procedere, una premessa importante: i PIC18F14K50 più vecchi, quelli antecedenti alla versione A7 inclusa, hanno un bug che non permette il funzionamento corretto di questo codice. La descrizione è presente nella Silicon Errata, paragrafo 9.1 Will not Start A/D Conversion; è descritto anche un work around che, sebbene non apporti i benefici più sopra descritti, permette almeno il debug del codice se avete una debug header vecchiotta (come nel mio caso...). Questo aspetto verrà ripreso al temine del paragrafo.

Inizializzazione dell'ADC, di Timer, del modulo ECCP

L'inizializzazione dell'ADC, degli ingressi analogici e della relativa interrupt è uguale a quanto descritto negli altri esempi:

TRISBbits.RB5 = 1;       // Disable digital output buffer on AN11/RB5
ANSELHbits.ANS11 = 1;    // Disable digital input buffer for AN11
ADCON0bits.CHS = 0b1011; // Analog Channel Select AN11
ADCON2bits.ADFM = 0;     // AD values left justified
ADCON2bits.ADCS = 0b110; // AD Conversion Clock = FOSC/64 (1.33 us @ 48 MHz)
ADCON2bits.ACQT = 0b001; // AD Acquisition time set to 2 TAD
ADCON1 = 0;              // Set VDD and VSS as voltage reference
ADCON0bits.ADON = 1;     // Power ON ADC

PIE1bits.ADIE = 1;       // Enable interrupt from ADC
IPR1bits.ADIP = 1;       // ADC Interrupt Priority set to high
PIR1bits.ADIF = 0;       // Clear interrupt status to avoid automatic interrupt ad boot time

Analogamente a quanto già presentato occorre inizializzare Timer1, scegliendo la sorgente del clock (interna, a 12 MHz) ed il valore del prescaler (disattivato):

T1CONbits.T1OSCEN = 0;   // Timer1 oscillator is shut off (not used - Free RC0, RC1 pin)
T1CONbits.TMR1CS = 0;    // Internal clock (FOSC/4)
T1CONbits.T1CKPS = 0b00; // 1:1 Prescale value
T1CONbits.TMR1ON = 1;    // Enables Timer1

Analogamente occorre impostare il modulo ECCP in modo tale che, quando Timer1 raggiunge il valore di 1199, avvengano due eventi:

Questi eventi sono identificati con la modalità trigger special event.

CCP1CONbits.CCP1M = 0b1011;            // Compare mode, trigger special event
CCPR1H = (TIMER1_VALUE & 0xFF00) >> 8; // Set compare registers - H
CCPR1L = (TIMER1_VALUE & 0xFF);        // Set compare registers - L
T3CONbits.T3CCP1 = 0;                  // Timer1 is the clock source for compare

Il valore TIMER1_VALUE è impostato a (1200 - 1), il clock di Timer1 a 12 MHz; quindi la frequenza di campionamento dell'ADC è 10 kHz, con la precisione e la stabilità dell'oscillatore al quarzo usato dal PIC18.

Occorre notare che né Timer1 né ECCP generano interrupt

ISR

La ISR è uguale a quanto già visto e si limita a leggere il "risultato" e visualizzarlo:

if (PIR1bits.ADIF)  // Interrupt from ADC?
{display(ADRESH);   // Display data from ADC
 PIR1bits.ADIF = 0; // Clear interrupt status
}

work around

Come già detto, un bug impedisce al modulo ECCP di avviare l'ADC nei PIC18F14K50 più vecchi. Innanzitutto occorre scoprire se il proprio PIC18 è affetto da tale bug, individuando la sua revisione.

La soluzione proposta per aggirare il problema, sulla falsariga dei fogli tecnici, è quella di far generare al modulo ECCP un interrupt, all'interno del quale avviare la conversione.

Quindi, all'interno della ISR:

if (PIR1bits.CCP1IF == 1) // Interrupt from ECCP ?
{ADCON0bits.GO = 1;       // Start conversion
 PIR1bits.CCP1IF = 0;     // Clear interrupt status
}

Tra le inizializzazioni:

PIE1bits.CCP1IE = 1; // Enable interrupt from CCP1
IPR1bits.CCP1IP = 1; // CCP1 Interrupt Priority set to high
PIR1bits.CCP1IF = 0; // Clear interrupt status to avoid automatic interrupt ad "boot time"

Tale codice è presente come commento nel codice proposto; va attivato solo se il vostro PIC18F14K50 è della serie A7 o A6.

Il codice


Data di creazione di questa pagina: ottobre 2015
Ultima modifica: 27 dicembre 2015


Licenza Creative Commons Attribuzione 4.0 Internazionale


Pagina principaleAccessibilitàNote legaliPosta elettronicaXHTML 1.0 StrictCSS 3

Vai in cima