Ein Mann verliert sich

kps 2025-09-13

Ich weiß nicht ob es nur mir so geht oder es alle Menschen gleichermaßen trifft.

Mann möchte eine Problem (eine Aufgabe) lösen und wird dabei mit einer Kette von Unteraufgaben konfrontiert.

Tathergang

Auf dem Weg zur Kommunikation zweier PICOs per LoRa und JSON key:value-pairs stellte sich natürlich die Frage nach den möglichen zu sammelnden Daten.

Eine unvollständige Liste und damit stets erweiterbar wäre folgende:

  • Wetterdaten
  • Steuerdaten
  • Standortdaten
  • Interne Daten

Alle diese Informationen sollen natürlich dann einer Datenbank zugeführt werden zur späteren Auswertung.

Und damit ist eines immer verbunden: die Zeit.

Der naive Betrachter würde natürlich antworten:

„Ist doch kein Problem gibt es doch als Internetzeit, GPS-Zeit, Funkzeit, etc.“

Das ist alles richtig.

Was aber wenn kein Internet, kein Mobilfunk, kein GPS vorhanden sind?

Dann sollte man über Redundanz nachdenken und verschiedene Quellen benutzen, die hoffentlich nicht alle gleichzeitig Wegfallen.

Diese Überlegungen haben leider einige sehr aktuelle Ereignisse untermauert,

Für den Fall der Empfangsstörung muß also eine Überbrückungslösung her.

Und die heißt üblicherweise „realtimeclock“ mit Batteriestützung.

Mit dem Vorsatz den Aufwand auf ein minimum zu reduzieren und damit auch die Umwelt zu schonen, bietet sich eine Lösung per Solarzelle mit Akku an.

Und dann die Nutzung der internen „realtimeclock“ des PICOs.

Nun ja gemäß der allgemeinen Regel, das Bessere ist des Guten Feind, ist man mit der tatsache konfrontiert, dass der RP2040 einen interne „realtimeclock“ besitzt, jedoch sein Nachfolger RP2350 nicht.

Wuschtraum geplatzt.

Der RP2350 besitzt sogenannte „AllwaysOn Timer“. Und die gibt es auch im RP2040.

Sucht man jedoch aus Bequemlichkeit nach einer Arduinobibliothek, wird man nicht fündig.

Wenn man jedoch im PICO SDK sucht findet man entsprechende Funktionen.

Also testen ob diese nutzbar sind.

Arduino versteckt vile Details vor dem unbedarften Programmierer, aber ja, es ist immer noch C/C++ und damit könnnen auch Funktionen in den Untiefen des RPI SDK genutzt werden.

Studieren der Beschreibung

Es gibt eine englische Beschreibung für „pico_aon_timer“.
Hier einige Auszüge:


High Level “Always on Timer” Abstraction.

Detailed Description

This library uses the RTC on RP2040.
This library uses the Powman Timer on RP2350.

This library supports both „aon_timer_xxx_calendar()“ methods which use a calendar date/time (as struct tm), and „aon_timer_xxx()“ methods which use a linear time value relative an internal reference time (via struct timespec).

On RP2040 the non ‘calendar date/time’ methods must convert the linear time value to a calendar date/time internally.

The default implementation of „pico_localtime_r“ is weak, so it can be overridden if a better/smaller alternative is available, otherwise you might consider the method variants ending in „_calendar()“ instead on RP2040.

On RP2350 the ‘calendar date/time’ methods must convert the calendar date/time to a linear time value internally; these methods are:

  • aon_timer_start_calendar
  • aon_timer_set_time_calendar
  • aon_timer_get_time_calendar
  • aon_timer_enable_alarm_calendar

This conversion is handled by the pico_mktime method.
By default, this pulls in the C library „mktime“ method which can lead to a big increase in binary size.

The default implementation of „pico_mktime“ is weak, so it can be overridden if a better/smaller alternative is available, otherwise you might consider the method variants not ending in „_calendar()“ instead on RP2350.


Und damit war klar, es werden die vier „_calendar()“ Funktionen benutzt.

Ist doch also ganz einfach.

  • Generiere einen Anfangszeitstempel, per „Was oder wie auch immer“
  • Lade den Kalender
  • Starte den Kalender
  • Generiere einen Alarminterrupt jede Sekunde und lese den aktuelle Zeitstempel um ihn dann anzuzeigen oder mit anderen Informationen zu speichern.

Nun ja, ganz so einfach ist es dann doch nicht.

Details zu den „_calendar()“ Funktionen:


Set the current time of the AON timer.

Start the AON timer running using the specified timespec as the current time.

Get the current time of the AON timer.

Enable an AON timer alarm for a specified time.

Alle diese Funktionen haben die Struktur „tm“ als Parameter.
Wo kommt die her?
Antwort: Sie ist definiert in „time.h“.

Diese Struktur einfach immer eine Sekunde zu erhöhen ist wohl doch nicht so einfach, da die einzelnen Mitglieder unabhängig voneinander sind.

Dazu kommt noch das Irgendwer mal festgelegt hat, dass die Minute nur 60 Sekunden, die Stunde 60 Minuten, der 24 Stunden, der Monat mal 28 oder 29 oder 30 oder 31 Tage und das Jahr mal 365 oder 366 Tage hat.

Was hatte der wohl geraucht?

Warum nicht 100 Sekunden und 100 Minuten und 100 Stunden … , dann könnte man ganz einfacu hochzählen.

Nun ja, wir Menschen können nun mal nicht ignorieren, dass es äußere Einflüsse gibt die unser leben bestimmen.

Die Erde, und nicht die Sonne, umkreist die Sonne.
Und eine Runde nennen wir ein Jahr.

Die Erde dreht sich in diesem Jahr ca, 365 mal um sich selbst.
Diese Drehungen nennen wir einen Tag.

Der Mond umrundet die Erde in diesem Zeitraum ca. 12 mal.
Diese Umkreisungen nennen wir einen Monat.

Damit ist geklärt wie unsere Daten zustande kommen.

Aber was ist mit Stunden, Minuten unnd Sekunden?

Hier können wir uns bei den Altvorderen aus Babylonien, Ägypten und den Griechen bedanken.
Zu unterschiedlichen Zeiten haben die den Tag in zwei Hälften zu je 12 Stunden unterteilt.
Später wurde diese Unterteilung verfeinert und im Mittelalter wurden dann 60 Minuten und 60 Sekunden eingeführt,

Warum 60?
Warum nicht?

60 läßt sich durch 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30 und 60 teilen.

In dieser Zeit war auch ein Dutzend mit 12 Einheiten üblich als Anzahlangabe.
Ein Dutzend² = 1 Gros = 144, aber ich will das hier nicht vertiefen. 😉

Zurück zu unserer Struktur.
Leider zeigt diese definition nicht alles wie die Wertebereiche.
Und da wird es gruselig.

$$
\begin{array}
{ c | c | c | c }
member & type & meaning & range\\
\hline
tm_sec & int & seconds\ after\ the\ minute & 0 – 61 \\
tm_min & int & minutes\ after\ the\ hour & 0 – 59 \\
tm_hour & int & hours\ since\ midnight & 0 – 23 \\
tm_mday & int & day\ of\ the\ month & 1 – 31 \\
tm_mon & int & months\ since\ January & 0 – 11 \\
tm_year & int & years\ since\ 1900 & \\
tm_wday & int & days\ since\ Sunday & 0 – 6 \\
tm_yday & int & days\ since\ January\ 1 & 0 – 365 \\
tm_isdst & int & Daylight\ Saving\ Time\ flag & \\
\end{array}
$$

Hieraus erfolgt die Erkenntnis, ganz so einfach sind die vier Funktionen doch nicht zu benutzen.
Wir müssen irgendwie Korrekturen einbauen.

Diese zu finden und sinnvoll anzuwenden war nicht einfach, da sich offensichtlich bis jetzt Niemand mit dieser Problematik beschäftigt hat.

Das Internet gab nur fragmentarische Lösungen und die gängigen AI-Helfer fingen an zu phantasieren, da ihr Kenntnisstand nicht aktuell war.

Also suchen, lesen und lernen, lernen, lernen (Lenin).

Dann das gelernte Wissen umsetzen.

Hier nun ein erstes Zwischenergebnis, das auf dem Arduinomonitor den Zeitstempel sekündlich ausgibt.

Noch von einem fixen Zeitstempel, der dann durch einen empfangenen ersetzt werden soll.

Da ich noch aus einer Zeit komme als Festplattenplatz noch richtig teuer war und eine gewisse Firma mit drei Buchstaben alle ihre Produkte zu Standards erklärte, habe ich mich im Studium sofort zu unabhängigen Lösungen hingezogen gefühlt, die dann auch noch universell angewandt werden konnten an Stelle von „Business Orientierten Sprachen“ wie COBOL etc.

Aus dieser Zeit gibt es für mich einige Grundregeln, die ich versuche weitestgehendst einzuhalten.

Mit ALGOL galt:

  • Einrückungen benutzen um den Gültigkeitsbereich zu verdeutlichen
  • Begin über End
  • Aufzählungen mit Komma: „comma first“ und auf neuen Zeilen. Hilft übrigens ungemein in SQL, da dort das Schlüsselwort AS optional ist und wenn Spaltennamen umgetauft werden muß man diesen nicht vor das Komma quetchen.
    Desweiteren wird ein vergessenes Komma nicht als Fehler ausgewiesen, sondern die Daten der Spalte bekommen den Namen der nächsten Spalte.
  • Programme sollten „correct by design“ sein.
    Also immer mit allen möglichen Grenzfällen und bewußt eingefügten Fehlern testen.
    Das dies möglich ist hat Donald Knuth mit dem TeX-Compiler bewiesen.
  • Kein (Unter-)Programm sollte länger als eine A4-Seite sein.
    Das führt automatisch zu einer „top down“ Strategie, vom Groben ins Feine. Es wird nicht gesagt aus wievielen seiten das entgültige Programm besteht. 😎

Diese Grundsätze lassen sich sehrwohl auch auf C/C++ unter Arduino anwenden.

Ja, ein Arduino Sketch kann aus mehreren *.ino Dateien bestehen und die müssen noch nicht mal im selben Ordner sein, sondern nur per Softlink zugeschaltet.
Gleiches gilt für *.h Dateien.

Hier ein Beispiel mit tree:

Also eigene Funktionen, die man immer wieder benutzt können in einem „common“ Ordner gespeichert und dann per Softlink nutzbar werden.

Ja, ja, auch bei den „Amateuren“ tut sich was.

Die Arduinowelt versteckt natürlich einiges was der hardcore Embeddedprogrammierer noch aufschreibt.

Ein neuer Arduino Sketch zeigt lediglich:

Was im Prinzip nur eine Vereinfachung ist.

Was die Arduino Welt so attraktiv macht ist die unmenge von Beispielen und Bibliotheken die, und jetzt kommts, frei zugänglich sind!

Daraus ergibt sich meiner Meinung nach die Pflicht der Gemeinschaft etwas zurückzugeben, was ich mit diesem Artikel machen werde,


Nun zum eigentlichen Program

„tree“ zeigt folgende Dateien:

Arduino arbeitet die *.ino Dateien folgendermaßen ab:

  1. die sketchname.ino
  2. alle anderen *.ino in alfabetischer Reihenfolge.
  3. Pre-Prozessoranweisungen müssen sich in der sketchname.ino befinden.

Mit diesen Regeln kommt man zu sehr übersichtlichen Programmen ohne die Notwendigkeit von Monstern wie Vscode.

Wobei Vscode Vorteile bietet wie zum Beispiel „debuggen“ per PICO Debug Probe.

Diese Aussage hat sich als falsch erwiesen.

Pico Projekte können sehr wohl aus dem Arduino IDE heraus entflöht werden.

Voraussetzung man hat ein Pico Debug Tool oder einen zweiten Pico mit entsprechender Software.

Aber wie schon im Titel angedeutet, führte dies zu weiteren Lernschritten.

Siehe: „Warum Einfaches die Dinge verkompliziert“.

Ein Folgeartikel wird versuchen Anhand des einfachen Programms Blink.ino den Debugprozeß zu erklären.

Spoiler: Die Primitivlösung per print() ist immer noch hilfreich.

In diesem aktuellen Programm wird noch wie vor fast hundert Jahren per print() entflöht.

Hier der Code:

Detailed Description

As usual there should be some #includes providing access to some needed functions.
And yes, I prefer to use „alias“ names for constant parameter values.

First part of setup():


Here the connected LEDs will be initilized and set to be off.

This version was used without the Pico Debug Tool.

Printing is done via the common USB UART.

If you use the Pico Debug Tool this output will be re-routed though the connection between the target Picos UART0 at GP0 and GP1, pins 1 and two, but then Serial must be modified to Serial1.

I will do that modification in the near future after mastering the Arduino Debugger (see above).

The code

tries to avoid that the execution of the program hangs because there is no USB connection.

The two test functions will be explained later.

Create a start timestamp

This is creating a start time using fixed values which maybe replaced by values read from any available time source.
My goal is to get from a DCF77 transmitter.

But first let’s observe the make_time() function, which returns a corrected timestamp.

The function accepts six integer values with normal human readable values for the various portions of a timestamp.
These values will be saved int a struct tm but corrected according to the allowed value ranges.
The important step is:

From the C-library:

Syntax

Following is the syntax of the C library mktime() function −

time_t mktime(struct tm *timeptr)

Parameters

This function accepts only a single parameter −

  • timeptr − This is the pointer to a time_t value representing a calendar time, broken down into its components.
  • Below is the detail of timeptr structure −

Return Value

This function returns a time_t value corresponding to the calendar time passed as a parameter.
The error has occured when it returns -1.

This function normalizes values which are outside of the valid ranges, that means if an incremented second value overflows it will be set to zero and the minutes will be incremented.


And this continues up to the year value.

The function print_tm():

prints the timestamp in human readable form with leading zeros.

Start the AON timer:

Seems to be quite self explaining.

Enable recurring interrupts every second

What is the alarm_handler() doing?

The LED is helping to verify if the alarm_handler is executed.

The function schedule_next_from() re-enables the alarm_handler.

Basically it repeats all the steps from the setup(), but now every second.

Last not least we will tell the endless loop that an alarm was detected:

And what is the endless loop doing?

If the flag from the alarm handler has been set the flag will be cleared and the current content of the AON timer will be printed.

Instead of just printing the data it maybe used also to control a clock with a TFT display or send it to local IoT devices so they now what time it is.

The loop is also using the non-blocking approach for flashing the on board LED.

It took a while to get together all the ingredients, but it is working now.

If I forgot to explain something, please tell me.