Interne Echtzeituhr des STM32 nutzen
Ein Vorteil neben mehr Speicher und Geschwindigkeit des STM32F103 gegenüber dem Arduino Uno ist die bereits integrierte Echtzeituhr (Real Time Clock, RTC).Zusatzplatinen wie das hier vorgestellte RTC DS1307-Modul kann man sich damit sparen. Es ist bereits alles an Bord, was man braucht: Funktionalität im STM32F103 und 32'768 Hz-Oszillator auf dem Board. Nur eine Batterie muss man noch anschließen.
Das hört sich in der Theorie alles ganz einfach an, doch in der Praxis tat sich mir der eine oder andere Stolperstein auf.
Natürlich versuchte ich wieder, mir Arbeit zu sparen und eine Library zu benutzen. Die STM32RTC-Library (https://github.com/stm32duino/STM32RTC) las sich gut. Der Copyright-Hinweis im Source lautete auf (c) 2017 STMicroelectronics und als Author wurder WI6LABS genannt. Natürlich nahm ich an, das WI&LABS ein Team bei STM ist und die Jungs werden ja wohl Ahnung haben. Also sollte meine erste Wahl darauf fallen.
Doch wenn ich diese Lib benutzen wollte, musste ich auch das Board STM32 Cores by STMicroelectronics (https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json in den Voreinstellungen) installieren. Das las sich auch ganz gut. Sie enthält Kern-Libraries wie etwa den HAL (hardware abstraction layer) für die Umsetzung von Standard-API-Aufrufen zu performant Aufrufen, die der STM32 versteht. Außerdem sind die leichtgewichtige Low-Layer (LL) APIs dabei, die performante und effiziente API Aufrufe zur Verfügung stellen. Und das vom Board-Hersteller selbst. Das musste doch taugen.
Ein bisschen in den Sourcen gestöbert machte das alles auch einen guten, vernünftigeren Eindruck.
Jetzt musste ich allerdings das Board umstellen auf Generic STM32F103C, sonst bekam ich Kompilierungsfehler. Die Sourcen für das RTC-Beispiel machte vom den Core-Libraries Gebrauch, die nur eingebunden wurden, wenn dieses Board gewählt wurde.
Das Problem: Damit war kein Upload mehr mit dem STMduino-Bootloader möglich, hier fehlte einfach die Auswahlmöglichkeit.
Aber jetzt ging es erst einmal darum, die Echtzeituhr des STM32 nutzen zu können. Also lud ich das Example mit meinem ST-Link hoch. Diese Option war ja noch da.
Das Beispielprogramm war ganz gut dokumentiert und die Aufrufe zum Zugriff auf die RTC intuitiv zu verstehen.
Doch obwohl es in der readme.md auf der GitHub-Seite heißt:
...- und ich hatte die neueste Core Version - wurde bei jedem Reset die RTC zurückgesetzt. Ob ich dabei 3 Volt am VBat-Pin anliegen hatte oder nicht, spielte keine Rolle - immer war die Zeit weg.Since STM32 Core version > 1.5.0 Reset time management By default, if a time is set it will not be reset afer a reboot. Using begin(true) or begin(true, HOUR_24) will reset the RTC registers.
Das ist natürlich nicht Sinn einer RTC. Also machte ich mich auf die Suche nach einer funktionierenden RTC-Library für den STM32. Die Core-Boards habe ich dann auch wieder deinstalliert, weil mir der direkte Upload via USB auf den STM32 doch wichtig ist.
Endlich eine STM32-RTC-Lib, die funkioniert
Nach mehreren Tagen fand ich endlich etwas, das funktionierte, die RTC-Library RTClock von Roger Clark, die auch mit meiner normalen STM32-Board-Definition lief.Und siehe da: hier wird die Zeit nicht zurückgesetzt bei einem Reset und wenn an VBat 3V anliegen, behält der STM32 auch die Zeit, wenn der USB-Stecker abgezogen wird und der STM32 stromlos gemacht wird. Die Uhr läuft, wie sich das gehört, weiter.
Den Source und das Beispiel fand ich nicht ganz so gut dokumentiert und intuitiv wie den von STM, aber dafür funktionierte das Zeug, wie es soll. Und darauf kommt es an.
Eigene Erweiterungen
Das in der Lib zumeist mit time_t, also vergangene Sekunden seit dem 1. Januar 1970, gearbeitet wird, gefiel mir nicht so gut, so dass ich den Code angepasst habe, damit er mit DateTime-Strukturen umgehen kann. Mit den Funktionenvoid setRtcDateTime (RTClock rtclock, DateTime dat)
void setRtcDateTime (RTClock rtclock, time_t tt)
DateTime getRtcDateTime (RTClock rtclock)
time_t getRtcTimeT (RTClock rtclock)
DateTime getCompileDateTime ()
void storeDateTimeInString (DateTime dat, char *buf, boolean withWeekday)
uint32_t getDateAsUint32 (DateTime dat)
uint32_t getTimeAsUint32 (DateTime dat)
uint64_t getDateTimeAsUint64 (DateTime dat)
Einen Alarm zu setzen, der dann eine Interrupt-Funktion zum entsprechenden Zeitpunkt ausführt ist auch möglich:
// Alarm setzen in 30 Sekunden
rtclock.createAlarm(irq_alarm, (rtclock.getTime() + 30));
// Alarm setzen zu einer bestimmten Zeit
DateTime datAlarm;
datAlarm=getRtcDateTime (rtclock);
datAlarm.hour=17; datAlarm.minute=0; datAlarm.second=0;
rtclock.createAlarm(irq_alarmzeit, (timetFromDateTime(rtclock, datAlarm)));
Zeit auch beim stromlosem STM32 erhalten
RTC-Module für den Arduino haben normalerweise eine Aufnahme für eine Knopfzelle, die dafür sorgt, dass die Echtzeituhr auch weiterlaufen kann, wenn der Arduino vom Strom getrennt wird. So muss man nicht jedesmal die Zeit neu stellen.
Die Blue Pill hat dafür einen extra Anschluss namens VBat (meist VB oben rechts auf der Platine), an der man eine Batterie anschließen kann. Ich habe hier einfach 2 AA-Zellen genommen, die zusammen 3V liefern und wohl nahezu ewig halten werden. Daraus wird die sogenannte Backup Domain (und nur diese) weiter mit Strom versorgt, auch wenn der STM32 stromlos ist.
Zur Backup-Domain gehören:
- die Echtzeituhr
- der Quartz mit 32768 Hertz, der vom LSE Timer benutzt wird. Darum ist es auch wichtig, den LSE-Timer für die RTC zu benutzen. Das macht auch Sinn, denn dies dürfte der genaueste Quartz on board sein.
- die BKP (Backup) Register mit zehn frei belegbaren 16-bit-Worten (also 20 Bytes) Speicher, darunter für die RTC-Kalibrierung und Einbruchserkennung
- die RCC BDCR Register
Hier noch ein kleines Demonstrationsvideo, dass zeigt, dass die Batterie an VBat auch für das Weiterlaufen der RTC ohne Strom am STM32 funktioniert:
Source-Code
Ich habe den Code in drei Dateien aufgespaltet: STM32-RTC.ino, DateTime.h und RTCInterface.h. Diese am besten in der Arduino IDE in eigenen Tabs unter diesen Namen speichern, damit es keine Probleme beim Kompilieren gibt. Außerdem braucht es die oben erwähnte Library RTClock von Roger Clark.STM32-RTC.ino (klicken, um diesen Abschnitt aufzuklappen)
////////////////////////////////////////////////////////
// (C) 2019 by Oliver Kuhlemann //
// Bei Verwendung freue ich mich über Namensnennung, //
// Quellenangabe und Verlinkung //
// Quelle: http://cool-web.de/arduino/ //
////////////////////////////////////////////////////////
// uses https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/master/STM32F1/libraries/RTClock
#include <RTClock.h>
#include "DateTime.h"
#include "RTCInterface.h"
// den genauen 32768 Hertz-Timer für die Uhr benutzen (LSE),
// auch ist nur der LSE durch VBat abgesichert
RTClock rtclock (RTCSEL_LSE);
void irq_alarm() {
Serial.println ("\nALARM!!!\n");
}
void irq_alarmzeit() {
Serial.println ("\nIT'S TEA TIME!!!\n");
}
void setup() {
char buf[25];
//Serial.begin(115200);
pinMode(PC13, OUTPUT);
delay(1000); // auf die ser. Schnittstelle warten
Serial.println (F("Compile-DateTime:"));
Serial.println (__DATE__);
Serial.println (__TIME__);
DateTime compdat;
DateTime rtcdat;
compdat = getCompileDateTime();
storeDateTimeInString (compdat, buf, false); // Compile-Datum ausgeben
Serial.println (buf);
storeDateTimeInString (compdat, buf, true); // Compile-Datum mit Wochentag
Serial.println (buf);
uint64_t ts64;
ts64=getDateTimeAsUint64(compdat); // Compile-Datum und Zeit als Zahl
Serial.println (ts64);
uint32_t ts32;
ts32=getDateAsUint32(compdat); // Compile-Datum als Zahl
Serial.println (ts32);
ts32=getTimeAsUint32(compdat); // Compile-Zeit als Zahl
Serial.println (ts32);
Serial.println (F("\nRTC-DateTime:"));
rtcdat = getRtcDateTime(rtclock);
storeDateTimeInString (rtcdat, buf, true); // RTC-Datum ausgeben
Serial.println (buf);
if ( getDateAsUint32(rtcdat) < getDateAsUint32(compdat)) { // RTC-Zeit aus der Vergangenheit, kann nicht stimmen
// Kopilierungszeit in Echtzeituhr speichern
setRtcDateTime (rtclock, compdat);
Serial.println (F("RTC-Uhrzeit auf Kompilierungszeit gesetzt."));
// ansonsten mit der Zeit in der RTC weitermachen
}
// Alarm setzen in 30 Sekunden
rtclock.createAlarm(irq_alarm, (rtclock.getTime() + 30));
// Alarm setzen zu einer bestimmten Zeit
DateTime datAlarm;
datAlarm=getRtcDateTime (rtclock);
datAlarm.hour=17; datAlarm.minute=0; datAlarm.second=0;
rtclock.createAlarm(irq_alarmzeit, (timetFromDateTime(rtclock, datAlarm)));
}
void loop() {
DateTime rtcdat;
char buf[25];
time_t tt; // Sekunden seit 01.01.1970
// tm_t mtt; // struct (year (+1970), month, day, weekday, weekday, pm, hour, minute, second
if ( Serial.available() > 14 ) { // 20190515-091017 // setzen der Uhrzeit per Ser-Schnittstelle
DateTime serdat;
for (byte i = 0; i < 15; i++) {
buf[i] = Serial.read();
}
Serial.flush();
buf [15] =0;
sscanf (buf, "%4d%2d%2d-%2d%2d%2d", &serdat.year, &serdat.month, &serdat.day, &serdat.hour, &serdat.minute, &serdat.second);
setRtcDateTime (rtclock, serdat);
}
tt = rtclock.getTime();
Serial.println(tt);
rtcdat = getRtcDateTime(rtclock);
storeDateTimeInString (rtcdat, buf, true); // RTC-Datum mit Wochentag
Serial.println (buf);
delay (1000);
digitalWrite (PC13, !digitalRead(PC13));
}
DateTime.h (klicken, um diesen Abschnitt aufzuklappen)
////////////////////////////////////////////////////////
// (C) 2019 by Oliver Kuhlemann //
// Bei Verwendung freue ich mich über Namensnennung, //
// Quellenangabe und Verlinkung //
// Quelle: http://cool-web.de/arduino/ //
////////////////////////////////////////////////////////
struct DateTime {
uint16_t year=0;
uint8_t month=0;
uint8_t day=0;
uint8_t hour=0;
uint8_t minute=0;
uint8_t second=0;
uint8_t weekday=0;
};
static const char *weekDays[7] = {
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
};
static const char *monthNames[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
uint8_t calcWeekday (int d, int m, int y) {
uint8_t w;
if (m < 3) y -= 1;
w = ((d + (int) (2.6 * ((m + 9) % 12 + 1) - 0.2) + y % 100 + (int) (y % 100 / 4) + (int) (y / 400) - 2 * (int) (y / 100) - 1) % 7 + 7) % 7 + 1;
return w;
}
DateTime getCompileDateTime () {
// __DATE__ = May 9 2019
DateTime dat;
char monthName[20];
uint8_t day = 0;
uint8_t month = 0;
uint16_t year = 0;
char compDate[13];
strncpy(compDate, __DATE__, 12);
sscanf (compDate, "%s %2d %4d", monthName, &day, &year);
for (int i = 0; i < 12; i++) {
if (strncmp (monthNames[i], monthName, 3) == 0) {
month = i + 1;
break;
}
}
dat.year = year;
dat.month = month;
dat.day = day;
// __TIME__ = 20:56:25
uint8_t hour = 0;
uint8_t minute = 0;
uint8_t second = 0;
sscanf (__TIME__, "%2d:%2d:%2d", &hour, &minute, &second);
dat.hour = hour;
dat.minute = minute;
dat.second = second;
dat.weekday = calcWeekday (dat.day, dat.month, dat.year);
return dat;
}
void storeDateTimeInString (DateTime dat, char *buf, boolean withWeekday) { // buf min. 20 bytes, mit Weekday mit 25
if (withWeekday) {
snprintf (buf, 25, "%s, %d-%02d-%02d %02d:%02d:%02d",
(dat.weekday <1 || dat.weekday >7) ? "???" : weekDays[dat.weekday - 1],
dat.year, dat.month, dat.day,
dat.hour, dat.minute, dat.second);
} else {
snprintf (buf, 20, "%d-%02d-%02d %02d:%02d:%02d",
dat.year, dat.month, dat.day,
dat.hour, dat.minute, dat.second);
}
}
uint32_t getDateAsUint32 (DateTime dat) { // gibt z. B. 20190510 zurück, alles Ziffern YMD
uint32_t ts;
ts=dat.year*10000;
ts+=dat.month*100;
ts+=dat.day;
return ts;
}
uint32_t getTimeAsUint32 (DateTime dat) { // gibt z. B. 20190510155906 zurück, alles Ziffern YMDhms
uint32_t ts;
ts=dat.hour*10000;
ts+=dat.minute*100;
ts+=dat.second;
return ts;
}
uint64_t getDateTimeAsUint64 (DateTime dat) { // gibt z. B. 20190510155906 zurück, alles Ziffern YMDhms
uint64_t ts;
ts=dat.year*10000000000;
ts+=dat.month*100000000;
ts+=dat.day*1000000;
ts+=dat.hour*10000;
ts+=dat.minute*100;
ts+=dat.second;
return ts;
}
RTCInterface.h (klicken, um diesen Abschnitt aufzuklappen)
////////////////////////////////////////////////////////
// (C) 2019 by Oliver Kuhlemann //
// Bei Verwendung freue ich mich über Namensnennung, //
// Quellenangabe und Verlinkung //
// Quelle: http://cool-web.de/arduino/ //
////////////////////////////////////////////////////////
// uses https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/master/STM32F1/libraries/RTClock
time_t timetFromDateTime (RTClock rtclock, DateTime dat) {
time_t tt;
tm_t mtt;
mtt.year = dat.year-1970;
mtt.month = dat.month;
mtt.day = dat.day;
mtt.hour = dat.hour;
mtt.minute = dat.minute;
mtt.second = dat.second;
mtt.weekday = dat.weekday == 1 ? 7 : dat.weekday-1;
tt = rtclock.makeTime(mtt);
return tt;
}
void setRtcDateTime (RTClock rtclock, DateTime dat) {
time_t tt;
tt=timetFromDateTime (rtclock, dat);
rtclock.setTime(tt);
}
void setRtcDateTime (RTClock rtclock, time_t tt) {
rtclock.setTime(tt);
}
DateTime getRtcDateTime (RTClock rtclock) {
DateTime dat;
time_t tt;
tm_t mtt;
tt=rtc_get_count();
rtclock.breakTime(tt, mtt);
dat.year = mtt.year+1970;
dat.month = mtt.month;
dat.day = mtt.day;
dat.hour = mtt.hour;
dat.minute = mtt.minute;
dat.second = mtt.second;
dat.weekday = mtt.weekday == 7 ? 1 : mtt.weekday+1;
return dat;
}
time_t getRtcTimeT (RTClock rtclock) {
time_t tt;
tt=rtc_get_count();
return tt;
}