Tutorial · Beginner · 30 min lezen

Analoge spanningen uitlezen: ADC-referenties en delers

analogRead geeft een verhouding terug, geen spanning. Kies een referentie die niet inzakt, dimensioneer een deler en bouw een batterijmonitor die niet liegt.

Inleiding

analogRead() is de meest vriendelijke functie op een Arduino en tegelijk een van de meest stiekem misleidende. Ze geeft een getal tussen 0 en 1023 terug, iedereen behandelt dat getal als een spanning, en meestal gaat er niets mis.

Op een robot gaat het wél mis, want een robot heeft motoren, en motoren bewegen precies datgene waartegen dat getal wordt gemeten.

analogRead geeft een verhouding terug

De ADC meet geen volt. Hij vergelijkt zijn ingang met een referentie en meldt de verhouding:

reading = 1023 × V_in / V_ref

Op een Arduino Uno is V_ref standaard AVCC — de 5 V-rail van het bord. analogRead() beantwoordt dus eigenlijk de vraag “welk deel van de 5 V-rail is dit?”, en omrekenen naar volt door met 5/1023 te vermenigvuldigen is alleen juist zolang de rail exact 5 V is.

Je weet al dat dat niet zo is, zodra de motoren draaien.

De referentie is de fout

Sluit een batterij via een deler aan op A0 en kijk naar het getal terwijl de motoren starten.

Twee grafieken boven elkaar. Het bovenste toont ADC-tellingen tegen pakketspanning van 6 tot 8,4 volt voor twee ontwerpen: een 5-voltsrailreferentie met een deler van 20k/10k oplopend van ongeveer 409 tot 573 tellingen, en een interne referentie van 1,1 volt met een deler van 68k/10k oplopend van ongeveer 715 tot 1002 tellingen. De onderste grafiek toont de batterijspanning die elk ontwerp meldt voor een constante 7,4 volt terwijl de werkelijke 5-voltsrail varieert van 4,2 tot 5,2 volt: het ontwerp dat op de rail is gebaseerd daalt van 8,8 volt naar 7,1 volt terwijl het ontwerp met interne referentie vlak op 7,4 blijft.
Onder: het pakket beweegt nooit. Terwijl de rail door motorbelasting inzakt van 5,0 V naar 4,2 V, meldt code die een 5 V-referentie vertrouwt dat de batterij oploopt van 7,4 V naar 8,8 V. De bandgapreferentie meldt de hele tijd 7,40 V. Boven: dezelfde 10 bits verspreid over 286 tellingen in plaats van 164, omdat de deler zo gedimensioneerd was dat hij de referentie volledig vult. SVG downloaden

Een laagspanningsuitschakeling die op het eerste ontwerp gebouwd is doet precies het omgekeerde van zijn taak: hoe harder de robot werkt, hoe gezonder de batterij eruitziet. En de fout is stil, want elke afzonderlijke uitlezing is een volstrekt geldige verhouding.

De oplossing is een referentie die niet beweegt. De ATmega328P heeft een 1,1 V-bandgapreferentie aan boord:

analogReference(INTERNAL);   // 1.1 V on an Uno; INTERNAL1V1 on a Mega
analogRead(A0);              // discard — the reference needs a moment to settle

Gooi de eerste uitlezing altijd weg nadat je van referentie hebt gewisseld. De interne referentie en de ingangscapacitor hebben allebei tijd nodig, en het eerste monster wordt genomen voordat ze die hebben gehad.

De deler dimensioneren

De referentie is nu 1,1 V en je pakket is 8,4 V, dus de deler moet de bovenkant van het bereik net onder de referentie brengen:

V_adc = V_pack × R2 / (R1 + R2)

Met R1 = 68 kΩ en R2 = 10 kΩ is de verhouding 0,128, dus een vol pakket van 8,4 V levert 1,077 V — net onder de referentie, met bijna de hele schaal benut. Drie getallen die je bij elke deler die je ontwerpt moet controleren:

Controle Deze deler Waarom
Bovenkant van het bereik tegenover de referentie 1,077 V tegenover 1,1 V erboven en hij klapt plat
Ruststroom 108 µA draint 2,6 mAh per dag — verwaarloosbaar tegenover 2000 mAh
Uitgangsimpedantie (R1∥R2) 8,7 kΩ houd onder ~10 kΩ, zie hieronder

Vergelijk dat met de naïeve versie — een deler van 20 kΩ/10 kΩ die tegen de 5 V-rail wordt uitgelezen. Die zet een vol pakket op 2,80 V, oftewel 573 tellingen van de mogelijke 1023, en gebruikt 164 tellingen over het hele bereik van 6,0–8,4 V van het pakket, tegenover 286 bij het betere ontwerp. Bijna tweemaal zoveel resolutie, voor dezelfde twee weerstanden en één regel code.

Resolutie is geen nauwkeurigheid

Tien bits tegen een 5 V-referentie is 4,88 mV per stap; tegen de 1,1 V-referentie is het 1,07 mV. Geen van beide getallen is je nauwkeurigheid.

Wat je werkelijk beperkt:

  • Tolerantie van de referentie. De bandgap is nominaal 1,1 V en varieert enkele procenten tussen chips. Dat is een vaste schaalfout, dus meet hem één keer per bord: voer een bekende spanning toe, lees de tellingen en los de echte referentie op. Sla die constante op. Dit is een kalibratiestap, geen optionele verfijning.
  • Ruis. De voeding van een robot is lawaai, en middelen is het goedkope antwoord. Het middelen van N monsters reduceert toevallige ruis met √N — 16 monsters is 4× stiller, één extra bit bruikbare resolutie. Het doet niets voor een schaalfout.
  • Bronimpedantie. De ADC laadt een kleine sample-en-houdcondensator op via alles waaraan hij gekoppeld is. Een bron met hoge impedantie kan die niet in de toegestane tijd vullen, en de restjes van het eerder gelezen kanaal zitten er nog in.

Die laatste levert een kenmerkende bug op die het waard is om te benoemen.

Kanalen die hun buren nadoen

Lees A0 en A1 om de beurt met bronnen van hoge impedantie en elke uitlezing wordt naar de vorige getrokken. Beweeg een potentiometer over A0 en zie A1 meebewegen. Er is niets verkeerd bedraad.

Twee oplossingen, en je kunt ze allebei gebruiken:

int readSettled(uint8_t pin) {
  analogRead(pin);        // charge the sample-and-hold on this channel
  return analogRead(pin); // then take the one you keep
}

Twee keer lezen en de eerste weggooien geeft de condensator een tweede meetvenster om de nieuwe spanning te bereiken. De andere oplossing is de bronimpedantie verlagen — en daarom gebruikt de deler hierboven 68 kΩ/10 kΩ in plaats van de nettere 100 kΩ/100 kΩ die een uitgangsimpedantie van 50 kΩ oplevert.

Waar dit op een robot opduikt

  • Batterijbewaking, zoals hierboven — en dat is wat een laagspanningsuitschakeling betrouwbaar genoeg maakt om een Li-ionpakket te beschermen.
  • Analoge lijnsensoren. Een IR-reflectiesensor-reeks levert per sensor een continue waarde, en dat is wat een lijnvolger een gladde positie laat berekenen in plaats van een links/rechts-vlag. Die sensoren worden uitgelezen tegen dezelfde rail die de motoren laten inzakken, en dat is een reden dat de kalibratie van een lijnvolger drift zodra de batterij leegloopt.
  • Stroommeting, een shunt of Hall-sensor waarvan de uitgang een spanning is.
  • Potentiometers als afstelknoppen, de vriendelijkste manier om een PID-versterking af te stellen zonder opnieuw te flashen.
  • Niet voor een 5 V-sensor aan een 3,3 V-bord. Een ESP32 of Raspberry Pi heeft de deler nodig voor bescherming, niet alleen voor bereik.

Het gaat mis

Symptoom Meestal
Batterijwaarde stijgt zodra de motoren starten gekoppeld aan de inzakkende rail; gebruik INTERNAL
Uitlezing vastgezet op 1023 ingang boven de referentie — delerverhouding te hoog
Eén kanaal echot het eerder gelezen kanaal bronimpedantie te hoog; lees tweemaal en gooi de eerste weg
Uitlezingen springen tientallen tellingen heen en weer voedingsruis; middel en controleer de voedingsrails
Spanning consequent een paar procent af ongekalibreerde interne referentie — meet hem één keer
Eerste uitlezing na analogReference() is fout verwacht; gooi hem weg
Kalibratie van de lijnsensor drift zodra de batterij leegloopt rail-gekoppelde uitlezingen op een vallende rail
Slechts 15% van het bereik gebruikt deler gedimensioneerd voor 5 V terwijl je tegen 1,1 V meet

De regel die de meeste hiervan voorkomt: beslis wat je referentie is voordat je iets anders beslist. Elk getal dat de ADC je geeft is relatief ten opzichte daarvan, dus een bewegende referentie maakt van elke uitlezing een verhaal over twee dingen tegelijk.

Verken de graaf

Onderdeel van deze bouwprojecten

Projecten en leerroutes waarin deze tutorial voorkomt.

Verder lezen

Bronnen