3.1 🔢 AD / DA Wandlung
[How to get the best ADC accuracy in STM32 microcontrollers][Was sind A/D- und D/A-Wandler?][MezData: Analog und Digital] [Wikipedia: Analog-Digital-Umsetzer]
Die AD-Wandlung (Analog-Digital-Wandlung) ist ein Prozess, bei dem analoge Signale in digitale Signale umgewandelt werden. Dies ist essenziell für die Verarbeitung von Signalen in digitalen Systemen wie Computern, Smartphones oder Messgeräten.
Fachbegriffe
- Bittiefe, Bit-Auflösung: Bestimmt die Genauigkeit der Quantisierung. Mehr Bits bedeuten eine höhere Auflösung.
- Abtastrate, Sample-Rate: Gibt an, wie oft pro Sekunde das Signal abgetastet wird.
- Latenzzeit: Zeitspanne zwischen der Signalaufnahme und der Ausgabe des digitalen Wertes.
Wert-Quantisierung: Analoge Spannung in digitalen Wert wandeln

Eine Eingangsspannung Ue wird mit einer Referenzspannung Uref verglichen und das Verhältnis als n-stellige Binärzahl ausgegeben.
Je mehr Stellen (Bits) der Digitalwert hat desto genauer lässt sich die Spannung umwandeln.
Die Wert-Auflösung wird in Bits angegeben, z.B CD-Qualität hat 16Bit Auflösung also $ 2^{16} = 65536 $ Stufen. Der NUCLEO L152RE kann 12 Bit Auflösung und rundet (siehe unten) den Digitalwert.
[🧐 How to get the best ADC accuracy in STM32 microcontrollers]
Quantisierungskennlinie und Bittiefe
[Wikipedia: Quantisierungskennlinie 📖]
Wie breit ist eine Stufe bei der Quantisierung?
Wieviel Spannung pro Digitalwert-Sprung LSB (Least Significant Bit📖)?
Beispiel: Uref = 4V; n=2Bit -> 4 Stufen Auflösung: Stufe 0 .. Stufe 3.
Breite der Quantisierungsstufe (Quantisierungsspannung) Uq:
$ U_{q} =\frac {U_{ref}} {2^{n}} = \frac {4V} {4} = 1V $
Auf welcher Stufe steht man bei Ue=1,5V? Bei der Digitalisierung wird praktisch Ue/Uq geteilt um den Digitalwert zur ermitteln. Soll abgerundet oder gerundet werden? Stehe ich auf Stufe 1 oder Stufe 2?
Achtung: Der Digitalwert ist <= 2n – 1. Bei 3,5V kann nicht 4 raus kommen denn sonst hätten wir 5 Werte.
$ \textbf {Messwert} = Digitalwert * U_q $
Numbers-Datei zum spielen
LibreOffice-Datei zum spielen
Aufgaben
- Gegeben: Uref=2V; 4Bit Wandlung. Gesucht: Uq, Digitalwert bei Ue=1,1V; Messwert für 1,1V
- Gegeben: Uref=3,3V; 8Bit Wandlung. Gesucht: Uq, Digitalwert bei Ue=1,1V; Messwert für 1,1V
Lösungen
- Uq= Uref/2n = 2V/16= 0,125V; Digitalwert = runden(Ue/Uq) = runden(1,1/0,125) = 9; Messwert = 9 * 0,125V = 1,125V
- Uq= Uref/2n = 3,3V/256= 13 mV; Digitalwert = runden(Ue/Uq) = runden(1,1/0,013) = 85; Messwert = 85 * 0,013V = 1,105V
Zeit-Quantisierung: Abtastung (Sampling)
Sollen Signale kontinuierlich gemessen werden, z.B. bei Musik, wird das analoge Signal in regelmäßigen Zeitabständen abgetastet. Diese Abstände bestimmen die Abtastrate, die gemäß dem Nyquist-Theorem📖 mindestens doppelt so hoch wie die höchste Frequenz des Eingangssignals sein muss siehe auch Zeit-Diskretisierung (Abtastung)📖.
Aufgaben
- Mit welcher Bittiefe und Frequenz (Samplingrate) wird bei CD-Qualität📖 (16Bit Stereo, max. Frequenz 22050 Hz) abgetastet?
- Welche Bitrate Bit/s (Bits pro Sekunde) hat dabei der entstehende serielle Datenstrom?
- Welche Byterate B/s ist dies?
Lösungen
- 16Bit pro Kanal, 44,1 kHz Abtastrate
- 2*16*44100 Bit/s= 1411200 Bit/s = 1,41 MBit/s
- 1411200 Bit/s / 8Bit/B = 176,4 kB/s
🍬 Typen von AD-Wandlern
- Wägeverfahren [Sukzessive Approximation] [Wikipedia: Sukzessive Approximation]
Typisches Verfahren bei µC [How to get the best ADC accuracy in STM32 microcontrollers] [AVR-Tutorial: ADC] - Parallel-Umsetzer (Flash-Wandler) [Wikipedia: Parallel-Umsetzer] [MezData: Flash-A/D-Wandler]
- Zählverfahren [Dual-Slope-Wandler] [Wikipedia: Integrierender Umsetzer (Zählverfahren)]
Aufgabe: Beschreiben Sie die Unterschiede der Wandlerarten und ihre Einsatzgebiete.
Arduino AD-Wandler verwenden
Beim Funduino Expansion Board für NUCLEO STM32 (FEB32) ist an A0 (PA0) ein Drehpotentiometer📖 angeschlossen, dessen Spannungswert zwischen 0V..3,3V eingestellt werden kann. Bei Arduino wird der AD-Wandler mit analogRead() abgefragt. A0, A2 sind beim FEB32 schon belegt und ab A4 sind LEDs angeschlossen, somit bleiben die A1 und A3 als mögliche einfache Arduino-AD-Eingänge.
Arduino Analog Ax | PortPin | Info |
---|---|---|
A0 belegt | PA0 | Potentiometer |
A1 (frei) | PA1 | Taster PA1 + NE555 |
A2 belegt | PA4 | NTC-TempSensor |
A3 (frei) | PB0 | DIP-Schalter |
A4 belegt | PC1 | LED1 |
A5 belegt | PC0 | LED0 |
void setup(){
Serial.begin(9600);
}
void loop(){
int adWert = analogRead(PA0);
Serial.printf("AD-Wert: %4d\n",adWert); // Serieller Monitor
//Serial.printf("AD-Wert:%d\n",adWert); // Serieller Plotter
delay(1000);
}
Arduino Einstellmöglichkeiten
Die Auflösung (Bittiefe) beträgt zunächst 10 Bit, kann aber mit analogReadResolution() verändert werden.
🍬 Die Referenzspannung ist beim Nucleo L152RE die Versorgungsspannung von 3,3V und kann mit analogReference() nicht geändert werden, siehe unten.
🖥 Aufgabe: Verändern Sie die Auflösung auf 11 und 12 Bit und beobachten Sie die Ausgaben. Welche Beobachtungen können Sie machen?
So schnell wie möglich messen
Um die Messschwankungen zu untersuchen könnte eine kurze schnelle Messfolge helfen. Hier ein erster Versuch, es werden 50 Werte eingelesen und mit Zeitmarke auf dem Seriellen Plotter ausgegeben, Start der Messung geschieht mit Tastendruck auf User-Button:
#define RESOLUTION 12
#define P_SENSOR A0
#define MESSUNGEN 50
#define P_USER PC13
void setup(){
pinMode(P_USER,INPUT);
Serial.begin(9600);
Serial.printf("Puffer für Serial: %d Byte\n",Serial.availableForWrite());
analogReadResolution(RESOLUTION);
}
void loop(){
if(!digitalRead(P_USER)){ // Messung starten
unsigned long start=millis(); // Startmarke
for(int i=0;i<MESSUNGEN;i++){ // lesen wie der Teufel und raus damit!
unsigned long adWert = analogRead(P_SENSOR); // Sensorwert auslesen
Serial.printf("AD-Wert:%d,Zeit:%d\n",adWert,millis()-start); // Ausgabe für Serieller Plotter
}
delay(1000); // damit Messung nicht gleich wieder los läuft
}
}

Synopsis: Serial.print()🔗 Serial.availableForWrite()🔗
Die orange Linie zeigt die Zeitpunkte an denen die Messung stattgefunden hat. Gut zu erkennen sind die Verzögerungen bei den Zeitpunkten, dies liegt an der Ausgabe, denn nach 63 Byte ist der Puffer für Serial voll und Serial.printf(..) blockiert weitere Messungen bis wieder Platz im Puffer ist.
❓Wie lange haben die 50 Messungen insgesamt gedauert?
Lösung
Etwas über 1000 ms lese ich aus dem Graphen, fleißige könnten den genauen Wert im Seriellen Monitor ablesen.
⁈ Wie kann eine stetigere Messung ohne Pausen erreicht werden?Erhöhung der Übertragungsrate auf 115200 Baud:

Besser aber nicht perfekt.
❓Wie lange haben die 50 Messungen insgesamt gedauert?
Lösung
Ca. 900 ms
Besser messen!
Die Lösung könnte eine Zwischenspeicherung in einem Messpuffer sein. Erst nach der Messung werden die Werte übertragen.
#define RESOLUTION 12
#define P_SENSOR A0
#define MESSUNGEN 50
#define P_USER PC13
void setup(){
pinMode(P_USER,INPUT);
Serial.begin(9600);
Serial.printf("Puffer für Serial: %d Byte\n",Serial.availableForWrite());
analogReadResolution(RESOLUTION);
}
unsigned long wertPuffer[MESSUNGEN]; // Puffer für die Messungen
unsigned long zeitPuffer[MESSUNGEN]; // Puffer für die Zeitmarken
void loop(){
if(!digitalRead(P_USER)){ // Messung starten
unsigned long start=micros(); // Startmarke
for(int i=0;i<MESSUNGEN;i++){ // Messungen mit Zeitmarken machen
wertPuffer[i] = analogRead(P_SENSOR); // Sensorwert auslesen
zeitPuffer[i] = micros()-start; // Zeitmarken speichern
}
for(int i=0;i<MESSUNGEN;i++){ // Seriell ausgeben
Serial.printf("AD-Wert:%d,Zeit:%d\n",wertPuffer[i],zeitPuffer[i]); // Serieller Plotter
}
delay(1000); // damit Messung nicht gleich wieder los läuft
}
}

Was für eine schöne Zeitlinie!
Die Messungen gehen jetzt so schell, das statt millis() micros() verwendet wurde. Leider sind die Werteschwankungen nicht mehr erkennbar.
❓Wie lange haben die 50 Messungen insgesamt gedauert?
Lösung
6000 µs = 6 ms
Den Messschwankungen auf die Spur kommen
LCD-Ausgabe anschließen und Float Printf ermöglichen


Brücken für LCD-Ausgabe einbauen
Damit die LCD-Ausgabe beim Funduino Expansion Board funktioniert, müssen zwei Verbindungen gemacht werden.
printf für float ertüchtigen
Printf kann float-Werte erst dann richtig ausgeben, wenn eine erweiterte Runtime Library verwendet wird:

Mit diesen Änderungen Erkenntnisse gewinnen:
- Quantisierungsspannung anzeigen
- Samplezeit messen
- Min- und Maxwerte ermitteln, Varianz = max-min
- Varianz in mV berechnen (🤯 Ist der Begriff Varianz📖 richtig gewählt?)
- Triggersignal für Oszi zur Messung von Spannung am AD-Eingang bei Start der Messung
#include <Wire.h> // Wire Bibliothek einbinden
#include <LiquidCrystal_PCF8574.h>
LiquidCrystal_PCF8574 lcd (0x27); // LCD-Adresse auf 0x27 für 16 Zeichen und 2 Zeilen ein
#define RESOLUTION 12
#define P_SENSOR A0
#define MESSUNGEN 50
#define P_USER PC13
#define P_OSZI PC1 // Ausgangsignal für Oszi-Triggerung
float uq = 3300.0/(1<<RESOLUTION); // Quantisierungsspannung
void setup(){
pinMode(P_USER,INPUT);
Serial.begin(9600);
lcd.begin(16, 2); // initialize the lcd
lcd.clear();
lcd.setBacklight(255);
lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
lcd.printf("Uq = %5.4f mV",uq); // Quantisierungsspannung ausgeben
analogReadResolution(RESOLUTION);
pinMode(P_OSZI,OUTPUT); // Um Oszi für Messung zu triggern
}
int wertPuffer[MESSUNGEN];
void loop(){
unsigned char byte_0; // left Byte;
unsigned char byte_1; // right Byte;
unsigned long start;
if(!digitalRead(P_USER)){ // Messung starten
start = micros();
for(int i=0;i<MESSUNGEN;i++){ // lesen wie der Teufel und speichern
digitalWrite(P_OSZI,HIGH);
wertPuffer[i] = analogRead(P_SENSOR); // Sensorwert auslesen
digitalWrite(P_OSZI,LOW);
}
int messdauer = (micros()-start)/MESSUNGEN;
int messMin = wertPuffer[0];
int messMax = wertPuffer[0];
float durchschnitt=0;
int wert;
for(int i=0;i<MESSUNGEN;i++){ // senden so schnell wie geht
wert = wertPuffer[i];
Serial.printf("AD-Wert:%d\n",wert); // Serieller Plotter
if (wert<messMin) messMin=wert;
if (wert>messMax) messMax=wert;
durchschnitt+=wert;
}
lcd.clear();
lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
lcd.printf("Z%d L%4d H%4d",messdauer,messMin,messMax);
lcd.setCursor(0,1); // erstes Zeichen, zweite Zeile
durchschnitt /=MESSUNGEN; // Durchschnitt berechen
lcd.printf("D%4.1f V%d %3.2fmV",durchschnitt,messMax-messMin,uq*(messMax-messMin));
delay(500);
}
}
Beim Start wird die Quantisierungsspannung angezeigt:


Aufgaben
- Berechnen Sie die Sampling-Rate, welche maximale Frequenz z.B. eines Audiosignals könnte damit sauber abgetastet werden?
- 🖥 Untersuchen Sie die Varianz in mV bei bei verschiedenen Poti-Stellungen und 12 bzw. 10 Bit Auflösung : Führt eine höhere Auflösung zu einer höheren Messgenauigkeit? Nehmen Sie Stellung.
- 🍬 🖥 Modifizieren Sie das Programm so, das mit DIP-Schalter an PB0 die Auflösung zwischen 10 und 12 umgeschaltet werden kann. Wenn umgeschaltet wird soll die neue Quantisierungsspannung berechnet und angezeigt werden.
Lösungen 🚧
- 1/129µs = 7752Hz -> 3876Hz maximale Signal-Frequenz
Messwertschwankungen mit Kondensator reduzieren
Ich habe versuchsweise einen 100nF Kondensator zwischen GND und A0 geschaltet. Die Varianz der Messungen ist deutlich zurückgegangen!


🤯 Komplexe Unterrichtsidee: Kombination aus HW, SW und Mathematik -> Die Messungen an Processing übertragen und dort stochastisch analysieren.
🍬 Beeinflusst der AD-Wander das Eingangssignal? (🚧)
In den Empfehlungen des Herstellers How to get the best ADC accuracy in STM32 microcontrollers🔗 wird die AD-Wandlung genau erklärt und dabei auf die Problematik mit Quellen mit hoher Ausgangsimpendanz eingegangen. Zu Beginn einer Messung wird der AD-Kanal auf Kondensatoren geschaltet und diese dabei aufgeladen, den dazu notwendigen Strom sollte die Quelle liefern können. Ich habe versucht, diesen Vorgang zu messen, dafür extra ein Trigger-Signal an PC1 erzeugt (orange). Ich hätte erwartet einen kurzen Spannungseinbruch zu finden. Leider ist das Kanalrauschen meines Digitaloszilloskops zu hoch für eine gute Messung. Die Spikes am Anfang der Messung (blau) führe ich auf ein Übersprechen des Trigger-Signals zurück. Zur genaueren Untersuchung würde ich:
- Testprogramm in Assembler schreiben um den genauen Zeitverlauf zu kennen.
- Einen Layoutplan der NUCLEO L152RE Platine haben wollen, z.B. bei Arduino UNO ist das Layout nicht auf beste AD-Wandlung ausgelegt.
- Einen anderen Ausgangspin für den Trigger verwenden, der weiter vom AD-Eingang entfernt ist.
- Die Eingangsimpedanz meiner Quelle regeln können.
- Ein rauschärmeres Oszilloskop verwenden..

Temperatursensor TMP36 auswerten
Synopsis: [🔗 Datenblatt TMP36] [🔗 Funduino Anleitung]
Ich hatte noch einige TMP36 rumliegen.
Vs an 3,3V GND an GND und den Ausgang (gelber Draht) an Analogeingang A1 anschließen.
Damit folgende Software funktioniert muss für printf was eingestellt werden.
printf für float ertüchtigen


#define RESOLUTION 10
#define P_SENSOR A1
void setup(){ // Einmalige Ausführung => Initialisierungen...
Serial.begin(9600); // Serielle Schnittstelle zum debuggen
analogReadResolution(RESOLUTION);
}
void loop(){
int adWert = analogRead(P_SENSOR); // Sensorwert auslesen
float spannung = adWert * 3.3/(1<<RESOLUTION); // in gemessene Spannung umwandeln
float temperatur = ? ; // nach Datenblatt die Temperatur berechnen
Serial.printf("AD-Wert: %4d Spannung: %5.3fV Temperatur: %3.1f°C\n",adWert,spannung,temperatur);
//Serial.printf("AD-Wert:%d\n",adWert); // Serieller Plotter
delay(1000);
}
Aufgaben
- Finden Sie im Datenblatt die Berechnung der Temperatur aus der Spannung und ergänzen Sie den Code.
- 🖥 Testen Sie das Programm. Verwenden Sie den Seriellen Plotter. Wie hoch ist die Temperatur-Varianz?
- 🖥 Erhöhen Sie die Bittiefe auf 12 und verwenden Sie den Seriellen Plotter.
- ❓ Führt eine höhere Bittiefe hier zu einer genaueren Temperaturmessung?
- 🖥 Modifizieren Sie das Programm um eine Glättung der Messwerte zu bekommen, Tipp: wert = (wert*n + wertNeu) / (n+1)
- ❓Diskutieren Sie welche Vor- und Nachteile große n für die Anzeige / Verarbeitung der Messungen haben.
Lösungen 🚧
float temperatur = (spannung-0.5)*100;
// nach Datenblatt die Temperatur berechnen 10mV=1°C
Temperatursensor LM35 auswerten
Synopsis: [LM35 Datenblatt🔗]
Die Werte eines LM35 Temperatursensors sollen mittels AD-Wandler eingelesen, und umgewandelt auf der seriellen Schnittstelle ausgegeben werden. Der Sensor ist an 5V und der Ausgang ist an A3 (PB0) des STM32 angeschlossen. Die Referenzspannung des AD-Wandlers beträgt 3.3 Volt und die Bit-Auflösung ist 10 Bit.

- Berechnen Sie die Quantisierungsspannung Uq mit 4 Nachkommastellen.
- Der Sensor gibt pro °C 10 mV aus. Welchen Spanungswert gibt der Sensor bei 100°C aus?
- Welcher Digitalwert adWert wird dabei gemessen?
- Welcher Temperaturwert float temp = adWert * … wird dabei ausgegeben Lösungsweg!
- Erstellen Sie eine Anweisung float temp = adWert * … ; die die Temperatur einer Messung berechnet.
- Wie hoch ist die theoretische Temperaturauflösung?
- 🖥 Erstellen und testen Sie ein Programm zur Temperaturmessung.
Lösungen 1..6
- Uq = 3,3V / 1024 = 3,2227 mV.
- 100 * 10 mV = 1V
- runden(1V / 3,2227 mV) = 310.
- adWert * Uq * 100 = 310 * 0.0032227 * 100 = 99.9
- float temp = adWert* 0.32227;
- Uq/(10mV/°C) = 3,2227mV/(10mV/°C) = 0,32227 °C
Lösungsvorschlag 7
#define BIT_AUFLOESUNG 10
void setup(){ // Einmalige Ausführung => Initialisierungen...
Serial.begin(9600); // Serielle Schnittstelle
analogReadResolution(BIT_AUFLOESUNG); // ohne die Zeile ist sie 10 = 1024 Werte
}
void loop(){
int adWert = analogRead(A3); // A3=PB0
float temp = adWert*3.3/(1<<BIT_AUFLOESUNG)*100;
Serial.printf("AD-Wert: %4d Temperatur: %3.2f C\n",adWert,temp); // Lib für float einstellen nicht vergessen
delay(1000);
}
Mit Fotowiderstand (LDR) Lichtstärke messen

Synopsis: [🔗 Datenblatt GL5528] [🔗 Funduino Anleitung]
Fotowiderstände werden wegen ihrer breiten spektralen Lichtempfindlichkeit verwendet, die unserer Wahrnehmung ähnlich ist:
de.wikipedia.org/wiki/Fotowiderstand📖
Im Bild ist ein LDR an 3,3V und über einen 1,2kΩ Widerstand gegen GND geschaltet. Die Spannung wird mit A1 gemessen. Im Hintergrund ist ein 100nF Kondensator parallel zum Widerstand zu sehen, mit ihm wird die Messung verbessert.
🖥 Erstellen Sie ein Programm zur Messung und Anzeige der Spannung an A1.
🏋️ Vergleiche: [🔗 Photodiode] und [🔗 Fototransistor]
Problemstellung ist nicht trivial, Widerstand messen und in Lichtstärke (Lux) umwandeln.
Joystick anschließen
Ein Joystick mit Taster anschließen und die x-y-Werte auslesen. Zuerst die Verkabelung für das Sturm-Board, weil A0 und A2 schon belegt sind:


Beim Didactic Elements Board gibt es eine Buchse für den Joystick:

Für DE-Board define JS
auskommentieren.
#include <Wire.h> // Wire Bibliothek einbinden
#include <LiquidCrystal_PCF8574.h>
LiquidCrystal_PCF8574 lcd (0x27); // LCD-Adresse auf 0x27 für 16 Zeichen und 2 Zeilen ein
#define JS // Jörg Sturm Board
#ifdef JS
#define P_X A1 // X-Achse
#define P_Y A3 // Y-Achse
#define P_SWITCH A4 // Taster am Joystick
#define P_LED PC7 // Ausgabe Switch gedrückt
#else // Didactic Elements Board
#define P_X PA4 // X-Achse
#define P_Y PA6 // Y-Achse
#define P_SWITCH PA7 // Taster am Joystick
#define P_LED PC7 // Ausgabe Switch gedrückt
#endif
void setup(){
Serial.begin(9600);
lcd.begin(16, 2); // initialize the lcd
lcd.clear();
lcd.setBacklight(255);
lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
pinMode(P_SWITCH,INPUT_PULLUP);
pinMode(P_LED,OUTPUT);
}
void loop(){
int x = analogRead(P_X);
int y = analogRead(P_Y);
digitalWrite(P_LED,!digitalRead(P_SWITCH));
//Serial.printf("AD-Wert: %4d\n",adWert); // Serieller Monitor
Serial.printf("X:%d,Y:%d\n",x,y); // Serieller Plotter
lcd.clear();
lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
lcd.printf("X%4d Y%4d",x,y);
delay(300);
}
🍬 AD-Wandler des L152RE unter die Lupe genommen
Synopsis: [Referenz-Manual RM008] S265ff. [Datenblatt stm32l152re] S25ff. [API: ADC internal channels]
Bei unserem L152RE ist die Referenzspannung VREF+ des AD-Wandlers mit VCC verbunden und lässt sich nicht ändern. Die Genauigkeit einer Messung hängt damit unmittelbar mit der Genauigkeit der Versorgungsspannung zusammen, wir gehen meist von genau 3,3V aus. Falls z.B. bei Batteriebetrieb der Spannungsregler nicht mehr richtig regeln kann beeinflusst dies auch die Messergebnisse einer Analog-Messung! Allerdings kann VREF rechnerisch ermittelt werden, denn es gibt eine sehr genaue, sogar bei der Produktion vermessene interne Referenzspannung von typisch 1,224V [Datenblatt stm32l152re] S64. In der stm32duino-API gibt es Beispiel-Code dafür. Dort wird neben VREF auch der Wert des internen Temperatursensors inclusive Berücksichtigung seiner Kalibrierungsdaten ausgegeben.
Interne Referenzspannung verwenden
Die Messung einer Analogspannung hängt vom Wert der Referenzspannung Uref=VREF+ ab. Uref ist aber beim L152RE VCC, wir nehmen oft an, dass es genau 3,3 V sind. Wie kann dies überprüft werden? Die folgenden Ausführungen gehen von 12 Bit Wandler-Auflösung aus. Im Chip ist eine genaue interne Spannungsquelle VREFINT mit typisch 1,224V eingebaut, sie kann mit analogRead(AVREF) gelesen werden. Bei der Fertigung des Chips wird dieser mit genau 3V = 3000 mV versorgt und VREFINT mit 12Bit gemessen (Uq=3V/4096), dieser Messwert VREFINT_CAL wird fest an der Adresse VREFINT_CAL_ADR 0x1FF800F8 als 16Bit Zahl gespeichert. Somit weis man welchen Digitalwert VREFINT bei Uref = 3V ergibt. Der Spannungswert von VREFINT kann leicht berechnet werden, hier die int Berechnung in mV:
#define VREFINT_CAL_ADR 0x1FF800F8 // Digitalmesswert der internen Spannungsquelle bei 3V Uref bei Produktion
int vRefInt=*(uint16_t*)VREFINT_CAL_ADR*3000/4096; // diese Spannung wurde dabei gemessen
*(uint16_t*)VREFINT_CAL_ADR liest von Adresse VREFINT_CAL_ADR einen 16-Bit Wert aus.
Um nun die Referenzspannung Uref = VCC zu ermitteln wird einfach die interne Spannungsquelle mit analogRead(AVREF) gemessen und zurückgerechnet:
int uref = vRefInt*4096/analogRead(AVREF); // UREF=Vcc gemessen in mV
// oder nach Einsetzen von vRefInt
int uref = *(uint16_t*)VREFINT_CAL_ADR*3000/analogRead(AVREF); // UREF=Vcc gemessen in mV
Test-Programm LM75A vs. LM35 auf LCD und Chiptemperatur

Im oben Bild habe ich rechts oben noch den analogRead(AVREF) ausgegeben, das Testprogramm gibt nun auch die Chiptemperatur aus, berechnet mit kalibrierter interner Referenzspannung und Kalibrationsdaten aus der Fertigung. Die Spannung Uref stimmt sehr genau mit meinem 4-Stelligen Multimeter überein.
Der Wert des LM75A bleibt stabil, die Messungen des LM35 schwanken ständig, trotz höchster Auflösung, nach der leichten Glättung sind die Werte recht nah beieinander.

#include <Wire.h> // I2C-Library
#include <LiquidCrystal_PCF8574.h> // LCD
#define ADRESSE 0x48//0x92
LiquidCrystal_PCF8574 lcd (0x27);
signed char byteH; // wichtig ist das signed!
char byteL; // signed führt zu Fehler
float temp;
void setup() {
Wire.begin(); // Alternatives PinMapping: Wire.begin(SDA, SCL);
lcd.begin(16, 2); // initialize the lcd
lcd.clear();
lcd.setBacklight(255);
lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
lcd.print("LM75A LM35");
analogReadResolution(12); // 12 Bit Auflösung 4096 Werte
Serial.begin(9600);
}
#define VREFINT_CAL_ADR 0x1FF800F8 // Messwert der internen Spannungsquelle bei 3V
#define TS_CAL1_ADR 0x1FF800FA // Messwert für 30°C bei 3V
#define TS_CAL2_ADR 0x1FF800FE // Messwert für 110°C bei 3V
void loop() {
Wire.requestFrom(ADRESSE, 2); // 2 Bytes lesen
byteH = Wire.read(); // receive a byte
byteL = Wire.read(); // receive a byte
temp = ((byteH<<3) + (byteL>>5))*0.125; // volle Auflösung bitte
lcd.setCursor(0, 1);
lcd.print(temp);
lcd.print(" ");
int uref = *(uint16_t*)VREFINT_CAL_ADR*3000/analogRead(AVREF); // UREF=Vcc gemessen in mV
temp = (temp*3+0.1*analogRead(A3)*uref/4096)/4; // LM35 auslesen und ein wenig glätten
lcd.print(temp);
lcd.setCursor(11, 0); // rechts oben ausgeben
int vRefInt =*(uint16_t*)0x1FF800F8*3000/4096; // kalibrierte interne Referenzspannung (mV) bei Vcc=3V gemessen
temp = ((analogRead(ATEMP)*uref/3000.0 - *(uint16_t*)TS_CAL1_ADR)*(110-30)/(*(uint16_t*)TS_CAL2_ADR-*(uint16_t*)TS_CAL1_ADR))+30;
lcd.print(temp);
Serial.printf("UrefIntern %d Uref %d TS_CAL1 %d TS_CAL2 %d ATEMP %d\n",vRefInt,uref,*(uint16_t*)TS_CAL1_ADR,*(uint16_t*)TS_CAL2_ADR,analogRead(ATEMP));
delay(1000);
}