Interne Echtzeituhr des STM32 kalibrieren

Wie man auf die interne Realtime-Clock des STM32 zugreift habe ich ja bereits erläutert.

Diesmal soll es darum gehen, die Gangungenauigkeit auszugleichen, die jede Quarz-Uhr mit sich bringt. Das kennen wir ja bereits von den Zusatzplatinen wie das hier vorgestellte RTC DS1307-Modul. Das Modul, dass ich in meiner 24-Stunden-Uhr mit Neopixel-Ring einsetzte, brachte es ja auch stolze 16 Sekunden Abweichung pro Tag. Das summiert sich im Laufe der Zeit auf.

Natürlich kann man es machen wie im Nachtrag vom 2019-04-03 zu obigem Blog: einfach jeden Tag um Mitternacht die Zeit um die betreffenden Sekunden vor- bzw. zurückstellen. Das funktioniert ganz gut. Meine Wanduhr stimmt immer noch, auch wenn wieder mehr als sechs Wochen vergangen sind.

Die RTC des STM32 hat dafür aber eine elegantere Methode, die allerdings nicht ganz unkompliziert ist und deshalb hier näher erläutert werden soll: die Kalibrierung über Registerwerte, so dass der STM32 intern die Korrekturen vornimmt und uns immer die richtige Zeit liefert.

Große Abweichung der RTC


Normalerweise soll die Echtzeituhr des STM32 ja recht genau gehen, zumindest im Vergleich zu den DS1307-Modulen. Darum war ich auch ein wenig enttäuscht von meiner ersten Messung, die ich mit Hilfe der Uhrenanzeige der Atomuhr des PTB Braunschweig als Vergleichswert durchgeführt habe. Startzeit (RTC und Echtzeit gleich) 21.05.2019 23:57:00 Jetzt Echt 22.05.2019 11:50:00 Jetzt RTC 22.05.2019 11:49:22 Vergangen Echt 11:53:00 Vergangen RTC 11:52:22 Diff Sek. Echt/RTC -38 Diff Sek. Echt/RTC -76.746 pro 24 h Meine RTC geht fast 77 Sekunden pro Tag zu langsam? Was für eine Katastrophe! Wie kann das sein?

Ein Quarz will frei schwingen


Die Erklärung für dieses Verhalten findet sich in einer Notiz im Datenblatt zum F103:
Note: Due to the fact that the switch only sinks a limited amount of current (3 mA), the use of GPIOs PC13 to PC15 in output mode is restricted: the speed has to be limited to 2 MHz with a maximum load of 30 pF and these IOs must not be used as a current source (e.g. to drive a LED).
Die Pins PC13, PC14 und PC15 sollen also max. 3 mA aufnehmen und mit max. 2 MHz und 30 pF Kapazität angesteuert werden.

Das liegt darin begründet, dass die RTC-Schaltung besonders stromsparend ausgelegt ist - die Puffer-Batterie soll ja lange halten - und damit gegen äußere Einflüsse dementsprechend empfindlich.

Nun hatte ich den kompletten Header über alle Pins gelötet und den STM32 in mein Breadboard gesteckt. PC14 und PC15 sind aber direkt mit dem Quarz verbunden. Dadurch habe ich also den Quarz an die Spangen in meinem Breadboard angeschlossen, die wahrscheinlich ganz gute Empfänger für alle möglichen elektromagnetischen Strahlen abgibt.

Da ich die RTC benutzen will, kann ich PC13 bis PC15 sowieso nicht benutzen, also habe ich die Pins kurzerhand ausgelötet, die Uhr neu gestellt, eine gewisse Zeit abgewartet und eine neue Messung durchgeführt:
Startzeit (RTC und Echtzeit gleich) 22.05.2019 14:03:00 Jetzt Echt 23.05.2019 13:06:00 Jetzt RTC 23.05.2019 13:06:01 Vergangen Echt 23:03:00 Vergangen RTC 23:03:01 Diff Sek. Echt/RTC 1 Diff Sek. Echt/RTC 1.041 pro 24 h Na, schau mal einer an! Nur noch eine Sekunde Abweichung pro Tag. Damit kann man doch leben. Das macht pro Jahr eine Abweichung von etwa 6 Minuten.

Kalibrieren durch Register

Wer es noch genauer will, der muss sich leider mit ein paar Registerwerten und deren Berechnung herumschlagen:

Die beiden Register sind RTC-PRL (bestehend aus PRLL (low) und PRLH (high)) sowie BKP->RTCCR aus den Backup-Registern, die uns hier ja bereits begegnet sind. Die Inhalte dieser Register bleiben auch bei Reset und Stromausfall erhalten, solange der STM32 über VBat mit einer 3V Pufferbatterie versorgt wird.

Das Datenblatt gibt uns nähere Informationen zu den beiden Registern:

RTC->PRL RTC prescaler load register (RTC_PRLH / RTC_PRLL) The Prescaler Load registers keep the period counting value of the RTC prescaler. They are write-protected by the RTOFF bit in the RTC_CR register, and a write operation is allowed if the RTOFF value is ‘1’. RTC prescaler load register high (RTC_PRLH) Address offset: 0x08, Write only Reset value: 0x0000 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 xxxxxxxx Reserved xxxxxxxxxxx w w w w Bits 15:4 Reserved, forced by hardware to 0. Bits 3:0 PRL[19:16]: RTC prescaler reload value high These bits are used to define the counter clock frequency according to the following formula: fTR_CLK = fRTCCLK/(PRL[19:0]+1) RTC prescaler load register low (RTC_PRLL) Address offset: 0x0C, Write only Reset value: 0x8000 Note: If the input clock frequency (fRTCCLK) is 32.768 kHz, write 7FFFh in this register to get a signal period of 1 second. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 w w w w w w w w w w w w w w w w Bits 15:0 PRL[15:0]: RTC prescaler reload value low These bits are used to define the counter clock frequency according to the following formula: fTR_CLK = fRTCCLK/(PRL[19:0]+1) Caution: The zero value is not recommended. RTC interrupts and flags cannot be asserted correctly. In RTC->PRL wird sozusagen die Frequenz in Anzahl Takte gespeichert, die der Quarz braucht, um eine wahre Sekunde zu messen. Minus 1.

Würde die RTC ganz genau gehen, würde man hier 32767 für den 32768 Hz-Quarz hineinschreiben, was auch der Standard-Wert ist.

Jede Einheit im RTC->PRL Register, die wie von 32767 abziehen, macht die Echtzeituhr 1 / 32768 * 24 * 3600 = 2.6367 Sekunden pro Tag schneller. Jede Einheit die wie zu 32767 hinzuzählen, macht die Echtzeituhr um 2.6367 Sekunden pro Tag langsamer.

Für unsere Abweichung von 76.7 Sekunden pro Tag, die die Uhr aus dem obigen Beispiel mit den angelöteten PC13-PC15-Headern zu langsam geht, würden wir also 76.7 / 2.6367 = 30 von den 32767 abziehen und kämen auf 32737. Diesen Wert würden wir dann ins Register RTC->PRL schreiben. Unser Quarz schwingt also sozusagen mit 32737+1 = 32738 (statt 32768) Hertz zu langsam.

Mit 30 * 2.6367 sind 79.1 Sek ausgeglichen. 79.1 Sekunden sind allerdings 2.4 s zuviel, die wir über das folgende Register BKP->RTCCR in die andere Richtung ausgleichen können. Damit machen wir die Uhr in 0.0824 s-Schritten langsamer.

Das Datenblatt gibt uns folgende Informationen: BKP->RTCCR Rev. 19-Oct-2007: Section 15.4.1: TIMx control register 1 (TIMx_CR1). Bit 8 and Bit 9 added to Section 6.4.2: RTC clock calibration register (BKP_RTCCR) RTC clock calibration register (BKP_RTCCR) Address offset: 0x2C Reset value: 0x0000 0000 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Reserved xxxxxxxx rw rw rw rw rw rw rw rw rw rw ----- CAL[6:0] ----- Bits 15:10 Reserved, must be kept at reset value. ... Bit 6:0 CAL[6:0]: Calibration value This value indicates the number of clock pulses that will be ignored every 2^20 clock pulses. This allows the calibration of the RTC, slowing down the clock by steps of 1000000/2^20 PPM. The clock of the RTC can be slowed down from 0 to 121PPM. Für jede Einheit, die im Register BKP->RTCCR (max. 127*) steht, wird ein 220-stel, also 1'048'576ste Takt übersprungen. Das macht die Echtzeit-Uhr (1 / 2^20) * 32768 * (1 / 32768 * 24 * 3600) = 0.0824 Sekunden langsamer pro Tag.

* Wenn 127 nicht ausreichen, kann seit Rev. 19-Oct-2007 auch das 8. und 9. Bit des TIMx Register benutzt werden, um Werte bis 511 zu erreichen.

Berechnung der Registerwerte

Zusammengefasst: Normalerweise schwingt der Quarz auf der Blue Pill zu langsam (nämlich, wenn PC13 bis PC15 nicht komplett frei sind, wie im obigen Beispiel) oder evtl. auch etwas zu schnell.

Um auf die richtigen Werte für die Register zu kommen, geht man wie folgt vor: Ich habe mir zur Berechnung eine kleine Excel-Tabelle gebastelt:



Aber natürlich kann man das mit ein bisschen C viel komfortabler regeln:

Source-Code

Hauptprogramm void setup() { ... // Für jede Uhr individuell, muss nach jedem völligen Stromausfall (auch VBat) neu gesetzt werden calibrateRtc(1.04); // RTC geht um 1.04 Sekunden am Tag zu schnell ... } RTCInterface.h //////////////////////////////////////////////////////// // (C) 2019 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// void calibrateRtc (double seksPerDayOffset) { //seksPerDayOffset negativ = RTC läuft zu langsam / positiv = RTC läuft zu schnell double seksPRL = 2.63671875; double seksRTCCR = 0.0823974609375; long prlOffset=0; double seksRest=0; uint32_t valPRL=32767; byte valRTCCR=0; prlOffset = (seksPerDayOffset/-seksPRL)+.99; if (prlOffset < 0) prlOffset--; valPRL= 32767-prlOffset; seksRest = seksPerDayOffset + (prlOffset*seksPRL); valRTCCR = (uint8_t) (seksRest/seksRTCCR+0.5); // runden, um möglichst genau zu sein. Max. Abw. dann 0.041 s rtc_set_prescaler_load(valPRL); // PRL-Register setzen rcc_start_lse(); // Uhr wieder starten, wurde angehalten // BKP->RTCCR-Register setzen ------------ // nur die 6 untersten Bits sind Kalibrierungswert (0-127) if (valRTCCR > 127) valRTCCR -= 128; // RTC clock calibration register (BKP_RTCCR) // Address offset: 0x2C // BKP at 0x40006C00 // RTCCR at 0x40006c2c // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 // Reserved xxxxxxxx rw rw rw rw rw rw rw rw rw rw // ----- CAL[6:0] ----- __IO uint32* tmpreg = (uint32_t*) 0x40006c2c; // BKP_BASE + 0x2c; *tmpreg &= 0xFFFFFFF80; // RTCCR CAL löschen (unteren 7 Bits) *tmpreg |= valRTCCR; // RTCCR CAL setzen } Den RTC->PRL-Wert zu setzen ist einfach, denn hier gibt es in der RTClock.cpp bereits eine fertige Funktion: rtc_set_prescaler_load. Dies hält allerdings die Uhr an. Darum starten wir sie mit rcc_start_lse() erneut. Für ein paar Millisekunden wird die Uhr aber trotzdem stehen. Darum sollte man die Funktion nicht in einer Schleife aufrufen, sondern einmalig im Setup.

Für den BKP->RTCCR-Wert habe ich leider nichts Fertiges gefunden, und benutze deshalb die Register-Adresse 0x40006c2c direkt, um in dessen unteren 7 Bits unseren Ausgleichswert hineinzuschreiben.

Für jede individuelle Blue Pill ist natürlich der Quarz vielleicht ein bisschen anders. Deswegen braucht auch jedes Board seinen eigenen Kalibrierungswert. Wer es 100%ig genau will, muss auch äußere Einflüsse wie die Temperatur am endgültigen Aufstellungsort berücksichtigen und dort seine Messungen durchführen.

Im Grunde genommen ist die Genauigkeit von Haus aus aber ausreichend, falls man nur nicht den Fehler macht, die Pins PC13 bis PC15 mit Headern zu bestücken. Auf jeden Fall ist die STM32-Uhr um Längen besser als die auf einem DS1307-Modul.