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.
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