Hallo,
hier eine Minimalimplementierung um auf einem STM32F042 einen AD Pin und
die Die Temperatur auszulesen. Gegebenenfalls müssen die Pins, die
eingelesen werden sollen, noch auf Analogen Eingang gestellt werden.
Gestestet mit PCLK=12MHz.
1
voidinitAdc(void){
2
__HAL_RCC_ADC1_CLK_ENABLE();
3
ADC1->CFGR2=ADC_CFGR2_CKMODE_1;//PCLK div by 4. Allowed range is 0.6MHz to 14MHz.
4
ADC1->SMPR=ADC_SMPR_SMP_0|ADC_SMPR_SMP_1|ADC_SMPR_SMP_2;//slowest possible sampling time, 239.5 clock cycles
Der Code mag auf den ersten Blick trivial erscheinen und damit kaum ein
Posting wert. Nur habe ich am Ende drei Abende herumprobiert, bis die
Ansteuerung funktionierte.
Die Gründe dafür waren:
1. Alle online gefundenen Beispiele verwenden einfach die HAL.
2. Der ADC verhält sich nicht so wie im Datenblatt beschrieben und wenn
ein nicht funktionieren mehrere Ursachen hat, wird es plötzlich
schwierig. Was nicht wie erwarten funktioniert hat:
2.1. Startet man die Kalibrierung und aktiviert danach den ADC, wird das
Bit nicht gesetzt. Dies ist auch im Errata beschrieben.
2.2. Laut Datenblatt soll man beim Aktivieren des ADC warten, bis das
Ready Bit gesetzt ist. Bei den ersten Versuchen funktionierte das aus
unbekannten Gründen nicht.
2.3. Deaktivieren des ADC mit dem ADDIS Bit. Klappte ebenfalls nicht.
Wohl auch ein Fehler bei den ersten Versuchen einer Implementierung.
2.4. Überall in der ADC Lib hat man defines für passende Bitfelder, nur
für die Channels ist es die ID und nicht die Bitmaske.
Warum nicht einfach die HAL Lib verwenden?
Weil der Code vom CubeMX nicht funktionierte. Warum nicht? Der Cube baut
alle ausgewählten AD Eingänge in eine Sequenz die alle automatisch
nacheinander Konvertiert werden. Und ohne DMA war Pollen per Software
bei 12MHz schlicht zu langsam um die Daten rechtzeitig abzuholen. Erst
Heruntertakten des ADC und Auswählen der langsamsten Samplingtime machte
eine Verwendung möglich. Außerdem spart der Verzicht auf die HAL
Funktionen (Ok, HAL_Delay ist jetzt noch drin) für den ADC hier mal eben
1,4KiB Flash (-Og Optimierung).
Hast Du mal gemessen, wie lange GetAdc() benötigt? Wäre ganz cool das zu
wissen. Einfach den Systick vor und nach GetAdc() auslesen.
Beispiel: https://github.com/rokath/trice.
Ein Hinweis:
Division ist rechenzeitintensiv - muss es aber nicht, wenn der Divisor
zur Compilezeit bekannt ist. Falls Du also in einem Pre-Compile Step die
prozessorspezifischen Werte tsCal1 und tsCal1 ermitteln kannst (die
ändern sich ja nur von Exemplar zu Exemplar, kannst Du mit dem
Taschenrechner rechnen:
KonstA = 80 / (tsCal2 - tsCal1)
KonstB = ( 80 / (tsCal2 - tsCal1)) * ( - tsCal2 ) + 30
temperatureCelsius = KonstA * temperature + KonstB
KonstA ist nun irgendeine Kommazahl, sagen wir 0,123. Die multiplizierst
Du nun mit einer geeigeten Zweierpotenz, sagen wir 1024. Das ergibt
125,952, also rund 126. Bei Maximaltemperatur gibt das auch noch keinen
Zahlenüberlauf. Die Zweierpotenz so wählen, dass die Zahlen möglichst
groß werden aber sicher kein Overflow passiert.
Nun im Code:
```
int32_t temperature = getAdc(ADC_CHANNEL_TEMPSENSOR);
int32_t temperatureCelsius = (126 * temperature)>>10) + KonstB;
```
Die Rechnung gehört natürlich in einen ausführlichen Codekommentar.
Klar, bei einer Temperaturmessung alle Sekunde ist das nicht nötig aber
manchmal kommt es auf Speed an.
dbgPrintf("Ticks for Adc required: %u\r\n",ticks);
um meinen Code gebaut.
Ergebnis sind 1091 Ticks (manchmal mehr wohl wegen anderen nicht
deaktivierten Interrupts). Das ist jetzt wenig verwunderlich, da ich den
ADC mit 1/4 der Timerfrequenz betreibe und die Sampletime auf 239.5 ADC
Takte steht. Das ergibt einfach einen Mindestwert von 958 Ticks. Dazu
kommt dann noch die Conversion time ~12 Takte * 4 = 48 -> 1006. Passt
also ziemlich gut :)
Mit den Divisionen hast du prinzipiell recht. Nur kam es bei meiner
Anwendung einfach nicht auf Geschwindigkeit an - sonst hätte ich auch
die Samplezeiten und Takt des ADC optimiert. Allenfalls länger schlafen
könnte der MCU mit einer höheren ADC Frequenz. In meiner Anwendung messe
ich 3 Eingänge alle 100ms ;) Umgekehrt habe ich zwei MCUs
(https://github.com/Solartraveler/audiomux) mit der selben Firmware und
die haben unterschiedliche Kalibrierungswerte.
Danke für die Zeitmessung! Was ich nicht verstehe ist:
Warum musst Du den ADC so langsam machen? Du schreibst, du kannst ohne
DMA die Daten nicht rechtzeitig abholen, aber bleibt das Ergebnis nicht
ewig im Datenregister bis Du den ADC wieder neu triggerst?
Wenigstens die zweite Division könntest Du durch ein >>10 ersetzen indem
Du mit Vielfachen von 1024 statt 1000 rechnest.
Thomas H. schrieb:> Warum musst Du den ADC so langsam machen? Du schreibst, du kannst ohne> DMA die Daten nicht rechtzeitig abholen, aber bleibt das Ergebnis nicht> ewig im Datenregister bis Du den ADC wieder neu triggerst?
Sofern man nur einen ADC Kanal nutzt, ja. Allerdings packt die HAL alle
ausgewählten ADC Eingänge in eine Chain, die man nur zusammen triggern
kann.
Man wählt also Eingang 1, 3, 5, aktiviert den Start und dann ist im
Register erst das Ergebnis für 1, wird dann von 3 und dann von 5
überschrieben. Und ohne DMA zum Abholen ist das IMHO etwas unbrauchbar.
Spätestens wenn ein anderer Interrupt dazwischen kommt, der länger als
eine Konvertierung dauert, geht es schief. Meine Funktion oben triggert
hingegen immer nur einen Kanal, so dass das da kein Problem wäre die
Timings zu beschleunigen.
Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.
Wichtige Regeln - erst lesen, dann posten!
Groß- und Kleinschreibung verwenden
Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang