Beste allen,

Ik kom er maar niet uit om de waarde uit de ADC te vertalen naar spanning in mV.

Controller: ESP32 (M5STACK Basic)
Hardware: Potmeter 10k tussen 0V en 5V voor een bereik van 0 t/m 1100mV (0 t/m 5000mV, maar dat gebruik ik niet), multimeter meet continu deze spanning, aangesloten op GPIO35 van de ESP32.

Software:
UIFlow; Micropython;
Instelling: 12Bits, 0DB (0...1100mV).
Ik lees 100x de waarde in, tel alles bij elkaar op en deel dit door 100.
Het display toont deze waarde, evenals de minimale en maximale waarde waarvan het gemiddelde na 50 metingen van 100ms stabiel is. Die waarde houd ik aan.

Stel ik regel deze af op 500mV. Dan krijg ik een ADC waarde van rond de 1795. Ik reken dit naar mV om met (1100mV/4096)*ADCin (=1795) = 449mV

Dit meet ik:
- Bij 75mV op de ingang (gemeten met multimeter), krijg ik een ADC waarde van 0 en een berekende mV waarde ('mV') van 0'mV'. Zo ook de volgende waarden:
- 100mV in = ADC 98 / 22'mV'
- 250mV in = ADC 730 / 182'mV'
- 700mV in = ADC 2671 / 667'mV'
- 1000mV in = ADC 3864 / 966'mV'
- 1100mV in = ADC 4095 / 1024'mV'

Echter klopt dat dus niet. Ik doe wiskundig misschien iets verkeerd. Er lijkt een verschuiving in te zitten. Ik snap er niks van. Wie kan me helpen? :)

De potmeter is een test. Ik wil een LM35 (10mV/°C) uitlezen.

(1100mV/9600)*ADCin

Hoe kom jij daar aan?
12 bit = 4096 ik zou dus 1100mV / 4096 x de ADC waarde verwchten

- 100mV in = ADC 98 / 22'mV'

1100/4096 * 98 = 25mV hmm dat klopt dus ook niet.

Om wel op 100mV uit komen word het

1100/1024 * 98 = 105mV geen 12bit dus gewoon 10 bit of de waarde 1100 klopt niet.

Maar je andere waarden zijn weer compleet anders.

Heb je een schema en een stukje code?

De adc in een esp is niet geweldig, maar ik vermoed dat er misschien meer aan de hand is.

Hoe kom jij daar aan?
12 bit = 4096 ik zou dus 1100mV / 4096 x de ADC waarde verwchten

Excuus, typfout inderdaad! Zo wordt alles anders...
Geen 9600 maar 4096, klopt.

Maar je andere waarden zijn weer compleet anders.

Precies. De meting begint bij 75mV (wat ik ook vreemd vind, ik zou (1100mV/4096 =) 0,27mV verwachten als laagste waarde.

Ik heb de ADC ingang gemeten als open einde, dan meet ik 1,2V. Met een 100k pulldown wordt dit 0V. De ingang is dus voldoende hoogohmig.

@elmowww:
Ik verwacht dat het maar iets kleins is eigenlijk, waar ik overheen kijk...

Schema:
Ik heb als test enkel een 10k potmeter zoals eerder genoemd met de loper aan GPIO35. Voor het volledige schema zie de website, via de link van de controller in m'n startpost of klik hier.

Code LM35 - V0.5.3 | Automatisch gegenereerd vanuit UIFlow | Geschreven voor de LM35, maar met een potmeter zoals beschreven getest:

from m5stack import *
from m5ui import *
from uiflow import *
import machine
import time


setScreenColor(0x000000)


MinData = None
MaxData = None
DB_Max_mV = None
ADCVar = None
MinTemp = None
MaxTemp = None
Bit = None
Counter = None
MaxAVar = None
TempVar = None
Var = None
i = None



TestLbl = M5TextBox(125, 8, "LM35", lcd.FONT_DejaVu24, 0xf7a700, rotate=0)
RawLbl = M5TextBox(0, 50, "Raw data: 3644", lcd.FONT_DejaVu24, 0xffffff, rotate=0)
MinDataLbl = M5TextBox(0, 80, "Min: 0000", lcd.FONT_DejaVu18, 0xffffff, rotate=0)
MaxDataLbl = M5TextBox(210, 80, "Max: 0000", lcd.FONT_DejaVu18, 0xffffff, rotate=0)
TempLbl = M5TextBox(0, 120, "mV: -", lcd.FONT_DejaVu24, 0xffffff, rotate=0)
MinTempLbl = M5TextBox(0, 150, "Min: 0000", lcd.FONT_DejaVu18, 0xffffff, rotate=0)
MaxTempLbl = M5TextBox(210, 150, "Max: 0000", lcd.FONT_DejaVu18, 0xffffff, rotate=0)
label0 = M5TextBox(266, 224, "Ver 0.5", lcd.FONT_Default, 0xffffff, rotate=0)
Lbl = M5TextBox(0, 224, "-", lcd.FONT_Default, 0xffffff, rotate=0)
DataAvgLbl = M5TextBox(130, 80, "0000", lcd.FONT_DejaVu18, 0x4b4b4b, rotate=0)
TempAvgLbl = M5TextBox(130, 150, "0000", lcd.FONT_DejaVu18, 0x4b4b4b, rotate=0)

from numbers import Number


# Describe this function...
def UpdateDisplay():
  global MinData, MaxData, DB_Max_mV, ADCVar, MinTemp, MaxTemp, Bit, Counter, MaxAVar, TempVar, Var, i, adc0
  RawLbl.setText(str((str('Raw data: ') + str(ADCVar))))
  MaxDataLbl.setText(str((str('Max: ') + str(MaxData))))
  MinDataLbl.setText(str((str('Min: ') + str(MinData))))
  DataAvgLbl.setText(str(int(((MinData + MaxData) / 2))))
  TempLbl.setText(str((str('mV: ') + str(TempVar))))
  MaxTempLbl.setText(str((str('Max: ') + str(MaxTemp))))
  MinTempLbl.setText(str((str('Min: ') + str(MinTemp))))
  TempAvgLbl.setText(str(int(((MinTemp + MaxTemp) / 2))))
  Lbl.setText(str(Counter))


def buttonB_wasPressed():
  global MinData, MaxData, DB_Max_mV, ADCVar, MinTemp, MaxTemp, Bit, Counter, MaxAVar, TempVar, Var, adc0
  speaker.tone(2200, 10)
  MinData = 4095
  MaxData = 0
  MinTemp = 4095
  MaxTemp = 0
  Counter = 0
  DataAvgLbl.setColor(0x333333)
  TempAvgLbl.setColor(0x333333)
  UpdateDisplay()
  pass
btnB.wasPressed(buttonB_wasPressed)


# Pulldown: 100k ext.
# min: 75mV
# max: 1100mV
adc0 = machine.ADC(35)
adc0.atten(machine.ADC.ATTN_0DB)
# (Vmax - Vmin)/2^Resolutie ADC
DB_Max_mV = 1100 - 75
adc0.width(machine.ADC.WIDTH_12BIT)
Bit = 12
MaxAVar = 2 ** Bit
MinData = 2 ** Bit
MaxData = 0
MinTemp = 2 ** Bit
MaxTemp = 0
while True:
  Var = 0
  for i in range(1, 101):
    Var = (Var if isinstance(Var, Number) else 0) + (adc0.read())
  ADCVar = int((Var / 100))
  # Q = MAXVoltage/ 2^resolutie[bits]
  TempVar = int(((DB_Max_mV / MaxAVar) * ADCVar))
  if ADCVar > MaxData:
    MaxData = ADCVar
  elif ADCVar < MinData:
    MinData = ADCVar
  if TempVar > MaxTemp:
    MaxTemp = TempVar
  elif TempVar < MinTemp:
    MinTemp = TempVar
  Counter = (Counter if isinstance(Counter, Number) else 0) + 1
  if Counter == 10:
    DataAvgLbl.setColor(0xffcc00)
    TempAvgLbl.setColor(0xffcc00)
  elif Counter == 50:
    DataAvgLbl.setColor(0x33ff33)
    TempAvgLbl.setColor(0x33ff33)
  UpdateDisplay()
  wait_ms(100)
  wait_ms(2)

Is de vref van een adc niet 3300mv

zou dan adc_raw*(vref/4096) moeten zijn in jou voorbeeld 1446 mv zijn.

O hier iemand die het in een project heeft zitten
https://esp32io.com/tutorials/esp32-lm35-temperature-sensor

Ik zal dat eens proberen. Dat zou natuurlijk kunnen.
Edit: In: 500mV = ADC 1769; 1420'mV'. Dat klopt niet.

[Bericht gewijzigd door OPTOdesign op (35%)]

De Vref van een adc is niet altijd de voedings spanning. Goede adcs hebben meestal een Vref ingang.

De esp32 heeft een interne Vref van ca 1100 mV. Indien niet gecalibreerd kan je daar zelf doen. Zie dit bijv.
https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-referenc…

Hoofdstuk 3.9.1 hier geeft de afwijking na calibratie:
https://www.espressif.com/sites/default/files/documentation/esp32_data…
Bij attn=1 +- 30mV. Maar jij hebt nog geen offset gecorrigeerd denk ik.

Ongecalibreerde nauwkeurigheid en offset krijg ik niet gevonden. Wellicht is dat de prijs die je betaald ;)

Phoeh, nu vraag je me wat.

in de code:
adc0.atten(machine.ADC.ATTN_0DB)

Bijschrift:
Set the attenuation value
atten:0, 2.5, 6, 11(dB)
ATTN_0DB: range: 0 - 1.1 V
ATTN_2_5DB: range 0 - 1.5 V
ATTN_6DB: range: 0 - 2.5 V
ATTN_11DB: range: 0 - 3.9 V

Aha, dat heeft iets weg van jou vraag. Dat lijkt me logischerwijs dus offset (?). Ofsett is dus 0, wat 1100mV als Vref betekend.

Dank voor het meedenken en de linkjes. Ik ga ze bestuderen, het lijkt me wel een goede richting waar wat te halen valt.

Hardware: Potmeter 10k tussen 0V en 5V

Als de ingangsimpedantie van de ESP bv 10k is de potmeter staat op 1/10 ingesteld dan heb je 1K // 9K // 10k. // = parallel, dat geeft een behoorlijke meetfout.

Offset zou makkelijk moeten zijn gewoon de ADC ingang hard aan de gnd hangen.

[Bericht gewijzigd door benleentje op (16%)]

De ingangsimpedantie van de ESP is meer dan 100k. Met een 100k weerstand aan GND geeft een ADC waarde van 0. Zonder zit die te zweven rond een willekeurig getal.
Een LM35 en een pulldown van 100k, trekt wel de adc-ingang naar beneden, maar niet de LM35. Dus de 10k potmeter zit wel goed.

Offset zou makkelijk moeten zijn gewoon de ADC ingang hard aan de gnd hangen.

Dat krijg ik een 0.

Attenuation = verzwakking
Dat heeft niets met offset te maken, maar met de full-range input.

Als je een goede spanningsreferentie aan boord hebt, kan je met een weerstandsdeler de offset en de gain calibreren. Je stelt eigenlijk een lijnvergelijking op:
y = aX + b.
a = gain (factor, ideaal is die dan 1)
b = de offset. Ideaal is die 0.

Hoe steek jet het hardware matig in elkaar:
- Je maakt een weerstandsdeler met 2 uitgangsspanningen, die onder worst-case omstandigheden binnen geldig gebied moeten liggen volgens de datasheet (Echter: specs zijn onvindbaar). Kies voor jouw geval bijv. 300mV en 800mV.
Buffer deze spanningen met een condensatortje 10n/100n, en voer ze naar een niet gebruikte, analoge IO pin.
Een ingang direct aan GND hangen kan niet, omdat een ADC geen negatief resultaat kan hebben, wat nodig is bij een positieve offset.
In jouw geval heb je misschien geen referentie bron nodig, en is de 3.3V nauwkeurig genoeg om je spannignsdeler op aan te sluiten.

Lees deze pin uit in dezelfde configuratie zoals je jouw analoge ingang wilt gebruiken.
Noem deze bijv. cal_low_reading en cal_high_reading.

Vervolgens kan je met de delta tussen deze twee (je weet immers het daadwerkelijke spanningsverschil) de factor a uitrekenen:


const float cal_low_mv = 300; //mV, daadwerkelijke waarde op de ADC pin
const float cal_high_mv = 800; //mV

uint32_t cal_low_reading = DoeAdcConversieOpPinLow(); (bijv 500 dec.)
uint32_t cal_high_reading = DoeAdcConversieOpPinHigh(); (bijv 3123 dec)

float a;

//Reken de factor uit: deltaV / deltaReading
a = (cal_high_mv-cal_low_mv)/(cal_high_reading-cal_low_reading) // = 0.1906214 in dit voorbeeld

//Reken offset uit:
//y = a*X + b. a heb je, en je hebt ook een bekend meetpunt. Immers,
//cal_low_reading moet overeenkomen met cal_low_mv
b = cal_low_mv - (a*cal_low_reading) // 261.87571 in dit voorbeeld.

Nu is je calibratie voltooid, en hoef je enkel a en b te bewaren.

Je kunt de ingangsspanning (ook in mv) berekenen door:
vin = a*reading + b

Jammer dat die ADC zoveel werk nodig heeft.

<Edit: typefout aangepast>

Je origineel gemeten 1795 bij 500mV in komt op 482mV theoretisch, mits de referentiespanning van de ADC 1100mV is. Omdat je die 1100mV ook regelmatig noemt (maar niet als referentiespanning) gok ik dat dit toch je referentiespanning is.

Atmel CPUs hebben ook een ingebouwde referentie van 1.1V. Echter dat is +/- 10%: Niet zo nauwkeurig. Allicht dat dit bij je ESP beter is, maar dan is de afwijking van 482mV vs 500mV van ongeveer 4% niet eens zo slecht.

ADCs hebben wel eens een "offset" fout. Dus dat ze bij 0V in al een uitlezing van 3 geven of dat ze pas bij vref * 3/4096 een andere waarde dan "0" gaan geven.

Ook die LM35 temp sensor kan makkelijk een offset hebben. Het beste is gewoon om de boel te calibreren bij 0 en bij 100 graden.

Probleem met de LM35 is dat die precies 0V moet leveren bij 0 graden celcius. Daar kan ook een niet lineariteit in je ADC zitten. (dus stel je referentiespanning is 4096 mV, dan krijg je 5 uit je adc bij 5mV. Maar als je dan omlaag gaat kan dat dan 4, 3,3,3,2 zijn bij respectievelijk 4,3,2,1 en 0mV aan de ingang. Een MCP9701 is precies hetzelfde chipje behalve dan dat er een bewuste offset in de output zit, zodat je ook 0 en zelfs negatieve temperaturen kan meten (zonder negatieve voeding).

@elmowww:
Heel erg bedankt voor je uitgebreide beschrijving en uitwerking. Ik ga dit binnenkort eens proberen!

@rew:
Dank voor je aanvulling. De 1100mV is de 'attenuation value'. De 'full-range input' zoals elmowww duidelijk maakt. Deze kan ik zelf instellen. Zie screenshot in de bijlage.

Bij 500mV is de afwijking niet heel groot, ergens zijn de waardes, de ingang en de berekende spanning vanuit de ADC, aan elkaar gelijk. In het lage gebied geeft de ADC veel minder aan dan werkelijk en in het hoge gebied zit het heel dicht tegen elkaar aan. Helaas kunnen we dus niet over een vaste afwijking (van 4%) spreken.

Met de offset is iets vreemds aan de hand: De ADC geeft een waarde van 0 bij 0V in. Deze blijft stabiel en wordt ook niet negatief. Echter, pas bij een ingangsspanning van meer dan 75mV gaat de ADC waarde schommelen tussen 0 en 2 (het gemiddelde van 100 monsters). Dit zou naar mijn idee al bij een lagere spanning moeten zijn.
Edit: De datasheet bied hierbij de uitkomst: De ingang moet meer dan 100mV zijn (tabel 8). De afwijking kan dus 46mV afwijken. Dat is een ruim begrip voor een LM35: bijna 5°C.

Ook die LM35 temp sensor kan makkelijk een offset hebben. Het beste is gewoon om de boel te calibreren bij 0 en bij 100 graden.

Probleem met de LM35 is dat die precies 0V moet leveren bij 0 graden celcius. Daar kan ook een niet lineariteit in je ADC zitten. (dus stel je referentiespanning is 4096 mV, dan krijg je 5 uit je adc bij 5mV. Maar als je dan omlaag gaat kan dat dan 4, 3,3,3,2 zijn bij respectievelijk 4,3,2,1 en 0mV aan de ingang. Een MCP9701 is precies hetzelfde chipje behalve dan dat er een bewuste offset in de output zit, zodat je ook 0 en zelfs negatieve temperaturen kan meten (zonder negatieve voeding).

Voor nu doet dit er niet toe. Ik heb enkel een potmeter die een spanning aan de ingang voor de ADC levert.

Als het trucje van elmowww niet voldoende werkt, is kalibratie zoals jij noemt ook een optie. Echter moet er een spanning te meten zijn naar mijn idee. Als ik daar een stukje code voor kan schrijven, kan ik dat voor alle projecten gebruiken wat je ook op de ADC aansluit zonder altijd te moeten kalibreren wat niet altijd mogelijk is en ook z'n onzuiverheden kent.

Heb je een capje (10-100nF) heel dicht bij de ingang van de microcontroller gezet? Ik weet niet wat de ADC van de ESP32 precies doet, maar bij sommige controller wordt de interne sample-and-hold condensator ontladen, opgeladen tot de halve referentiespanning, of hij blijft op de spanning van de vorige meting. Zonder condensator dicht bij de ingang krijg je de sample-and-hold condensator niet goed opgeladen in de korte tijd dat hij verbonden is met de ingang, en bij je potmeter is de uitgangsimpedantie (Norton weerstand) afhankelijk van de stand van de potmeter, dus dan is het logisch dat de meting veranderd over het bereik.

En inderdaad, de ESP32 ADC doet het slecht aan beide uiteinden van het bereik, daar tussenin zou het nog redelijk lineair moeten zijn. Het is wel ongeveer de slechtste ADC die ik ooit in een microcontroller heb gezien.

[Bericht gewijzigd door SparkyGSX op (15%)]

Dank SparkyGSX.
Ik heb wel zitten expirimenteren met een 100n aan de ingang van de ADC-pin. Echter ging mijn waarde daardoor nog meer van afwijken. Ik heb niet gecheckt of de afwijking over het gehele bereik stabieler werd. Dat kan ik nog wel eens testen!
Hmm, interessant wat je schrijft over de veranderende impedantie van de potmeter. Zonder toevoeging van een actief component (ik denk dan aan een opamp) is hier zeker niks aan te doen?

Yes, de ESP32 en zeker de M5STACK modules zijn leuk om snel en goedkoop wat in elkaar te kunnen zetten, maar kent ook zijn beperkingen. Daar is dit ook weer een goed voorbeeld van (helaas).

De ADC van de ESP32 is niet helemaal lineair, en vooral onderin niet erg bruikbaar. Dus als je lage temperaturen wilt meten met een LM35 dan heb je een offset nodig en dus een opamp. Of je moet de MCP9701 gebruiken zoals rew al zei.

Zie https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/

Dus het klopt wel dat er niks gebeurt onder 75mV. Dat is een beperking van de hardware en daar kun je met software niks aan veranderen.

PS: de MCP9701 geeft al 400mV bij 0 °C

Dank voor het meedenken over de temperatuurmeting. Ik heb de LM35 genoemd, maar die moet niet leidend gaan worden in mijn hulpvraag. Dan zou de MCP9701 zeker een mooi alternatief kunnen zijn. Toevallig ga ik nu de LM35 ook binnen gebruiken, dus dat zullen spanningen zijn van 150mV en meer.

Echter krijg ik ook boven de 150mV geen juiste uitslag, wat de hoofdvraag betreft (om de waarde uit de ADC te vertalen naar spanning in mV).
Ik ga experimenteren met de suggesties van elmowww en SparkyGSX. Ik sta open voor eventuele aanvullingen.

Op 28 oktober 2023 14:05:01 schreef SparkyGSX:
Het is wel ongeveer de slechtste ADC die ik ooit in een microcontroller heb gezien.

Dan moet je ook nog eens kijken naar die van de RP2040. In de praktijk zitten er 3 forse "hikken" in de output. Schijnt iets te zijn met een condensator op de chip met een verkeerde waarde.

Dat van die 100mV van de ESP32 is ook wel raar. Maar de RP2040 doet ook zoiets, maar niet zo extreem.

Over meedenken voor de temperatuurmeting: Overweeg ook een DS18x20. Die meet de temperatuur en geeft dat digitaal door. (en waar je voor 2, 3, 4 temperatuurmetingen 2, 3, 4 pins op de ESP32 nodig hebt, is dat voor de DS18x20 familie chips respectievelijk 1, 1 en 1. :-) )

[Bericht gewijzigd door rew op (11%)]

Waarom niet gewoon een temperatuur sensor met digitale uitgang?

Op 28 oktober 2023 17:16:49 schreef benleentje:
Waarom niet gewoon een temperatuur sensor met digitale uitgang?

Misschien omdat het kan.
Maar een paar aan elkaar geplakte DS18B20 kan ook eenvoudig.

Ik gebruik meestal de MCP9800/01/03. I2c interface en nooit problemen mee...

Dank voor de aanvullingen en meedenken. De keuze voor een LM35 was omdat het me niet zo ingewikkeld leek om een analoge waarde in te lezen met een ADC :)

Het project betreft een (modulaire) ventilator sturing met een 4-polige connector waar naast de voedingsspanning één pin voor de temperatuursensor en één voor de potmeter gereserveerd is. Dus naast de LM35 of wat voor sensor dan ook, moet ik toch ook een potmeter inlezen. Nu hoeft dat niet persé in mV. Maar kom op, ik moet toch gewoon een adc kunnen gebruiken!!

Op 28 oktober 2023 14:15:45 schreef OPTOdesign:
Dank SparkyGSX.
Ik heb wel zitten expirimenteren met een 100n aan de ingang van de ADC-pin. Echter ging mijn waarde daardoor nog meer van afwijken.

Het feit dat het toevoegen van die condensator iets veranderd, bewijst m.i. dat je wat dat betreft een probleem hebt. Ik denk dat je met de condensator wel de juiste (of best haalbare) conversie van je ADC krijgt, maar een fout hebt gemaakt bij het omrekenen (misschien met getallen van eerdere metingen), waardoor het verkeerd leek.

Die condensator heeft juist als doel om de impedantie van het hele circuit veel kleiner te maken; in de kort tijd (meestal een paar honderd nanoseconde) die de ADC neemt om de sample-and-hold condensator op te laden, komt vrijwel alle lading uit de externe condensator. De potmeter moet die dan alleen bijladen voordat het volgende sample genomen wordt, waardoor de impedantie daarvan veel minder kritisch wordt.

Op 27 oktober 2023 22:00:06 schreef elmowww:
'Code'

Ik heb je berekening uitgevoerd in Excel. Om even gemakkelijk te kunnen spelen met de variabelen en dan kijken wat de uitkomst is. De waarde van a in jouw voorbeeldcode komt niet overeen met mijn berekening, maar de uitslag b waar a in zit weer wel. Klopt de berekening zo? Zie bijlage. Dank!

Edit:

elmowww schrijft 800 mV maar heeft 500 mV in de code gezet, daar gaat het mis...