STM32 jump to bootloader
STM32 - Jump To Bootloader
In manchen Anwendungen möchte man keinen Hardwarebutton für ein Firmware-Update haben. Muss man trotzdem ab und zu ein Update einspielen, kann man den Bootloader über eine versteckte UI-Funktion aufrufen oder einen Befehl über die serielle Schnittstelle senden um den Bootloader anzuspringen. Die Möglichkeiten sind sehr vielfältig.
Die hier beschriebene Möglichkeit schreibt eine Magic-Konstante in's RAM. Dieses überlebt einen System-Reset und der veränderte Startup-Code prüft auf diese Konstante. Ist diese vorhanden, wird zum Bootloader gesprungen.
Der Code zum Prüfen und Verzweigen wird kurz vor dem Aufruf der main()-Funktion eingefügt. Kurz dahinter kommt die Subroutine 'Reboot_Loader'. Diese setzt einen Pointer auf den Bootloader und hüpft dann dort hin.
startup.S
/* Call the clock system intitialization function.*/
bl SystemInit
/* check for a magic constant in RAM, if present
* brach to bootloader */
LDR R0, =0x2000FFF0
LDR R1, =0xDEADBEEF
LDR R2, [R0, #0]
STR R0, [R0, #0] /* Invalidate*/
CMP R2, R1
BEQ Reboot_Loader
/* Call the applications entry point.*/
bl main
bx lr
.size Reset_Handler, .-Reset_Handler
Reboot_Loader:
LDR R0, =0x1FFFF000
LDR SP,[R0, #0]
LDR R0,[R0, #4]
BX R0
app.c
*((unsigned long *)0x2000FFF0) = 0xDEADBEEF; // 64KB STM32F103, also works with 512k STM32F103VCT6
NVIC_GernerateSystemReset();
app.c ohne fwlib/HAL
#define AIRCR_VECTKEY_MASK ((uint32_t)0x05FA0000)
*((unsigned long *)0x2000FFF0) = 0xDEADBEEF; // 64KB STM32F103, also works with 512k STM32F103VCT6
SCB->AIRCR = AIRCR_VECTKEY_MASK | (u32)0x04;
Beispiel
Der folgende Code-Ausschnitt zeigt die ISR des USART1 eines STM32F103. Der Code sammelt alle empfangenen Bytes und zählt mit. Sobald 32bytes oder ein '\r' empfangen wurden, wird der String verglichen. Ist dieser "GBL\r", springt er in den Bootloader.
Auf dem Programmier-Computer läuft ein kurzes Python-Script, welches "GBL\r" sendet und danach wird direkt das Flash-Update-Tool wie stm32flash aufgerufen. So entfällt jegliches Bedienen am Gerät.
Das Python-Script heisst gbl.py, danach der Aufruf zum flashen:
$ ./gbl.py && stm32flash -b 921600 -w output/main.bin -g 0x0 /dev/ttyUSB0
Das Python-Script benötigt das Paket python-serial.
#!/usr/bin/python
import serial
ser = serial.Serial('/dev/ttyUSB0', 115200)
print ser.name
ser.write('GBL\r')
ser.close
Nun der Controller-Teil. Zuerst die ISR, danach die Initialisierung sowie einfache putc/puts Funktionen.
void USART1_IRQHandler(void) {
char c;
static int count = 0;
static char rcv[33];
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
c = (char)USART_ReceiveData(USART1);
rcv[count] = c;
count++;
if((count == 32) || (c == '\r')) {
rcv[count] = '\0';
//usart1_puts(rcv);
if(!(strcmp(rcv, "GBL\r"))) { // go to bootloader
/* write constant to ram */
*((unsigned long *)0x2000FFF0) = 0xDEADBEEF;
/* do a system reset */
SCB->AIRCR = ((uint32_t)0x05FA0000) | (u32)0x04;
//} else if(!(strcmp(rcv, "HELP\r"))) {
// print_help();
} else {
usart1_puts("?! < no cmd >\n");
}
count = 0;
memset(rcv, 0, 33);
}
//USART_SendData(USART1, c);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
void usart1_init(void) {
USART_InitTypeDef usart_i;
GPIO_InitTypeDef gpio_i;
GPIO_PinRemapConfig(GPIO_Remap_USART1,DISABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
/* TX */
gpio_i.GPIO_Pin = GPIO_Pin_9;
gpio_i.GPIO_Speed = GPIO_Speed_50MHz;
gpio_i.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &gpio_i);
/* RX */
gpio_i.GPIO_Pin = GPIO_Pin_10;
gpio_i.GPIO_Speed = GPIO_Speed_50MHz;
gpio_i.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio_i);
/* 115200 8n1 */
usart_i.USART_BaudRate = 115200;
usart_i.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart_i.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
usart_i.USART_Parity = USART_Parity_No;
usart_i.USART_StopBits = USART_StopBits_1;
usart_i.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &usart_i);
/* enable RXNE interrupt */
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_EnableIRQ(USART1_IRQn);
USART_Cmd(USART1,ENABLE);
}
void usart1_putc(char c){
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, c);
return;
}
void usart1_puts(char *str){
while(*str != 0) {
usart1_putc(*(str++));
}
return;
}
Nachteile / Beachten
Der Bootloader erwartet einen gewissen Status der Peripherie, was nicht von ST dokumentiert wurde. In meiner jetzigen Applikation habe ich mit dieser Methoder bisher keine Probleme. Andere User im ST-Forum berichten von Problemen beim Anspringen des Bootloaders. Eventuell muss dann noch von Hand Peripherie deinitialisiert werden um die korrekte Funktion zu gewährleisten. Siehe ST-Forum-Lunk unter "Quellen".
Quellen
Autor
clive1 - ST-Forum (code)
Nils S. - µC.net (Artikel)