Forum: Mikrocontroller und Digitale Elektronik optiboot: Watchdog Timer im Bootloader


von Karol B. (johnpatcher)


Lesenswert?

Hi,

ich bin gerade dabei optiboot [1] an meine Bedürfnisse anzupassen und 
bin dabei auf ein konzeptuelles Problem gestoßen und erhoffe mir nun ein 
wenig Input, um aus dieser "Sackgasse" wieder heraus zu kommen.

Meine Umbauarbeiten [2] waren bisher auch erfolgreich und soweit 
funktioniert alles. Allerdings möchte ich nun auch aus der eigentlichen 
Anwendung heraus in den Bootloader gelangen können (beim Empfangen eines 
bestimmten UART Strings). Dies geschieht derzeit mittels einem "jmp" auf 
die entsprechende Adresse. Allerdings halte ich das für unschön, weil 
ich die Adresse einkodieren muss und sich diese je nach 
Fuse-Einstellungen bzw. konkretem Mikrocontroller unterscheiden.

Erstrebenswerter wäre ein Reset über den Watchdog. Allerdings finde ich 
keine Möglichkeit wie dies in meinem Fall zu bewerkstelligen ist, da der 
Bootloader selbst auch schon den Watchdog benutzt. Das Ganze läuft in 
etwa so ab:

- Der Bootloader wird gestartet und aktiviert den Watchdog (Timeout: 1 
Sekunde).

- Wird während dieser Zeit etwas empfangen, dann wird der Watchdog 
jeweils zurück gesetzt.

- Ist das Programmieren/Verifizieren abgeschlossen, "schlägt" der 
Watchdog irgendwann einmal zu.

- Beim Start wird nun festgestellt, dass das Watchdog Reset Flag (WDRF) 
gesetzt ist. Ist dies der Fall, wird nun mittels "jmp" auf 0x0000 die 
eigentliche Anwendung gestartet.

Soweit, so gut: Problem ist nur, dass ich mit einem Watchdog Timeout in 
der eigentlichen Applikation dafür sorge, dass der Bootloader direkt in 
die Anwendung springt und ihn damit effektiv umgehe. Beim Arduino wird 
das Ganze anders gelöst, weil der Mikrocontroller über eine externe 
Beschaltung resettet wird und somit das EXTRF zur Auswertung zur 
Verfügung steht.

Nun war meine Idee den Bootloader dahingehend abzuändern, dass man das 
Timeout nicht mit dem Watchdog, sondern mittels eines Zählers und der 
dazugehörigen ISRs realisiert. Das würde aber "größere" Umbauarbeiten 
nach sich ziehen, weil das aktuelle Design bisher ohne Interrupts im 
Bootloader auskommt.

Daher wollte ich, bevor ich mich an die Arbeit mache, fragen, ob jemand 
ggf. eine bessere bzw. cleverere Idee hat ;). Ideal z.B. wäre ein echter 
Reset ohne das WDRF zu setzen.

Vielen Dank schon einmal im vorab!

Mit freundlichen Grüßen,
Karol Babioch

[1]: https://code.google.com/p/optiboot/
[2]: 
https://github.com/Wordclock/firmware/blob/master/bootloader/wordboot/wordboot.c

von Chris S. (schris)


Lesenswert?

Nimm eine eeprom Zelle sollte aber nicht 00 sein.
EE = derzeitiger code, ansonsten wird in bootloader verzweigt und vorher 
eeprom Zelle incrementiert. Eventuell auch nur das high nibble und low 
nibble wird als config Register verwendet z.b. um eeprom/flash Bereiche 
für den bootloader zu sperren. Kann auchm HR als das low nibble sein, 
bit 7 und 6 wurde ich aber als Zähler reservieren.

von Karol B. (johnpatcher)


Lesenswert?

Chris S. schrieb:
> Nimm eine eeprom Zelle sollte aber nicht 00 sein.

Vielen Dank für den Vorschlag. Allerdings gefällt mir die Vorstellung 
bei jedem Start im EEPROM herum zu pfuschen. Zum einen sind die 
Schreibzyklen begrenzt (klar sind es genug, es geht mir hier aber ums 
Prinzip) und zum anderen benutzt auch die eigentliche Applikation das 
EEPROM, insofern müsste man Überschneidungen ausschließen, um 
Seiteneffekte auszuschließen.

Mit freundlichen Grüßen,
Karol Babioch

von uwe (Gast)


Lesenswert?

Kannst ja auch nen IO-Pin vom AVR mit dem Reset vom AVR 
verbinden(natürlich noch nen Pullup ran und nen Widerstand in serie zum 
ISP-Header).

von Chris S. (schris)


Lesenswert?

Es ist ein byte an eeprom Speicher. Normalerweise wird die letzte 
Speicherstelle verwendet.
Ansonsten geht nur das hard codieren der Sprung Adresse bzw auch eine 
fixe Adresse dafür zB. 0004 mit einem weiteren long jump dort.

von Amateur (Gast)


Lesenswert?

Es gibt im Prozessor bestimmt ein, zwei Register, die ungenutzt sind.
Diese wiederum sollten sowohl vom Bootloader, als auch vom Programm aus 
erreichbar sein.

Der Bootloader schreibt hier rein was Sache ist und das Programm kann 
dann nachschauen. Indirekte Sprungbefehle gibt es auch für diesen Fall.

Eine weitere Möglichkeit ist die Verwendung von Festadressen im RAM. Für 
die normale Verwendung des Speichers muss ja ein Mechanismus vorhanden 
sein, der verhindert, dass Bootloader und Programm die gleichen Adressen 
verwenden.

>Timeout nicht mit dem Watchdog, sondern mittels eines Zählers und der
>dazugehörigen ISRs realisiert.

Geht auch, kostet aber einen Timer. Diese sind aber oft Mangelware.

von c-hater (Gast)


Lesenswert?

Karol Babioch schrieb:

> Erstrebenswerter wäre ein Reset über den Watchdog.

Nein. Wirklich erstrebenswert wäre, einfach seinen Anwendungscode zu 
beherrschen.

Dann kann der nämlich gezielt aufräumen, bevor er in den Bootloader 
springt. Sprich: Er kann die gesamte Hardware geordnet und gezielt in 
genau den "jungfräulichen" Zustand versetzen, den der Bootloader nach 
einem Reset erwartet.

Und das ist echt nur für die Leute ein Problem, die nicht selber 
programmieren, sondern nur geklauten Code zusammenkleben.

Alle anderen sehen einfach zu jeder init_* eine passende uninit_* 
Routine vor.

Der Sprung in den Bootloader besteht dann darin, die Interrupts zu 
disablen und dann all diese netten uninit_*-Routinen aufzurufen. Wenn 
keiner einen Fehler gemacht hat, dann sieht jetzt jedes verschissene 
Hardware-Register ganz genau so aus wie nach einem PowerUp-Reset. Mit 
einer einzigen generellen Ausnahme: MCUSR. Die allerletzte Aktion vor 
dem Sprung in den Bootloader muß sein, auch hier alle Flags 
zurückzusetzen.

Daran kann der Bootloader dann nämlich völlig problemlos unterscheiden, 
ob er absichtlich von der Anwendung aufgerufen wurde, oder als Folge 
irgendeines Resets (gewollt oder ungewollt) und er kann dann auch die 
Quelle dieses Resets bestimmen und entsprechend verzweigen.

Und ja, bevor du fragst: Es ist ein absolut fantastisches Gefühl, 
wirklich kompetent programmieren zu können und nicht hilflos rumfrickeln 
zu müssen...

von Karol B. (johnpatcher)


Lesenswert?

uwe schrieb:
> Kannst ja auch nen IO-Pin vom AVR mit dem Reset vom AVR
> verbinden(natürlich noch nen Pullup ran und nen Widerstand in serie zum
> ISP-Header).

An der Hardware kann ich leider nichts mehr ändern.

Amateur schrieb:
> Es gibt im Prozessor bestimmt ein, zwei Register, die ungenutzt sind.
> Diese wiederum sollten sowohl vom Bootloader, als auch vom Programm aus
> erreichbar sein.

Wobei das der Philosophie widerspricht, dass Bootloader und Anwendung 
unabhängig voneinander sein sollen. Bei gemeinsam genutzten Registern 
besteht (theoretisch) ja immer die Möglichkeit, dass es zu unerwünschten 
Seiteneffekten kommt, wenn eine der beiden Komponenten "fehlerhaft" 
agiert.

Amateur schrieb:
> Geht auch, kostet aber einen Timer. Diese sind aber oft Mangelware.

Naja, im Bootloader ist das ja nicht wirklich ein Problem, und vor dem 
Verlassen des Bootloaders würde ich den Timer natürlich wieder frei 
geben bzw. zurücksetzen.

c-hater schrieb:
> Nein. Wirklich erstrebenswert wäre, einfach seinen Anwendungscode zu
> beherrschen.

Du bist ja im Forum bekannt für deine "provokanten" und frechen 
Beiträge. Die Sinnhaftigkeit bzw. Korrektheit der Aussagen allerdings 
sind oft anzuzweifeln, so auch in diesem Fall.

c-hater schrieb:
> Und das ist echt nur für die Leute ein Problem, die nicht selber
> programmieren, sondern nur geklauten Code zusammenkleben.

Falls das an mich gerichtet war, so behaupte ich einfach mal, dich 
enttäuschen zu müssen. Ich habe schon ein paar Jährchen 
Programmiererfahrung und hier keine konkrete Programmierfrage gestellt, 
sondern grundsätzlich nach Möglichkeiten und Konzepten gefragt, um mich 
prinzipiell für die beste (TM) entscheiden zu können.

Falls du damit auf das Anpassen des bereits vorhandenen Bootloaders 
anspielst, so kann ich auch das begründen: Ein guter Programmierer fängt 
eben nicht immer bei 0 an, sondern ist auch in der Lage fremden Code zu 
beherrschen und anzupassen. In diesem Fall erschien mir das durchaus 
sinnvoll, weil o.g. Bootloader täglich von vielen tausenden Personen 
getestet wird, und damit im Prinzip alle gängigen Inkompatibilitäten 
zwischen verschiedenen Systemen ausgeschlossen sind. Außerdem ist die 
Auswahl bei meinen Anforderungen nicht unbedingt groß gewesen.

Eine Eigenentwicklung hätte nicht nur mehr Zeit beansprucht (und 
vermutlich mehr unentdeckte Fehler), sondern eben auch das selbe Problem 
mit sich gebracht, weil es sich hier um eine konzeptuelle Frage handelt.

Ich will das jetzt sicherlich zu keinem virtuellen Schwanzvergleich 
unserer Programmierkünste ausarten lassen, aber du scheinst ein Talent 
dafür zu haben technische Diskussionen persönlich werden zu lassen, 
indem du pauschal die Kompetenz des Fragestellers subtil in Abrede 
stellst und gleichzeitig dich und deine subjektiven Fantasien über alles 
andere stellst. Jedenfalls nach meiner Auffassung ist das nicht in 
Ordnung.

c-hater schrieb:
> Dann kann der nämlich gezielt aufräumen, bevor er in den Bootloader
> springt.

Klar ist das eine Möglichkeit. Allerdings kostet das neben dem 
Speicherplatz auch ein bisschen Hirnschmalz, damit die 
De-Initialisierungen geordnet vonstatten gehen. Ich habe mich auch schon 
mehr als nur einmal durch die Quellen von (größeren) Mikrocontroller 
Projekten gewälzt und ein gezieltes De-Initialisieren nur selten 
angetroffen. Wenn dann mit dem Hintergrund, dass man eine gegebene 
Funktionalität auch dediziert abschalten kann bzw. aus 
Energiespargründen - nicht aber, um den Bootloader zu betreten. Dies 
widerspricht nämlich der Philosophie, dass ein Bootloader unabhängig von 
der Anwendungssoftware funktionieren soll. Hast du denn ein paar 
Beispielprojekte für dieses Vorgehen parat?

Dies an sich ist aber noch nicht einmal das Problem, sondern die o.g. 
Abhängigkeiten, die ich damit schaffe. Ein Sprung an die 
Bootloader-Adresse ist nämlich gewissermaßen beliebig. Je nach 
Mikrocontroller und Fuses ändert sich die Adresse. Das ist eine 
potentielle Fehlerquelle - gerade bei der Migration auf andere 
Mikrocontroller-Modelle & Co. Bei einem Reset mittels Watchdog-Timer 
kann dieses Problem nicht auftreten. Ein Sprung aus dem Bootloader 
heraus an die Adresse 0 kann bei AVRs auch nicht schief gehen und somit 
ist dieser Ansatz empfehlenswerter. Das machen die meisten von mir 
angesehen Bootloader auch so, und auch in den App-Notes von Atmel wird 
darauf hingewiesen.

c-hater schrieb:
> Wenn
> keiner einen Fehler gemacht hat, dann sieht jetzt jedes verschissene
> Hardware-Register ganz genau so aus wie nach einem PowerUp-Reset.

Eben, und der Sinn eines Bootloaders ist es gerade nicht die 
Fehlerlosigkeit der Anwendung einzufordern. Fehlerlose Software gibt es 
oftmals nur in der Theorie. Meiner Erfahrung nach sind gerade 
diejenigen, die behaupten "fehlerfrei" zu programmieren, zuverlässige 
Produzenten von schwierig zu debuggendem Fehlverhalten. Ein Update der 
Anwendungssoftware ist in den meisten Fällen nämlich genau dann 
notwendig, wenn irgendetwas nicht wie gewollt funktioniert und sollte 
auch dann noch möglich sein, wenn die Anwendungssoftware "kaputt" bzw. 
nicht vorhanden ist.

c-hater schrieb:
> Und ja, bevor du fragst: Es ist ein absolut fantastisches Gefühl,
> wirklich kompetent programmieren zu können und nicht hilflos rumfrickeln
> zu müssen...

Darf man denn in den Genuss kommen eines deiner fehlerlosen Meisterwerke 
selbst zu begutachten? Oder geschieht all das hinter verschlossenen 
Toren und der hier durch das Forum gegebenen Anonymität?

Mit freundlichen Grüßen,
Karol Babioch

von Karol B. (johnpatcher)


Lesenswert?

Der Vollständigkeit halber möchte ich nun kurz noch den von mir 
verfolgten Ansatz skizzieren. Ich hatte zunächst in der Tat auf einen 
Interrupt-basierten Ansatz gesetzt. Das hat allerdings Anpassungen am 
Makefile erfordert, sodass die entsprechende Interrupt-Tabelle generiert 
und an der richtigen Stelle platziert wird. Zusätzlich war es notwendig 
die Interrupts "umzubiegen" und vor dem Sprung in die Applikation wieder 
"gerade" zu biegen.

Zusammen hat all das einige hundert Bytes an Flash-Speicher gekostet und 
hat sich als unpraktisch erwiesen. Stattdessen benutzte ich nun einen 
Timer/Counter und polle das Timer Overflag Flag. Das Ganze sieht in etwa 
so aus:
1
    uint8_t counter = 0;
2
3
    while(!(UCSR0A & _BV(RXC0))) {
4
5
        if (TIFR0 & _BV(TOV0)) {
6
            counter++;
7
            TIFR0 = _BV(TOV0);
8
        }
9
10
        if (counter > BOOTLOADER_TIMEOUT_COMPARE_VALUE) {
11
            start_application();
12
        }
13
14
    }

Innerhalb von start_application() wird dann die verwendete Hardware 
(Timer, UART, usw.) zurückgesetzt, sodass der Mikrocontroller sich 
effektiv im selben Zustand wie nach einem echten Reset befindet. Der 
Inhalt des MCUSR Registers wird über ein dediziertes Register an die 
eigentliche Anwendung weitergegeben.

Der gesamte Quellcode (d.h. der komplette Bootloader auf Basis von 
optiboot) kann hier [1] eingesehen werden.

Mit freundlichen Grüßen,
Karol Babioch

[1]: 
https://github.com/Wordclock/firmware/blob/master/bootloader/wordboot/wordboot.c

von Karol B. (johnpatcher)


Lesenswert?

Hi,

nochmals ein Addendum für Leute, die das ggf. im Nachhinein finden 
sollten und nachvollziehen möchten. Ich habe den Bootloader mittlerweile 
in ein eigenes Repository ausgelagert, siehe [1]. Der o.g. Link hingegen 
ist nicht mehr gültig.

Mit freundlichen Grüßen,
Karol Babioch

[1]: https://github.com/Wordclock/wordboot

von sep (Gast)


Lesenswert?

Ich weiß nicht, ob die Idee von mir gerade gut ist.
Ist ja nur eine fixe Idee.
In der Praxis ist es ja so, dass die .fini0 Section die letzte section 
ist. Danach kommen im Speicher nur 0xFF --> NOP!!!
die .fini0 Section definiert dann nur eine Endlosschlefe, die man aber 
überschreiben kann!
Also, überschreiben, prüfen ob in den Bootloader gesprungen werden soll 
und laufen lassen. Dann wird er schon im Bootloader landen. Denke ich.
http://www.nongnu.org/avr-libc/user-manual/mem_sections.html#sec_dot_init

Oder etwas so in der Art:
1
int main(void)
2
{
3
  extern uint8_t __data_load_end;
4
  asm volatile( "push %A0" "\n\t"
5
          "push %B0" "\n\t"
6
          "ret"
7
          : : "e" ( &__data_load_end ) );
8
  
9
    while(1)
10
    {
11
        //TODO:: Please write your application code 
12
    }
13
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.