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

Blockschaltbild

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 $

Quantisierungskennlinie
Digitalwert = Abrunden(Ue/Uq)
Quantisierungskennlinie
Digitalwert = Runden(Ue/Uq)

Aufgaben

  1. Gegeben: Uref=2V; 4Bit Wandlung. Gesucht: Uq, Digitalwert bei Ue=1,1V; Messwert für 1,1V
  2. Gegeben: Uref=3,3V; 8Bit Wandlung. Gesucht: Uq, Digitalwert bei Ue=1,1V; Messwert für 1,1V
Lösungen
  1. Uq= Uref/2n = 2V/16= 0,125V; Digitalwert = runden(Ue/Uq) = runden(1,1/0,125) = 9; Messwert = 9 * 0,125V = 1,125V
  2. 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

  1. Mit welcher Bittiefe und Frequenz (Samplingrate) wird bei CD-Qualität📖 (16Bit Stereo, max. Frequenz 22050 Hz) abgetastet?
  2. Welche Bitrate Bit/s (Bits pro Sekunde) hat dabei der entstehende serielle Datenstrom?
  3. Welche Byterate B/s ist dies?
Lösungen
  1. 16Bit pro Kanal, 44,1 kHz Abtastrate
  2. 2*16*44100 Bit/s= 1411200 Bit/s = 1,41 MBit/s
  3. 1411200 Bit/s / 8Bit/B = 176,4 kB/s

🍬 Typen von AD-Wandlern

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 AxPortPinInfo
A0 belegtPA0Potentiometer
A1 (frei)PA1Taster PA1 + NE555
A2 belegtPA4NTC-TempSensor
A3 (frei)PB0DIP-Schalter
A4 belegtPC1LED1
A5 belegtPC0LED0
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
  }
}
Serieller Plotter mit 9600 Baud
Serieller Plotter mit 9600 Baud

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:

Serieller Plotter mit 115200 Baud
Serieller Plotter mit 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
  }
}
Mit Puffer und 9600 Baud
Mit Puffer und 9600 Baud

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

LCD anschließen
LCD anschließen
LCD anschließen
LCD anschließen

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:

Printf mit float
Printf kann dann auch float

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:

Quantisierungsspannung
Quantisierungsspannung
LCD-Anzeige Beschreibung
LCD-Anzeige Beschreibung

Aufgaben

  1. Berechnen Sie die Sampling-Rate, welche maximale Frequenz z.B. eines Audiosignals könnte damit sauber abgetastet werden?
  2. 🖥 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.
  3. 🍬 🖥 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. 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!

Messwertverbesserung mit 100nF Kondensator
Messwertverbesserung mit 100nF Kondensator
Bessere Messwerte mit 100nF Kondensator
Bessere Messwerte mit 100nF Kondensator

🤯 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:

  1. Testprogramm in Assembler schreiben um den genauen Zeitverlauf zu kennen.
  2. Einen Layoutplan der NUCLEO L152RE Platine haben wollen, z.B. bei Arduino UNO ist das Layout nicht auf beste AD-Wandlung ausgelegt.
  3. Einen anderen Ausgangspin für den Trigger verwenden, der weiter vom AD-Eingang entfernt ist.
  4. Die Eingangsimpedanz meiner Quelle regeln können.
  5. Ein rauschärmeres Oszilloskop verwenden..
Oszillogramm AD-Eingang
Oszillogramm AD-Eingang

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

Printf mit float
Printf kann dann auch float
TMP36 anschließen
TMP36 anschließen
#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

  1. Finden Sie im Datenblatt die Berechnung der Temperatur aus der Spannung und ergänzen Sie den Code.
  2. 🖥 Testen Sie das Programm. Verwenden Sie den Seriellen Plotter. Wie hoch ist die Temperatur-Varianz?
  3. 🖥 Erhöhen Sie die Bittiefe auf 12 und verwenden Sie den Seriellen Plotter.
  4. ❓ Führt eine höhere Bittiefe hier zu einer genaueren Temperaturmessung?
  5. 🖥 Modifizieren Sie das Programm um eine Glättung der Messwerte zu bekommen, Tipp: wert = (wert*n + wertNeu) / (n+1)
  6. ❓Diskutieren Sie welche Vor- und Nachteile große n für die Anzeige / Verarbeitung der Messungen haben.
Lösungen 🚧
  1. 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.

Temperatursensor LM35
Temperatursensor LM35
  1. Berechnen Sie die Quantisierungsspannung Uq mit 4 Nachkommastellen.
  2. Der Sensor gibt pro °C 10 mV aus. Welchen Spanungswert gibt der Sensor bei 100°C aus?
  3. Welcher Digitalwert adWert wird dabei gemessen?
  4. Welcher Temperaturwert float temp = adWert * … wird dabei ausgegeben Lösungsweg!
  5. Erstellen Sie eine Anweisung float temp = adWert * … ; die die Temperatur einer Messung berechnet.
  6. Wie hoch ist die theoretische Temperaturauflösung?
  7. 🖥 Erstellen und testen Sie ein Programm zur Temperaturmessung.
Lösungen 1..6
  1. Uq = 3,3V / 1024 = 3,2227 mV.
  2. 100 * 10 mV = 1V
  3. runden(1V / 3,2227 mV) = 310.
  4. adWert * Uq * 100 = 310 * 0.0032227 * 100 = 99.9
  5. float temp = adWert* 0.32227;
  6. 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

LDR an Arduino anschließen
LDR an Arduino anschließen

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:

Joystick verkabeln für Sturm-Board
Joystick verkabeln für Sturm-Board
Joystick verkabeln für Sturm-Board
Joystick verkabeln für Sturm-Board

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

Joystick an Didactic Elements Board anschließen
Joystick an Didactic Elements Board anschließen

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

Testaufbau mit LM75A und LM35
Testaufbau mit LM75A und LM35

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.

Formel für Interne Temperaturberechnung
Formel für Interne Temperaturberechnung
#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);
}