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]

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

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

Arduino Einstellmöglichkeiten

Bei Arduino wird der AD-Wandler mit analogRead() abgefragt. Die Auflösung beträgt zunächst 10 Bit, kann aber mit analogReadResolution() verändert werden. Die Referenzspannung ist beim Nucleo L152RE die Versorgungsspannung und kann mit analogReference() nicht geändert werden, siehe unten.

Aufgabe: Temperatursensor LM35 auswerten

Synopsis: [LM35 Datenblatt][STM32-API: ADC internal channels]

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 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. L: Uq = 3,3V / 1024 = 3,2227 mV. Der Sensor gibt pro °C 10 mV aus. Welchen Spanungswert gibt der Sensor bei 100°C aus? L: 100 * 10 mV = 1V Welcher Digitalwert adWert wird dabei gemessen? L: runden(1V / 3,2227 mV) = 310. Welcher Temperaturwert float temp = adWert * … wird dabei ausgegeben Lösungsweg! L: adWert * Uq * 100 = 310 * 0.0032227 * 100 = 99.9 Erstellen Sie eine Anweisung float temp = adWert * … ; die die Temperatur einer Messung berechnet. L: float temp = adWert* 0.32227; Wie hoch ist die Temperaturauflösung? L: Uq/(10mV/°C) = 3,2227mV/(10mV/°C) = 0,32227 °C.
Die Auflösung wird nun auf 12Bit erhöht.

#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("Bit-Aufloesung: %2d AD-Wert: %4d Temperatur: ",BIT_AUFLOESUNG,adWert);
  Serial.println(temp);
  //Serial.printf("AD-Wert: %4d Temperatur: %3.2f C\n",adWert,temp); // %3.2f bei Arduino kaputt?
  delay(1000);
}
Temperatursensor LM35
Temperatursensor LM35

Wechselspannung digitalisieren

Um den Verlauf eines sich ändernden Signals z.B. Musik zu digitalisieren wird die Spannung in regelmäßigen Zeitabständen abgetastet (Sampling).
[Wikipedia: Zeit-Diskretisierung (Abtastung)]

Aufgabe: Wie hoch muss die Abtastrate bei einem Audiosignal mit max. 22kHz sein?

Wandlerarten

Aufgabe: Beschreiben Sie die Unterschiede der Wandlerarten und ihre Einsatzgebiete.

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
  Wire.available(); // MSByte
  byteH = Wire.read(); // receive a byte
  Wire.available(); // LSByte
  byteL = Wire.read(); // receive a byte
  Wire.endTransmission();
  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);
}