
In diesem Teil des Tutorials richten wir einen Watchdog-Timer ein, um unseren Tag zurückzusetzen, falls das Programm „einfriert“. Der fertige Code dieses Blogbeitrags kann bei Ruuvi GitHub im ruuviblog-Branch, Tag 3.15.0-alpha, heruntergeladen werden.
Bitte folge Teil 1 der Serie für Details zum Klonen des Repositories und Kompilieren des Codes. Die finale Hex-Datei dieses Tutorials kann vom Ruuvi Jenkins heruntergeladen werden.
Watchdog-Timer
„Ein Watchdog-Timer (WDT) ist ein Hardware-Timer, der automatisch einen System-Reset auslöst, wenn das Hauptprogramm es versäumt, ihn regelmäßig zu bedienen. Er wird häufig verwendet, um ein eingebettetes Gerät automatisch zurückzusetzen, das aufgrund eines Software- oder Hardwarefehlers hängen bleibt.“ — MBED
Da unser Programm immer komplexer wird, steigt auch die Anzahl der Bugs in der Firmware. Selbst wenn wir perfekten Code schreiben würden, könnten externe Einflüsse wie ein Spannungseinbruch oder ein extremes Nahfeldkommunikations-Feld (NFC) dazu führen, dass unser Programm blockiert.
Noch schlimmer als die bloße Funktionsunfähigkeit ist, dass ein eingefrorener Tag seine Batterie innerhalb weniger Tage leeren könnte. Der Watchdog ist unsere letzte Verteidigungslinie gegen solche Fehler.
Watchdog-Schnittstelle
Einfach ist gut. Wenn es um den Watchdog geht, wollen wir die Komplexität auf einem absoluten Minimum halten. Wir definieren zwei Funktionen: Initialisierung und Feed. Die Initialisierung nimmt die Anzahl der Millisekunden, bevor der Watchdog-Timer einen Reset auslöst, als Parameter entgegen, und Feed hat gar keinen Parameter – es setzt lediglich den Watchdog-Timer zurück.
/**
* Initializes watchdog module.
* After initialization watchdog must be fed at given interval or the program will reset.
* There is not way to uninitialize the watchdog.
* Consider bootloader watchdog interval on setup.
*
* parameter interval: how often the watchdog should be fed.
*
* Return RUUVI_DRIVER_SUCCESS on success, error code on failure.
*/
ruuvi_driver_status_t ruuvi_interface_watchdog_init(uint32_t interval);
/**
* "Feed" the watchdog, resets the watchdog timer.
* This must be called after watchdog initialization or the program will reset.
*/
ruuvi_driver_status_t ruuvi_interface_watchdog_feed(void);Watchdog-Treiber
Die Implementierung der Schnittstellenfunktionen ist einfach genug: Wir kapseln Aufrufe an das Nordic SDK.
nrf_drv_wdt_channel_id m_channel_id;
/**
* @brief WDT events handler.
*/
void wdt_event_handler(void)
{
// NOTE: The max amount of time we can spend in WDT interrupt is two cycles of 32768[Hz] clock
// - after that, reset occurs
}
/**
* Initializes watchdog module.
* After initialization watchdog must be fed at given interval or the program will reset.
* There is not way to uninitialize the watchdog.
* Consider bootloader watchdog interval on setup.
*
* parameter interval: how often the watchdog should be fed.
*
* Return RUUVI_DRIVER_SUCCESS on success, error code on failure.
*/
ruuvi_driver_status_t ruuvi_interface_watchdog_init(uint32_t interval)
{
uint32_t err_code = NRF_SUCCESS;
nrf_drv_wdt_config_t config = NRF_DRV_WDT_DEAFULT_CONFIG;
config.reload_value = interval;
err_code = nrf_drv_wdt_init(&config, wdt_event_handler);
err_code = nrf_drv_wdt_channel_alloc(&m_channel_id);
nrf_drv_wdt_enable();
return ruuvi_platform_to_ruuvi_error(&err_code);
}
/**
* "Feed" the watchdog, resets the watchdog timer.
* This must be called after watchdog initialization or the program will reset.
*/
ruuvi_driver_status_t ruuvi_interface_watchdog_feed(void)
{
nrf_drv_wdt_channel_feed(m_channel_id);
return RUUVI_DRIVER_SUCCESS;
}Ein wichtiger Fallstrick ist die Interrupt-Prioritätsstufe – wenn das Programm mit einer höheren oder gleichen Priorität hängen bleibt wie unser Watchdog-Interrupt, würde der Interrupt nicht ausgeführt werden. Wir wählen Stufe 2, die höchste für die Anwendung verfügbare Interrupt-Stufe, und verschieben den Bluetooth-Radio-Aktivitäts-Interrupt nach unten auf Stufe 3.
// <e> NRFX_WDT_ENABLED - nrfx_wdt - WDT peripheral driver
//==========================================================
#ifndef NRFX_WDT_ENABLED
#define NRFX_WDT_ENABLED 1
#endif
// <o> NRFX_WDT_CONFIG_BEHAVIOUR - WDT behavior in CPU SLEEP or HALT mode
// <1=> Run in SLEEP, Pause in HALT
// <8=> Pause in SLEEP, Run in HALT
// <9=> Run in SLEEP and HALT
// <0=> Pause in SLEEP and HALT
#ifndef NRFX_WDT_CONFIG_BEHAVIOUR
#define NRFX_WDT_CONFIG_BEHAVIOUR 1
#endif
// Default WDT reset interval in ms
#ifndef NRFX_WDT_CONFIG_RELOAD_VALUE
#define NRFX_WDT_CONFIG_RELOAD_VALUE 12000
#endif
#ifndef NRFX_WDT_CONFIG_IRQ_PRIORITY
#define NRFX_WDT_CONFIG_IRQ_PRIORITY 2
#endifTesten
Wir werden keinen separaten task_watchdog erstellen, sondern initialisieren den Watchdog standardmäßig beim Booten. Wenn wir Unit-Tests ausführen, wird der Watchdog erst nach Abschluss der Unit-Tests initialisiert, da die Tests recht viel Zeit in Anspruch nehmen können.
Für die Zwecke des Tests füttern wir den Watchdog nur bei Tastendruck – wenn die Taste nicht alle 12 Sekunden gedrückt wird, wird der Tag zurückgesetzt.

Wir können sehen, wie die FIFO-Lesevorgänge aus unserer vorherigen Version ausgeführt werden, wobei der letzte gedruckte Zeitstempel bei 11777 ms liegt. Dann wird der Tag zurückgesetzt und die Selbsttests laufen erneut ab. Diesmal füttern wir den Watchdog zweimal; unser Tag wird erst zurückgesetzt, wenn wir die Watchdog-Zeit ablaufen lassen.
Leistungsprofilierung
Im letzten Teil der Serie blieben wir bei einem etwas hohen Verbrauch von 29,4 μA stehen. Schauen wir uns an, wie unser Profil durch den Watchdog-Timer beeinflusst wird.

Der Verbrauch liegt nun bei 29,7 μA, also 0,3 μA höher als zuvor. Dies liegt innerhalb der Fehlertoleranz der Messung; in jedem Fall können wir feststellen, dass der Anstieg des Stromverbrauchs geringfügig ist.
Fazit
Wir haben nun eine Watchdog-Funktionalität zu unseren Tags hinzugefügt und damit die Zuverlässigkeit der RuuviTags im Einsatz weiter verbessert.
Bleib dran und folge @ojousima und @ruuvicom auf Twitter für #FirmwareFriday-Beiträge!