Forum: Mikrocontroller und Digitale Elektronik RP2040: Flash (Programm) aus dem Programm updaten


von Ing-Dom (Firma: OpenKNX) (sirsydom)


Lesenswert?

Hallo!

Ich versuche mich aktuell daran auf einem RP2040 (Pi Pico) ein FW-Update 
hinzubekommen.
Ziel ist es später einmal ein Firmwareupdate für Endanwener anzubieten, 
und zwar ein FW-Update über die vorhandene Feldbusanbindung, also ohne 
USB, SWD etc...

Ich hab erstmal ein "Proof-Of-Concept" aufgesetzt und die ganze 
Feldbusgeschichte ausgeklammert.

In der Testumgebung wird ein UF2-File (identisch mit der 
Updatemöglichkeit über den integrierten USB UF2 Bootloader) über 
USB-Serial hochgeladen und im Flash (weit hinter dem Programmbereich + 
0x100000) abgelegt.

Ein Vergleich des Flashinhaltes an XIP_BASE + 0x000000 (Flashadresse 0, 
laufendes Programm über USB BL geladen) und XIP_BASE + 0x100000 (UF2 
über Serial geladen) zeigt keine Unterschiede.

Jetzt gehts an Eingemachte. Ich kann ja nicht den Ast absägen, auf dem 
ich sitze. Also wird die "UpdateFlash" Methode als
void __not_in_flash_func(UpdateFlash)() definiert und liegt daher im 
RAM.
Das funktioniert auch, im MAP-File liest man 0x200000c0 (0x20000000 = 
Start SRAM Adressbereich).

Dann rufe ich 2 Methoden aus dem SDK, und zwar flash_range_erase 
(https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_flash/flash.c#L63) 
und flash_range_reprogramm 
(https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_flash/flash.c#L87).

Die ersten Versuche gingen schief und ich hab gecheckt dass zwar die 
Funktionen im RAM liegen aber nicht alle von diesen Funktionen 
aufgerufenen Funktionen.
Auch das Sektorweise kopieren war quatsch.

Also kopiere ich jetzt alles am Stück und zwar auch noch mit einer neuen 
Funktion erase_and_program (im Endeffekt bei beiden SDK Funktionen 
zusammengeführt).
Die Funktionen verwenden übrigend wieder unterlagerte Funktionen aus dem 
Boot-ROM für die Flash-Operationen.
1
void __no_inline_not_in_flash_func(flash_range_erase_and_program)(uint32_t flash_offs, const uint8_t *data, size_t count) {
2
#ifdef PICO_FLASH_SIZE_BYTES
3
    hard_assert(flash_offs + count <= PICO_FLASH_SIZE_BYTES);
4
#endif
5
    invalid_params_if(FLASH, flash_offs & (FLASH_PAGE_SIZE - 1));
6
    invalid_params_if(FLASH, count & (FLASH_PAGE_SIZE - 1));
7
    void (*connect_internal_flash)(void) = (void(*)(void))rom_func_lookup(rom_table_code('I', 'F'));
8
    void (*flash_exit_xip)(void) = (void(*)(void))rom_func_lookup(rom_table_code('E', 'X'));
9
    void (*flash_range_program)(uint32_t, const uint8_t*, size_t) =
10
        (void(*)(uint32_t, const uint8_t*, size_t))rom_func_lookup(rom_table_code('R', 'P'));
11
  void (*flash_range_erase)(uint32_t, size_t, uint32_t, uint8_t) =
12
        (void(*)(uint32_t, size_t, uint32_t, uint8_t))rom_func_lookup(rom_table_code('R', 'E'));
13
    void (*flash_flush_cache)(void) = (void(*)(void))rom_func_lookup(rom_table_code('F', 'C'));
14
    assert(connect_internal_flash && flash_exit_xip && flash_range_erase && flash_range_program && flash_flush_cache);
15
    flash_init_boot2_copyout();
16
17
    __compiler_memory_barrier();
18
19
    connect_internal_flash();
20
    flash_exit_xip();
21
  flash_range_erase(flash_offs, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD);
22
    flash_range_program(flash_offs, data, count);
23
    flash_flush_cache(); // Note this is needed to remove CSn IO force as well as cache flushing
24
    flash_enable_xip_via_boot2();
25
}

Und so wird das dann gerufen. Die Adressen hab ich mit einer 
"Dry-Run"-Verion mal ausgegeben und da sieht alles gut aus:

flash_range_erase_and_program 0x0 0x10100000 0x11000
1
void __not_in_flash_func(UpdateFlash)()
2
  {
3
    noInterrupts();
4
    rp2040.idleOtherCore();
5
    watchdog_enable(10000, false);
6
7
    uint32_t new_binary_start_ram = m_firstTargetAdr + m_flashoffset; // in RAM address space, so XIP_BASE ist not substracted
8
    uint32_t new_binary_length = m_nextTargetAdr - m_firstTargetAdr;
9
    uint32_t prog_flash_start = m_firstTargetAdr - XIP_BASE; // addresses in UF2 are in RAM address space, substrat XIP_BASE to get FLASH address
10
11
    uint32_t num_sectors = (new_binary_length / FLASH_SECTOR_SIZE) + 1;
12
13
    uint8_t *new_binary_start_ram_p = (uint8_t*)new_binary_start_ram;
14
15
16
    flash_range_erase_and_program(prog_flash_start, new_binary_start_ram_p, num_sectors*FLASH_SECTOR_SIZE);
17
18
    while(1); //wait for WD to restart


Trotzdem haut es am Ende nicht hin. Nach dem Reset bleibt der UF-2 
Bootloader stehen.

Einen Debugger hab ich aktuell nicht (ist unterwegs) und printf oder gar 
gpio debugging funktioniert auch nicht, weil die methoden ja alle im 
überschriebenen Flash liegen.

Jemand eine Idee?
Achja, auf einen "Second-Stage" BL wollte ich verzichten, um die 
Komplexität zu reduzieren und das einfach adaptierbar zu machen.

von Gerd E. (robberknight)


Lesenswert?

Ich stehe demnächst vor einem ähnlichem Problem mit dem RP2040.

Was mir an Lösungen mit einem einzigen Image absolut nicht gefällt, ist 
daß es im Fall von einem Fehler keinen Weg zurück gibt. Ich möchte daher 
2 Images verwenden und dann wird zwischen denen umgeschaltet. Flash ist 
dafür ja normal genug vorhanden.

Also genauer: ich teile den Flash auf in 3 Blöcke: Mini-Bootloader + 
"Partitionstabelle", Image 1, Image2.

Beim Boot lädt der integrierte Bootloader immer meinen Mini-Bootloader. 
Der entscheidet anhand der Metadaten in der "Partitionstabelle" und 
anhand eines Tasters mit dem man im Notfall auf das vorige Image 
zurückgehen kann, welches Image jetzt gestartet werden soll. Dann 
startet er dies.

Innerhalb des Images gibt es dann eine Update-Funktion die ein neues 
Image empfangen kann. Damit wird dann das aktuell nicht laufende Image 
überschrieben. Wenn Prüfsumme etc. ok sind, werden als letztes die Daten 
in der "Partitionstabelle" aktualisiert und ein Reboot ausgelöst.

Das einzige Problem an der Sache ist, daß der XIP-Block leider zu den 
Flash-Adressen kein Offset addieren kann. Die Speicheradressen im Flash 
sind also absolut und daher unterschiedlich, je nachdem ob ich gerade 
Image 1 oder Image 2 verwende.

Ich werde daher wohl jede Version 2x bauen, mit 2 unterschiedlichen 
Linker-Skripts mit den unterschiedlichen Flash-Adressen. Ein Update 
enthält dann beide Images hintereinander und der Code zum Empfangen des 
Updates schreibt nur den einen Teil davon der für die aktuell benötigte 
Image-Adresse passt.

von Ing-Dom (Firma: OpenKNX) (sirsydom)


Lesenswert?

Ich mag gerade die Einfachheit der Lösung, dass eine Funktion im RAM das 
aktuelle Program im Flash überschreibt.

Ich brauch keinen 2ten Bootloader, keine Tabelle, keine zwei 
Linkerscripts... Einfach das UF2, das auch beim Laden mit dem UF2 USB 
Bootlader geladen wird, und fertig.

Schief gehen kann immer was - jedoch wird es nur problematisch, wenn 
etwas in der Funktion schief geht, die aus dem "hinteren" Flashbereich 
dann auf 0x00 kopiert. Eventuelle Checks (Prüfsumme etc..) kann man 
vorher machen.

Aber - der USB UF2 Bootloader ist ja als ROM fest eingebrannt. Der wird 
immer verfügbar sein, egal was beim Update schief geht. Das reicht mir 
als Fallback.


Ich konnte das Problem übrigens zwischenzeitlich lösen:

Die flash_range_ Funktionen sind zwar als __no_inline_not_in_flash 
definiert, rufen jedoch Funktonen, die das nicht sind. Und dann kracht 
es, weil an der Stelle im Flash dann ggf. was anderes steht... kriegt 
man aber hin - hab nun eine eigene flash_range funktion die zu 100% im 
Ram läuft.

von Tobias (Gast)


Lesenswert?

@sirsydom

Danke für deine Infos.
Ich versuche ebenfalls ein "einfaches" OTA mit dem pico zu realisieren.

Kannst du Bitte deine fertigen Funktionen "flash_range_" nochmals 
posten.
Am besten wäre natürlich alle beteiligten Funktionen (flash_range_, 
flash_range_erase_and_program & UpdateFlash).

Du würdest mir dadurch sehr helfen.

Danke
Tobias

von Ing-Dom (Firma: OpenKNX) (sirsydom)


Lesenswert?

das SDK wurde in diesen beiden Funktoinen überarbeitet und 
wahrscheinlich ist mein Workaround gar nicht nötig.
Hast du mit dem aktuellen SDK-Funktionen getestet?

von m.n. (Gast)


Lesenswert?

Gerd E. schrieb:
> Das einzige Problem an der Sache ist, daß der XIP-Block leider zu den
> Flash-Adressen kein Offset addieren kann. Die Speicheradressen im Flash
> sind also absolut und daher unterschiedlich, je nachdem ob ich gerade
> Image 1 oder Image 2 verwende.

Schon länger her, aber es ist ein allgemeines Problem und Du bist hier 
ja wohl noch aktiv.
Vielleicht wäre es eine Lösung, die Programme immer auf 0x20000100 zu 
linken und im RAM laufen zu lassen: passender Bootloader vorausgesetzt. 
Dann muß nur beim Erstellen der .uf2-Datei der Ablageort im FLASH 
angepaßt werden.

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.