Forum: Mikrocontroller und Digitale Elektronik In den Bootloader des STM32F0 springen vom User Code


von Frank Z. (hardwarecop)


Lesenswert?

Guten Tag,

für meine Anwendung mit einem STM32F042K6 möchte ich ermöglichen, dass 
man über den eingebauten USB-Port in den Bootloader springen kann, um 
mit z.B. STM32CubeProgrammer die Firmware zu aktualisieren.

- Verwendet wird der USB-Port, wenn das Gerät nicht programmiert werden 
soll, als Virtual COM Port (VCP) mit dem USB Device (FS) Treiber
- Option Bits: nBOOT0 = 1, nBOOT1 = 0, BOOT_SEL = 0
- Der Boot0-Pin wird als GPIO, konfiguriert als Input mit Pullup 
verwendet
- Ich verwende STM32CubeMX, die HAL und SystemWorkbench als 
Programmierumgebung.
- Weitere verwendete Peripherie, falls relevant: TIM2, TIM16, I2C1, SPI1
- Clock: 48 MHz (HSI48), USB: 48 MHz

Mein Ansatz besteht bisher weitgehend daraus, die Schritte der Anleitung 
hier: https://www.youtube.com/watch?v=cvKC-4tCRgw zu replizieren, und 
sie natürlich an meiner Anwendung anzupassen, bisher allerdings 
vergeblich. Hier der adaptierte Code:
1
void (*sys_mem_boot_jmp)(void);
2
void activate_bootloader(uint32_t bootloader_status) {
3
  if(bootloader_status == 1) {
4
    HAL_RCC_DeInit();
5
6
    SysTick->CTRL = 0;
7
    SysTick->LOAD = 0;
8
    SysTick->VAL = 0;
9
10
    __disable_irq();
11
    sys_mem_boot_jmp = (void (*)(void)) (*((uint32_t *) ((0x1FFFC400 + 4))));
12
13
    // __set_PRIMASK(1); // Disable Interrupts
14
    __set_MSP(*(__IO uint32_t*)0x1FFFC400);
15
    sys_mem_boot_jmp();
16
17
    while(1);
18
  }
19
}

Erläuterung: Nach Application Note AN2606, S. 43 von STM ist die Adresse 
0x1FFFC400 die System Memory Start Address, demnach soll der Sprung bei 
der Adresse +4 liegen; da in 0x1FFFC400 die Default-Stack Adresse liegt, 
wird sie bei __set_MSP einmal geladen.

Die Funktion
1
void activate_bootloader(uint32_t)
 wird (erfolgreich) getriggert, wenn im Serial-Protokoll des Virtual COM 
Ports (User Code) das Kommando zur Anforderung eines Firmware-Updates 
erkannt wird.
Die Funktion wird erfolgreich ausgeführt. Beim Debuggen sieht man, dass 
es sich weder in eine HardFault aufhängt, noch in die InfiniteLoop geht 
bei while(1). Dennoch wird im CubeProgrammer weder ein Gerät im 
DFU-Modus erkannt (No Device Found), noch lässt sich eine Verbindung 
darin mittels UART herstellen (Timeout).

Mache ich irgendeinen offensichtlichen Fehler? Was mache ich falsch? 
Falls etwas fehlt, werde ich es schnellstmöglich ergänzen.

Danke im Voraus und bleibt gesund.
Liebe Grüße.

von Andreas M. (amesser)


Lesenswert?

Ich denke du musst vorher einen USB Disconnect emulieren. Ein PC scannt 
ein USB Gerät nur einmalig beim Erkennen des Ansteckens.

Ich habs nicht im Kopf, aber eine der D+ oder D- Leitung muss dazu auf 
einen Pegel gezogen werden. Danach ein paar Sekunden warten, das der PC 
es merkt und dann erst in den Bootloader hüpfen. Eventuell musst du 
vorher auch noch mehr Tegister zurücksetzen. Hat der F0 MSP und PSP?

von pegel (Gast)


Lesenswert?

Ich denke, in diesem Projekt ist das gut umgesetzt:

http://tomeko.net/miniscope_v2e/

von Frank Z. (hardwarecop)


Lesenswert?

Super, danke Dir schonmal für die Antwort!

Ob Kabel an- und ausstecken den gleichen Effekt bewirkt? Das Gerät wird 
über eine separate 5V Quelle versorgt; gleichzeitig läuft der Debugger 
über ST-Link. Ansonsten versuche ich mal die D+ und D- Leitungen als 
GPIO Low zu ziehen mit nem darauffolgenden HAL_Delay...

Ja, der µC hat beide. Ich kenne mich aber wirklich nicht gut damit aus 
^^. Habe unmittelbar vor dem Sprung einen Breakpoint gesetzt und die 
Werte rauskopiert:

General Registers  General Purpose and FPU Register Group
  r0  0
        ... (nicht so interessant)
  r12  0
  sp  0x200016e8
  lr  134232845
  pc  0x80065cc <activate_bootloader+20>
  xPSR  1090519087
  msp  0x200016e8
  psp  0xfffffffc
  primask  0
  basepri  0
  faultmask  0
  control  0

von Frank Z. (hardwarecop)


Lesenswert?

pegel schrieb:
> Ich denke, in diesem Projekt ist das gut umgesetzt:
>
> http://tomeko.net/miniscope_v2e/

Danke! Leider ist mein Boot0-Pin bereits als GPIO konfiguriert, demnach 
entfällt die Option eines einfachen Switches wie in diesem Projekt 
umgesetzt.

von Frank Z. (hardwarecop)


Lesenswert?

Update: Habs jetzt raus, mit leicht verändertem Ansatz.
Anstatt mit voller Peripherie in den Bootloader zu gehen, setze ich, wie 
in diesem (http://blog.fahhem.com/2017/12/jump-to-emb-bootloader/) 
Artikel beschrieben eine Magic Number Flag, lese diesen direkt nach dem 
Start aus und springe ggf. in den Bootloader.

Hier der verwendete Code für die Nachwelt, falls es je jemand gebrauchen 
kann:

Die activate_bootloader-Funktion in der Main-Funktion wird aufgerufen, 
um den Flag zu setzen. Dieser kriegt irgendeine Position im RAM.
1
volatile uint32_t _bootloader_flag = 0;
2
3
void activate_bootloader(uint32_t bootloader_status) {
4
  if(bootloader_status == 1) {
5
    _bootloader_flag = 0x00C0FFEE; // Magic number flag. See you latte.
6
    NVIC_SystemReset();
7
8
    while(1);
9
  }
10
}

Im startup_stm32f042x6.s wird beim Reset_Handler die Prüfung unterzogen, 
ob der Bootloader-Flag gesetzt ist:
1
/*Check for bootloader*/
2
ldr r0, =_bootloader_flag // Load bootloader flag address
3
ldr r1, =0x00C0FFEE // Load magic number
4
ldr r2, [r0, #0] // Dereference flag
5
str r0, [r0, #0] // Clear
6
cmp r2, r1 // Check flag
7
beq Reboot_Loader // Flag detected? Go to loader

Und letztendlich der Reboot_Loader mit passender Bootloader-Adresse 
gemäß AN2606:
1
Reboot_Loader:
2
  ldr r0, =0x1FFFC400 // Load bootloader address
3
  ldr r1, [r0, #0] // Load default stack pointer
4
  mov sp, r1 // Set stack pointer to default
5
  ldr r0, [r0, #4] // Load bootloader +4
6
  bx r0 // Go to bootloader +4

von Andreas M. (amesser)


Lesenswert?

Schön das Du eine Lösung hast. SP/MSP könnte in Ordnung sein. Eventuell 
liegt es auch daran
(Aus dem ARM Manual)

> Note
> When changing the stack pointer, software must use an ISB instruction
> immediately after the MSR instruction. This ensures that instructions
> after the ISB execute using the new stack pointer. See ISB.

D.h. nach dem __set_MSP muss noch ein "ISB" rein. Achja, und was evtl. 
auch noch fehlt: Ich denke alle Interrupte müssen auch in der VIC 
deaktiviert sein, letztlich müsste man die komplette Peripherie halbwegs 
aufräumen bevor man in den Loader hüpft. Von daher ist das mit dem Reset 
vermutlich die einfachste Lösung. Theoretisch könntest Du Dein 
_bootloader_flag auch direkt nach dem Start setzen (Nach PowerOn Reset 
ist das immer 0) und noch einen Watchdog aufsetzen, dann würde er bei 
einem Firmware-Absturz automatisch im Bootloader landen.

Achja, Kabel Ziehen/Stecken geht natürlich auch wenn den Pin nicht 
schalten kann.

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.