LABORATORUL 05

Convertorul analog-numeric (ADC)

Durată: 2 ore Suport: Capitolul 5 Regiștri: ADMUX · ADCSRA · ADC PDF îndrumar EN English version

Majoritatea mărimilor fizice - temperatură, lumină, presiune, poziție - sunt continue. Convertorul analog-numeric le transformă în valori discrete pe care microcontrolerul le poate prelucra, cu o precizie limitată de rezoluție și de tensiunea de referință aleasă.

1Obiectivele lucrării

  • Înțelegerea conversiei prin aproximări succesive
  • Calculul rezoluției, al cuantei și al erorii de cuantificare
  • Alegerea corectă a tensiunii de referință și a prescalerului
  • Citirea unui potențiometru și a unui senzor analogic
  • Conversia valorii digitale înapoi în mărimea fizică măsurată
  • Reducerea zgomotului prin mediere

2Materiale necesare

  • 1 Arduino Uno
  • 1 Breadboard
  • 1 Potențiometru 10 kΩ
  • 1 Fotorezistor (LDR)
  • 1 Rezistor 10 kΩ
  • 1 Senzor LM35 (opțional)
  • 6 Fire jumper

3Principiul conversiei

ATmega328P folosește un convertor SAR (Successive Approximation Register). Principiul seamănă cu cântărirea cu greutăți: se testează pe rând fiecare bit, începând cu cel mai semnificativ, și se păstrează dacă tensiunea generată intern nu depășește intrarea.

Vref 0 V_in bit 9 bit 8 bit 7 bit 6 … bit 0 prea mare → bit = 0 încape → bit = 1 aproximarea converge spre V_in în 10 pași
Fig. 1 - Conversia prin aproximări succesive: la fiecare pas se înjumătățește intervalul de căutare. Pentru 10 biți sunt necesare exact 10 comparații.

4Parametrii convertorului

ParametruValoare pe ATmega328PSemnificație
Rezoluție10 biți1024 de niveluri distincte (0…1023)
Canale6 (A0…A5)multiplexate - un singur convertor comutat între intrări
V_ref implicit5 V (AVCC)poate fi comutat pe 1,1 V intern sau pe AREF extern
Durată conversie13 cicluri ADC≈ 104 µs la prescaler 128
Prima conversie25 cicluriinclude inițializarea circuitului analogic
Frecvență recomandată50–200 kHzpeste 200 kHz rezoluția efectivă scade sub 10 biți
Impedanță sursă recomandatăsub 10 kΩpeste această valoare, condensatorul de eșantionare nu se încarcă complet
Relația fundamentală
ADC = V_in · 2ⁿ / V_ref
Pentru 10 biți și V_ref = 5 V: ADC = V_in · 1024 / 5, iar o unitate reprezintă 5 / 1024 = 4,88 mV - aceasta este cuanta sau LSB-ul.

5Simulator de conversie

Reglați tensiunea de intrare și observați valoarea digitală, cuanta și eroarea de cuantificare. Graficul arată caracteristica în trepte a convertorului față de dreapta ideală. Schimbați prescalerul și urmăriți avertizarea când ieșiți din domeniul recomandat.

Convertor analog-numeric - calcul interactiv

6Alegerea tensiunii de referință

Precizia depinde direct de V_ref: cu cât domeniul este mai îngust, cu atât fiecare pas este mai fin. Un senzor care produce 0–1 V, citit cu V_ref = 5 V, folosește doar 205 din cele 1024 de niveluri disponibile - se irosesc peste 80% din rezoluție.

V_refCuantă (LSB)Selectare în ArduinoPotrivit pentru
5,0 V (AVCC)4,88 mVanalogReference(DEFAULT)potențiometre, divizoare 0–5 V
1,1 V (intern)1,07 mVanalogReference(INTERNAL)LM35, termocupluri, semnale mici
extern (AREF)V_AREF / 1024analogReference(EXTERNAL)referință de precizie externă
Pericol la referința externăLa folosirea analogReference(EXTERNAL), tensiunea trebuie aplicată pe pinul AREF înainte de prima citire. Altfel se poate scurtcircuita referința internă cu cea externă și se distruge microcontrolerul.
Prima citire după schimbarea referințeiCircuitul intern are nevoie de timp să se stabilizeze. Prima valoare citită după analogReference() este nesigură - se ignoră.

7Regiștrii ADC

Modificați biții și urmăriți valorile rezultate. Configurația implicită corespunde canalului A0, referință AVCC, convertor pornit, prescaler 128.

ADMUX și ADCSRA - configurarea convertorului
Registrul ADMUX
Fig. 3 - Registrul ADMUX: REFS1:REFS0 aleg referința, ADLAR alinierea rezultatului, iar MUX3:MUX0 canalul analogic. Bitul 4 nu este folosit. Fig. 5.1 din îndrumar
Registrul ADCSRA
Fig. 4 - Registrul ADCSRA: ADEN pornește convertorul, ADSC declanșează o conversie, ADIF este fanionul de terminare, iar ADPS2:ADPS0 dau prescalerul. Fig. 5.2 din îndrumar
BitRegistruRol
REFS1:REFS0ADMUX00 = AREF extern · 01 = AVCC (5 V) · 11 = 1,1 V intern
ADLARADMUXaliniere la stânga - util pentru citirea rapidă pe 8 biți
MUX3:MUX0ADMUXselectează canalul: 0000 = A0 … 0101 = A5
ADENADCSRApornește modulul ADC
ADSCADCSRAdeclanșează o conversie; revine la 0 când s-a terminat
ADIEADCSRAactivează întreruperea la terminarea conversiei
ADPS2:ADPS0ADCSRAprescaler: 111 = 128 → 16 MHz / 128 = 125 kHz
Ordinea citirii pe 10 bițiRezultatul se află în ADCL și ADCH. Trebuie citit întâi ADCL: hardware-ul blochează registrul până când se citește și ADCH. Inversarea ordinii poate produce valori din conversii diferite. În C, folosirea variabilei ADC face acest lucru automat și corect.

8Simulator: secvența de conversie

Urmăriți pas cu pas ce se întâmplă la nivel de registru în timpul unei conversii declanșate manual, de la selectarea canalului până la citirea rezultatului.

O conversie completă, la nivel de bit
Varianta neblocantăBucla while (ADCSRA & (1 << ADSC)) blochează procesorul 104 µs. Pentru aplicații care nu își permit asta, se activează ADIE și se tratează ADC_vect: conversia rulează în fundal, iar ISR-ul preia rezultatul.

9Scheme de montaj

Arduino Uno 5V A0 GND 10k cursorul furnizează 0…5 V, proporțional cu poziția axului ADC = V_in · 1024 / 5
Fig. 5 - Potențiometrul funcționează ca divizor rezistiv reglabil. Rezistența totală de 10 kΩ respectă recomandarea de impedanță sursă sub 10 kΩ.
Arduino Uno 5V A1 GND LDR 10k divizor rezistiv: V_A1 = 5 · R_fix / (R_LDR + R_fix) întuneric → R_LDR mare → V mic lumină → R_LDR mic → V mare
Fig. 6 - Fotorezistorul într-un divizor de tensiune. Rezistorul fix de 10 kΩ transformă variația de rezistență într-o variație de tensiune, măsurabilă de ADC.
Montaj practic pentru utilizarea modulului ADC
Fig. 7 - Montajul practic pentru utilizarea modulului ADC, cu afișarea rezultatului pe un ecran LCD. Fig. 5.3 din îndrumar

10Reducerea zgomotului

Citirile ADC fluctuează chiar și cu o intrare perfect stabilă, din cauza zgomotului electric intern și extern. Există trei metode uzuale de îmbunătățire:

MetodăEfectCost
Mediere pe N eșantioanezgomotul scade cu √Ntimp: N × 104 µs
Filtru exponențialnetezire continuă, fără tablouo singură înmulțire per citire
Condensator 100 nF pe intrarefiltrează zgomotul de înaltă frecvențăo componentă; încetinește răspunsul
Filtrul exponențial în variantă întreagă medie = medie + (nou - medie) / 8; - echivalent cu o mediere pe aproximativ 8 eșantioane, dar fără tablou și fără float. Împărțirea la o putere a lui 2 se compilează într-o simplă deplasare de biți.

11Cod sursă

Citirea potențiometrului

citire_adc.ino
const uint8_t PIN_POT = A0;

void setup() {
  DDRB = 0x3F;                                  // pinii 8...13: bara de 6 LED-uri
}

void loop() {
  uint16_t brut = analogRead(PIN_POT);          // 0…1023
  float tensiune = brut * 5.0 / 1024.0;         // reconversie in volti
  uint8_t procent = map(brut, 0, 1023, 0, 100);

  // Nu avem inca monitorul serial (lucrarea 6). Aratam rezultatul in binar,
  // pe cei mai semnificativi 6 biti: deplasarea cu 4 lasa 0...63.
  PORTB = brut >> 4;

  delay(200);
}

Configurare directă prin regiștri

adc_registri.ino
void adcInit() {
  ADMUX  = (1 << REFS0);                                  // referinta AVCC (5 V)
  ADCSRA = (1 << ADEN)                                    // porneste convertorul
         | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);    // prescaler 128 -> 125 kHz
}

uint16_t adcRead(uint8_t canal) {
  ADMUX = (ADMUX & 0xF0) | (canal & 0x0F);   // selecteaza canalul, pastreaza referinta
  ADCSRA |= (1 << ADSC);                     // porneste conversia
  while (ADCSRA & (1 << ADSC));              // asteapta terminarea
  return ADC;                                // citeste rezultatul pe 10 biti
}

void setup() {
  DDRB = 0x3F;             // bara de 6 LED-uri pe pinii 8...13
  adcInit();
  adcRead(0);              // prima conversie dureaza mai mult - se ignora
}

void loop() {
  PORTB = adcRead(0) >> 4;   // cei mai semnificativi 6 biti, pe bara
  delay(100);
}

Mediere și filtrare

filtrare.ino
const uint8_t PIN = A0;
const uint8_t N   = 16;          // putere a lui 2 -> impartirea devine deplasare

uint16_t citireMediata() {
  uint32_t suma = 0;
  for (uint8_t i = 0; i < N; i++) suma += analogRead(PIN);
  return suma / N;
}

void setup() {
  DDRB = 0x3F;                   // bara pe pinii 8...13 = valoarea BRUTA
  DDRD |= 0b11111100;            // pinii 2...7        = valoarea FILTRATA
}

void loop() {
  static uint16_t filtrat = 0;

  uint16_t brut   = analogRead(PIN);
  uint16_t mediat = citireMediata();

  // filtru exponential, doar cu intregi
  filtrat = filtrat + ((int16_t)(mediat - filtrat) / 8);

  // Comparati cele doua bare: cea bruta tresare, cea filtrata urmeaza lin.
  PORTB = brut    >> 4;
  PORTD = (PORTD & 0x03) | ((filtrat >> 4) << 2);

  delay(50);
}

Măsurarea temperaturii cu LM35

Senzorul LM35 produce 10 mV pentru fiecare grad Celsius. La 25 °C tensiunea de ieșire este 250 mV, adică doar 51 din cele 1024 de niveluri - un caz clasic în care referința internă de 1,1 V îmbunătățește semnificativ precizia.

lm35.ino
const uint8_t PIN_SENZOR = A1;

void setup() {
  DDRB = 0x3F;                   // bara-termometru pe pinii 8...13
  DDRD |= (1 << PD7);            // LED de alarma pe pinul 7
  analogReference(INTERNAL);     // referinta interna 1,1 V
  delay(10);
  analogRead(PIN_SENZOR);        // prima citire dupa schimbare se ignora
}

void loop() {
  uint32_t suma = 0;
  for (uint8_t i = 0; i < 16; i++) { suma += analogRead(PIN_SENZOR); delay(2); }
  uint16_t brut = suma / 16;

  // varianta cu intregi: temperatura in zecimi de grad
  // (1100 mV / 1024 pasi, iar LM35 da 10 mV pe grad -> mV = zecimi de grad)
  int16_t tempZecimi = (int32_t)brut * 1100 / 1024;
  int16_t tempC = tempZecimi / 10;

  // termometru cu banda: cate un LED la fiecare 10 grade
  uint8_t nrLeduri = tempC / 10;
  if (nrLeduri > 6) nrLeduri = 6;
  PORTB = (1 << nrLeduri) - 1;

  if (tempC > 40) PORTD |=  (1 << PD7);
  else            PORTD &= ~(1 << PD7);

  delay(500);
}

Comanda unui LED în funcție de lumina ambientală

lumina_automata.ino
const uint8_t PIN_LDR = A0;
const uint8_t PIN_LED = 9;        // pin PWM
const uint16_t PRAG_INFERIOR = 300;
const uint16_t PRAG_SUPERIOR = 700;

void setup() {
  pinMode(PIN_LED, OUTPUT);
  DDRB |= 0b00111101;             // bara de martori (fara pinul 9, folosit de PWM)
}

void loop() {
  uint16_t lumina = analogRead(PIN_LDR);

  // cu cat e mai intuneric, cu atat LED-ul lumineaza mai puternic
  uint8_t duty = map(constrain(lumina, PRAG_INFERIOR, PRAG_SUPERIOR),
                     PRAG_INFERIOR, PRAG_SUPERIOR, 255, 0);
  analogWrite(PIN_LED, duty);

  delay(100);
}
De ce constrainFuncția map nu limitează rezultatul la intervalul de ieșire: dacă intrarea depășește pragurile, valoarea returnată iese din 0–255 și produce comportament neașteptat la conversia în uint8_t.

12Montaj interactiv

Convertorul analog-numeric transformă o tensiune într-un număr întreg între 0 și 1023. Mișcați cursoarele de mai jos și priviți simultan: tensiunea aplicată, valoarea brută citită și același număr afișat în binar pe bara de LED-uri.

Conversia, de la tensiune la număr
#include <avr/io.h>
#include <util/delay.h>

unsigned int adcCiteste(unsigned char canal)
{
    ADMUX = (1 << REFS0) | (canal & 0x0F);   // referinta AVCC = 5 V
    ADCSRA |= (1 << ADSC);                   // porneste conversia
    while (ADCSRA & (1 << ADSC)) ;           // asteapta bitul ADSC sa se stearga
    return ADC;
}

int main(void)
{
    DDRB  = 0x3F;              // pinii 8..13 = bara de 6 LED-uri
    DDRD |= (1 << PD7);        // pinul 7 = LED-ul de prag

    ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

    while (1) {
        unsigned int brut = adcCiteste(0);   // 0...1023, pe 10 biti

        /* Avem doar 6 LED-uri, deci aratam cei mai importanti 6 biti:
           deplasarea cu 4 la dreapta lasa 0...63.                    */
        PORTB = brut >> 4;

        if (brut > 700) PORTD |=  (1 << PD7);
        else            PORTD &= ~(1 << PD7);

        _delay_ms(300);
    }
}
De încercatMișcați cursorul: bara arată rezultatul în binar, iar sub ea îl citiți în zecimal. Un pas al convertorului este 5 V / 1024 ≈ 4,9 mV, de aceea valoarea brută sare din unitate în unitate și nu poate reprezenta ceva mai fin. Priviți bitul ADSC din ADCSRA: programul îl pune pe 1 ca să pornească conversia, iar hardware-ul îl șterge singur când rezultatul este gata. Încercați PORTB = brut >> 2; - se pierd biții de sus și bara devine haotică.
Exercițiu - termometru cu LM35
#include <avr/io.h>
#include <util/delay.h>

/* Senzorul LM35 da 10 mV pentru fiecare grad Celsius.
   Deci:  temperatura = tensiune / 0,01 = tensiune * 100          */

int main(void)
{
    DDRB  = 0x3F;              // pinii 8..13 = banda termometrului
    DDRD |= (1 << PD7);        // pinul 7 = LED-ul de alarma
    ADCSRA = (1 << ADEN) | 7;

    while (1) {
        ADMUX = (1 << REFS0);
        ADCSRA |= (1 << ADSC);
        while (ADCSRA & (1 << ADSC)) ;
        unsigned int brut = ADC;

        /* Completati:
           1. Transformati valoarea bruta in tensiune (referinta este 5 V).
           2. Transformati tensiunea in grade Celsius.
           3. Aprindeti pe bara atatea LED-uri cate zeci de grade sunt.
              Indiciu: nrLeduri = temperatura / 10;  apoi
                       PORTB = (1 << nrLeduri) - 1;
           4. Aprindeti LED-ul de pe pinul 7 daca temperatura depaseste 40 de grade. */

        _delay_ms(500);
    }
}
De încercatMișcați cursorul senzorului: cardul vă arată ce tensiune produce LM35-ul la temperatura respectivă și ce valoare brută rezultă. Programul dumneavoastră trebuie să refacă drumul invers. Observați și pierderea de precizie: cu referința de 5 V, un pas al convertorului înseamnă aproape o jumătate de grad. Trucul (1 << n) - 1 aprinde exact primele n LED-uri - este aceeași construcție de mască de la lucrarea a doua.

13Sarcini de lucru

  • Conectați potențiometrul și afișați pe serial valoarea brută, tensiunea și procentul.
  • Determinați experimental cuanta: rotiți lent potențiometrul și observați pasul minim de variație.
  • Realizați montajul cu fotorezistor și rezistor de 10 kΩ; afișați nivelul de iluminare.
  • Comandați intensitatea unui LED prin PWM, invers proporțional cu lumina ambientală.
  • Implementați medierea pe 16 eșantioane și comparați stabilitatea cu varianta fără mediere.
  • Comparați medierea cu filtrul exponențial: care răspunde mai repede la o schimbare bruscă?
  • Comutați referința pe 1,1 V intern și observați cum se modifică valoarea pentru aceeași tensiune.
  • Rescrieți citirea folosind direct regiștrii ADMUX și ADCSRA, fără analogRead().

14Aplicație de aprofundare

Extindere - voltmetru digital cu domeniu extinsFolosiți un divizor rezistiv (de exemplu 100 kΩ și 10 kΩ) pentru a măsura tensiuni de până la 55 V, calculând factorul de divizare în program. Afișați rezultatul cu două zecimale și adăugați o avertizare la depășirea domeniului.

Extindere suplimentară: implementați citirea prin întrerupere (ADC_vect) și măsurați cât timp câștigă procesorul față de varianta cu buclă de așteptare.
SiguranțăNu aplicați niciodată direct pe pinii microcontrolerului tensiuni peste 5 V. Verificați de două ori divizorul rezistiv înainte de conectare și porniți întotdeauna de la tensiuni mici.

15Întrebări de verificare

16Resurse