Forum: Mikrocontroller und Digitale Elektronik STM32 ADC Calibration


von Matthias H. (streno)


Lesenswert?

Hallo zusammen,

ich versuche mich gerade an einem STM32F0 Discovery_Board und habe den 
ADC in Verwendung.

Diesen calibriere ich zu Beginn und erhalte einen Wert von 66.

Anschließend gibt mir mein ADC in der Main zyklisch Werte von Pa0 und 
Vref-Int aus.

Danach möchte ich die Spannung an PA0 berechnen mit der Formel aus dem 
Reference Manual V=(3300V*V_ref_cal*ADC-Data)/(4095*V_ref_data)

Die Spannung die ich messen möchte, liegt ca. bei 2.8V. Ich bekomme als 
Werte ungefähr ADC_Data=3949 und V_ref_data=2569.
Mein Ergebnis liegt immer bei 74. Müsste allerdings bei 2800 liegen.

Benutze ich eine falsche Formel? Stimmt der Calibration-Wert oder müsste 
dieser größer sein?

Vielleicht sieht jemand meinen Fehler..

Danke für eure Hilfe

von Matthias H. (streno)


Lesenswert?

Main Code:
1
while(1)
2
    {
3
4
        /* Test DMA1 TC flag */
5
            while((DMA_GetFlagStatus(DMA1_FLAG_TC1)) == RESET );
6
            /* Clear DMA TC flag */
7
            DMA_ClearFlag(DMA1_FLAG_TC1);
8
9
10
            /* Convert Vref voltage value in mv */
11
            VrefIntVoltmv[x]  = (uint32_t)((Buffer[0]* 3300* calib )/(4095*Buffer[1]));
12
            ADC_StartOfConversion(ADC1);
13
14
          x++;
15
          if(x==2000)
16
            x=0;
17
18
19
    }
20
}

ADC_Konfig:
1
  /* ADC1 Configuration *******************************************************/
2
  /* ADCs DeInit */  
3
  ADC_DeInit(ADC1);
4
  
5
  /* Configure the ADC1 in continous mode withe a resolutuion equal to 12 bits*/
6
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
7
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
8
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;    
9
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
10
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
11
  ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward;
12
  ADC_Init(ADC1, &ADC_InitStructure); 
13
  
14
  /* Convert the ADC1 Channel 0 with 1.5 Cycles as sampling time */
15
  ADC_ChannelConfig(ADC1, ADC_Channel_0 , ADC_SampleTime_1_5Cycles);
16
17
  /* Convert the ADC1 Vref  with 1.5 Cycles as sampling time */
18
  ADC_ChannelConfig(ADC1, ADC_Channel_Vrefint , ADC_SampleTime_1_5Cycles);
19
20
21
  /* ADC Calibration */
22
  calib=ADC_GetCalibrationFactor(ADC1);
23
24
25
26
  /* Enable the auto delay feature */    
27
 // ADC_WaitModeCmd(ADC1, ENABLE);
28
  
29
  /* Enable the Auto power off mode */
30
 // ADC_AutoPowerOffCmd(ADC1, ENABLE);
31
  
32
  /* Enable ADCperipheral[PerIdx] */
33
  ADC_Cmd(ADC1, ENABLE);
34
  ADC_VrefintCmd(ENABLE);
35
36
  /* Warten bis ADC enabled */
37
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN));
38
  
39
  /* Enable ADC_DMA */
40
  ADC_DMACmd(ADC1, ENABLE);
41
42
43
  
44
  /* TIM3 starten */
45
  TIM_Cmd(TIM3, ENABLE);
46
  
47
  /* ADC1 regular Software Start Conv */ 
48
  ADC_StartOfConversion(ADC1);
49
}

von Stefan K. (stefan64)


Lesenswert?

Setzt Du für v_ref_cal den richtigen Wert ein? Der steht im Flash:

Aus STM32F071 datasheet:

3.10.2 Internal voltage reference (VREFINT)
The internal voltage reference (VREFINT) provides a stable (bandgap) 
voltage output for the ADC. VREFINT is internally connected to the 
ADC_IN17 input channel. The precise voltage of VREFINT is individually 
measured for each part by ST during production test and stored in the 
system memory area. It is accessible in read-only mode.

Calibration value name            Memory address
-----------------------------------------------------------
VREFINT_CAL                       0x1FFF F7BA - 0x1FFF F7BB

Ich habe den Eindruck, dass im Reference manual Offset-calibration und 
VREF-calibration-factor etwas unglücklich zusammengemischt werden.

Gruß, Stefan

von Stefan K. (stefan64)


Lesenswert?

V_ref_cal
----------
V_ref_data

müssen einen Faktor von ca. 1.0 ergeben.

Viele Grüße, Stefan

von Matthias H. (streno)


Lesenswert?

Stefan K. schrieb:
> Setzt Du für v_ref_cal den richtigen Wert ein? Der steht im Flash:

Laut dir nicht. Ich dachte immer, ich setze den Wert meiner Kalibrierung 
zu Beginn der ADC-Konfigurierung ein. So ist das aus dem Datenblatt für 
mich ersichtlich.

Ich benutze allerdings einen STM32F051. In dem Reference Manual steht 
das nicht so deutlich.

Das das so ist, dachte ich mir eigentlich auch schon. Habe allerdings 
noch keinen Wert für Vref_CAL gefunden, ausser eben den Wert, den ich 
nach der Kalibrierung erhalte. Danke schonmal für deinen Einwand, dann 
muss ich wohl noch weiter suchen...

von Stefan K. (stefan64)


Lesenswert?

Matthias H. schrieb:
> Laut dir nicht. Ich dachte immer, ich setze den Wert meiner Kalibrierung
> zu Beginn der ADC-Konfigurierung ein. So ist das aus dem Datenblatt für
> mich ersichtlich.

Ja, das dachte ich auch zuerst.
Allerdings ist der Wert, der nach der ADC-Kalibration gelesen soll, ja 
anscheinend ein DC-Offset, erstens wegen seiner Größe (Bit 0..6, also 0 
.. 127) und zweitens wegen dem Satz in 13.4.1:

"The internal analog calibration is kept if the ADC is disabled 
(ADEN=0). When the ADC operating conditions change (VDDA changes are the 
main contributor to ADC offset variations and temperature change to a 
lesser extend), it is recommended to re-run a calibration cycle."

Die Werte V_ref_cal und V_ref_data werden in der von Dir benutzten 
Formel aber als Gain-Korrektur verwendet.

Meiner Meinung nach musst Du den Vref Channel messen und diesen Wert als 
V_ref_data in Deine Formel einsetzen. Für V_ref_cal wird der Wert aus 
dem Flash genommen. Dieser Wert wurde von ST für diesen speziellen Chip 
berechnet und stellt den individuellen Korrekturfaktor für die interne 
Referenzspannungsquelle dar.

Viele Grüße, Sefan

von Matthias H. (streno)


Lesenswert?

Matthias H. schrieb:
> In dem Reference Manual steht
> das nicht so deutlich.

Wer lessen kann ist klar im Vorteil. Im reference manual steht nichts, 
allerdings im Datenblatt:

VREFINT_CAL 0x1FFFF7BA-0x1FFFF7BB

Allerdings ist diese Variable nicht in der CooCox-Umgebung vorhanden.

von Felix U. (ubfx)


Lesenswert?

Matthias H. schrieb:
> Ich bekomme als
> Werte ungefähr ADC_Data=3949 und V_ref_data=2569.

2569/4095 * 3.3V sind etwa 2V. Da kann also schon mal etwas nicht 
stimmen, denn das Bandgap Element liegt bei etwa 1.2V
Welchen Wert kriegst du für calib?

Edit:
Du rufst ja auch schon ADC_GetCalibrationFactor auf. Je nach 
Interpretation des Reference Manuals könnte man zum Schluss gelangen, 
dass danach die Korrektur schon intern angewendet wird. (siehe ADCAL 
Abschnitt)

: Bearbeitet durch User
von Matthias H. (streno)


Lesenswert?

Felix U. schrieb:
> Matthias H. schrieb:
>> Ich bekomme als
>> Werte ungefähr ADC_Data=3949 und V_ref_data=2569.
>
> 2569/4095 * 3.3V sind etwa 2V. Da kann also schon mal etwas nicht
> stimmen, denn das Bandgap Element liegt bei etwa 1.2V
> Welchen Wert kriegst du für calib?

Habe die SR erhöht und erhalte 1692 für Vref_Data. Bei 2,978 VDDA ergibt 
das 1,23 was relative genau ist. Daran liegt es nicht. Mir fehlt der 
Wert von V_REF_CAL

von Stefan K. (stefan64)


Lesenswert?

Felix U. schrieb:
> Du rufst ja auch schon ADC_GetCalibrationFactor auf. Je nach
> Interpretation des Reference Manuals könnte man zum Schluss gelangen,
> dass danach die Korrektur schon intern angewendet wird. (siehe ADCAL
> Abschnitt).

Das denke ich auch. Der hier ermittelte Wert wird ja auch an keiner 
weiteren Stelle von Reference oder datasheet mehr erwähnt.

Matthias H. schrieb:
> Mir fehlt der Wert von V_REF_CAL

Dessen Adresse sollte in einer der CMSIS Dateien definiert sein.

Viele Grüße, Stefan

von Matthias H. (streno)


Lesenswert?

Felix U. schrieb:
> Edit:
> Du rufst ja auch schon ADC_GetCalibrationFactor auf. Je nach
> Interpretation des Reference Manuals könnte man zum Schluss gelangen,
> dass danach die Korrektur schon intern angewendet wird. (siehe ADCAL
> Abschnitt)

Nein. Mache ich es ohne Rechnung mit VrefInt komme ich auf Werte wie 
3.1V anstatt 2.8V. Ist ja auch klar, da ich mit VDDA=3.3V rechne und 
nicht mit 2.978V

Stefan K. schrieb:
> Dessen Adresse sollte in einer der CMSIS Dateien definiert sein.

Nein habe alles durchsucht.

Ich habe es probiert mit:

#define VREFINT_ADDR 0x1FFFF7BA

uint16_t Vrefcal
Vrefcal=*((uint16_t*)VREFINT_ADDR

funktioniert immer noch nicht...

von Felix U. (ubfx)


Lesenswert?

Matthias H. schrieb:
> Nein. Mache ich es ohne Rechnung mit VrefInt komme ich auf Werte wie
> 3.1V anstatt 2.8V. Ist ja auch klar, da ich mit VDDA=3.3V rechne und
> nicht mit 2.978V
Lösung: Rechne mit 2.978 * ADC_DATA/Fullscale, statt mit 3.3. Die 
Kalibrierung macht höchstens 0.1 V aus, das ist ja eine Ein-Punkt 
Korrektur, kann also gar nicht Offset und Linearitätsfehler 
berücksichtigen.

Welchen Wert liest du denn an der Speicheradresse aus? Dann kann man da 
mal einen Plausibilitätscheck machen.

: Bearbeitet durch User
von Stefan K. (stefan64)


Lesenswert?

Felix meinte die Offset-Korrektur.

Hast Du einen jtag-Debugger dran? dann schau Dir doch mal den 
Memory-Bereich bei 0x1FFFF7BA an.


Gruß, Stefan

von Matthias H. (streno)


Lesenswert?

Felix U. schrieb:
> Lösung: Rechne mit 2.978 * ADC_DATA/Fullscale, statt mit 3.3. Die
> Kalibrierung macht höchstens 0.1 V aus, das ist ja eine Ein-Punkt
> Korrektur, kann also gar nicht Offset und Linearitätsfehler
> berücksichtigen.

Nein, in der späteren Anwendung kenne ich mein VDD nämlich nicht mehr.

Ich habe es jetzt!

Der Fehler war im Wert von VREF_CAL. Nachdem ich den Zugriff auf die 
Adresse hinbekommen habe, funktioniert alles einwandfrei.

Danke für eure Hilfe!

von A.. P. (arnonym)


Lesenswert?

Hallo Gemeinde,

vielen Dank auch nochmal von mir für den wirklich nutzbringenden 
Hinweis, dass das in der ST-Doku erwähnte "VREFINT_CAL" nicht der Wert 
ist, welcher in der Standard Peripheral Library von 
ADC_GetCalibrationFactor() zurückgegeben wird - was mein Vorgänger ja 
ebenfalls glaubte.

Stundenlang hab ich auch damit zugebracht herauszufinden, warum einfach 
keine meiner Berechnungen mit den zur Verfügung stehenden Werten zur 
richtigen Referenzspannung führen wollte. Mathe 3 hab ich zwar noch vor 
mir, aber so sehr bin ich nun doch nicht auf den Kopf gefallen XD

Ziemlich schade von ST, nicht genau an der Stelle im Reference Manual, 
wo die Berechnungen der Referenzspannung aufgeführt ist, einfach nochmal 
darauf hinzuweisen, an welcher Adresse das "richtige" VREFINT_CAL zu 
finden ist.

Ich benutze im übrigen einen STM32F030CC und mein Test-Code sieht (für 
die Nachwelt) wie folgt aus:
1
// Configure ADC pins
2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
4
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
5
GPIO_Init(GPIOA, &GPIO_InitStructure);
6
7
// Configure ADC
8
ADC_DeInit(ADC1);
9
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
10
ADC_ClockModeConfig(ADC1, ADC_ClockMode_SynClkDiv4);
11
ADC_StructInit(&ADC_InitStructure);
12
ADC_Init(ADC1, &ADC_InitStructure);
13
14
// Save the calibration factor for later temperature calculation
15
adcCalibFactor = ADC_GetCalibrationFactor(ADC1);
16
17
ADC_VrefintCmd(ENABLE);
18
ADC_Cmd(ADC1, ENABLE);
19
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY) != SET);
20
21
// Get the applied reference voltage
22
ADC_ChannelConfig(ADC1, ADC_Channel_Vrefint, ADC_SampleTime_239_5Cycles);
23
i = 5000;
24
while (i--) {
25
  ADC_StartOfConversion(ADC1);
26
  while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) != SET);
27
  adcRefVoltage += ADC_GetConversionValue(ADC1);
28
}
29
adcRefVoltage /= 5000;
30
adcRefVoltage = 3.3 * (*(uint16_t *)(0x1ffff7ba)) / adcRefVoltage;
31
32
// Clear all selected channels since there is no StdPeriph command for that
33
ADC1->CHSELR = 0;
34
// Configure channel 6 for temperature conversion
35
ADC_ChannelConfig(ADC1, ADC_Channel_6, ADC_SampleTime_239_5Cycles);

Falls sich jemand darüber wundert, warum ich gleich 5000 Mal den ADC 
auslese:
Es ist wie gesagt nur Test-Code und ich gebe den Spannungswert über ein 
UART aus. Dabei waren mir die teils sehr unschönen Schwankungen von 
einigen 10 mV bei einer einzelnen Konvertierung zu nervig anzusehen. 
Daher habe ich einfach ein bisschen rumexperimentiert und bin bei 5000 
geblieben. Macht bei 48 MHz ja sowieso nur 2 ms aus und ist auch in 
meinem Fall absolut unkritisch :D

Und noch ein Hinweis für all diejenigen, die es nicht schon bereits 
wussten:
In der Standard Library setzte die Methode ADC_ChannelConfig() nicht 
vorher alle Channel zurück, sondern Oder-verknüpft das CHSELR-Register 
mit den übergebenen Channels.
Dies kann dann zum Problem werden, wenn man so wie ich die ADC-Werte 
durch ein manuelles Auslösen in 5000 Konvertierungen mittelt und dabei 
versehentlich mehrere Channels aktiv waren. So werden dann nämlich 
reihum alle Kanäle einmal ausgelesen (entsprechend der Einstellung 
ADC_ScanDirection) und von all diesen Werten dann das Mittel genommen. 
Das könnte dann u.U. zu einer nervigen Fehlersuche führen.

Gruß
Arno

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.