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.
4Parametrii convertorului
| Parametru | Valoare pe ATmega328P | Semnificație |
|---|---|---|
| Rezoluție | 10 biți | 1024 de niveluri distincte (0…1023) |
| Canale | 6 (A0…A5) | multiplexate - un singur convertor comutat între intrări |
| V_ref implicit | 5 V (AVCC) | poate fi comutat pe 1,1 V intern sau pe AREF extern |
| Durată conversie | 13 cicluri ADC | ≈ 104 µs la prescaler 128 |
| Prima conversie | 25 cicluri | include inițializarea circuitului analogic |
| Frecvență recomandată | 50–200 kHz | peste 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 |
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.
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_ref | Cuantă (LSB) | Selectare în Arduino | Potrivit pentru |
|---|---|---|---|
| 5,0 V (AVCC) | 4,88 mV | analogReference(DEFAULT) | potențiometre, divizoare 0–5 V |
| 1,1 V (intern) | 1,07 mV | analogReference(INTERNAL) | LM35, termocupluri, semnale mici |
| extern (AREF) | V_AREF / 1024 | analogReference(EXTERNAL) | referință de precizie externă |
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.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.
| Bit | Registru | Rol |
|---|---|---|
REFS1:REFS0 | ADMUX | 00 = AREF extern · 01 = AVCC (5 V) · 11 = 1,1 V intern |
ADLAR | ADMUX | aliniere la stânga - util pentru citirea rapidă pe 8 biți |
MUX3:MUX0 | ADMUX | selectează canalul: 0000 = A0 … 0101 = A5 |
ADEN | ADCSRA | pornește modulul ADC |
ADSC | ADCSRA | declanșează o conversie; revine la 0 când s-a terminat |
ADIE | ADCSRA | activează întreruperea la terminarea conversiei |
ADPS2:ADPS0 | ADCSRA | prescaler: 111 = 128 → 16 MHz / 128 = 125 kHz |
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.
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
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ă | Efect | Cost |
|---|---|---|
| Mediere pe N eșantioane | zgomotul scade cu √N | timp: N × 104 µs |
| Filtru exponențial | netezire continuă, fără tablou | o singură înmulțire per citire |
| Condensator 100 nF pe intrare | filtrează zgomotul de înaltă frecvență | o componentă; încetinește răspunsul |
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
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
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
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.
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ă
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);
}
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.
PORTB = brut >> 2; - se pierd biții de sus și bara devine haotică.(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 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.