STM32 jump to bootloader

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

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)