Reputation: 3933
The STM32 App Note 2606 discusses this, but there is no simple code example.
Upvotes: 2
Views: 33978
Reputation: 23
Since this topic is rather old and it did not work for me (and it comes up when searching for this topic) I want to present my solution (for a STM32F412 in particular, but should work universally).
The memory address of the boot loader of you MCU
The starting address of your RAM
Some magic number of your choice (my favourites beeing 0xFEEDFEED, 0xDECAFBAD, 0xDEADBEEF, ...)
and 2) you will find in the AN2606 on the ST page. For my controller the relevant part looks like this:
So the address of the boot loader is 0x1FFF000 and the starting address of the RAM is 0x20000000.
Now add a function to your source code which sets the magic number you chose in the RAM and resets the controller. It could look something like this:
void reboot_into_dfu()
{
*((uint32_t *)0x20000000) = 0xDECAFBAD;
NVIC_SystemReset();
}
Replace 0x2000000 with the RAM starting address of you controller and 0xDECAFBAD with the magic number of your chioce.
Next step is to modify the startup procedure. For this you have to open the file startup_stm32xxx.s (xxx being the name of your controller), located in Project > Core > Startup (as of CubeIDE v1.14, 2024). There you will find some Assembly code which should look something like this:
[...]
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit
CopyDataInit:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
LoopCopyDataInit:
adds r4, r0, r3
cmp r4, r1
bcc CopyDataInit
[...]
Now we will add a check if the magic number is set right after the label Reset_Handler: and jump into the DFU bootlader, if so. The code then looks like this:
[...]
Reset_Handler:
/* begin check for flag */
ldr r0, =0x20000000
ldr r1, =0xDECAFBAD
ldr r2, [r0, #0]
str r0, [r0, #0]
cmp r2, r1
beq Reboot_Loader
/* end check for flag */
/* Copy the data segment initializers from flash to SRAM */
ldr sp, =_estack /* set stack pointer */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit
/* begin jump to DFU */
Reboot_Loader:
ldr r0, =0x1FFF0000
ldr sp, [r0, #0]
ldr r0, [r0, #4]
bx r0
/* end jump to DFU */
CopyDataInit:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
LoopCopyDataInit:
adds r4, r0, r3
cmp r4, r1
bcc CopyDataInit
[...]
Again replace 0x2000000 with the RAM starting address of you controller and 0xDECAFBAD with the magic number of your chioce.
So now once the MCU starts up, it checks for the magic number at the RAM starting address. If so it clears the magic number and enters the DFU mode. Otherwise it boots up the system normally.
And if you want to reset your MCU by software after applying the update tage a look at pydfu from Micropython.
(I also posted this at https://community.st.com)
Upvotes: 0
Reputation: 63
In my project, I'm essentially doing the same as Brad, but without modifying the SystemInit() function.
The CubeMX HAL defines as
void __attribute__((weak)) __initialize_hardware_early(void);
which does - in my case - nothing but calling SystemInit();
So you can just overwrite this function:
#include <stdint.h>
#include "stm32f0xx_hal.h"
#define SYSMEM_RESET_VECTOR 0x1fffC804
#define RESET_TO_BOOTLOADER_MAGIC_CODE 0xDEADBEEF
#define BOOTLOADER_STACK_POINTER 0x20002250
uint32_t dfu_reset_to_bootloader_magic;
void __initialize_hardware_early(void)
{
if (dfu_reset_to_bootloader_magic == RESET_TO_BOOTLOADER_MAGIC_CODE) {
void (*bootloader)(void) = (void (*)(void)) (*((uint32_t *) SYSMEM_RESET_VECTOR));
dfu_reset_to_bootloader_magic = 0;
__set_MSP(BOOTLOADER_STACK_POINTER);
bootloader();
while (42);
} else {
SystemInit();
}
}
void dfu_run_bootloader()
{
dfu_reset_to_bootloader_magic = RESET_TO_BOOTLOADER_MAGIC_CODE;
NVIC_SystemReset();
}
Upvotes: 3
Reputation: 3933
This answer has been tested on the STM32F072 Nucleo board using IAR EWARM. This answer uses the "STM32 Standard Peripheral Library" and nothing else.
Note that the best/easiest way to verify you are successfully in bootloader mode (DFU mode) is to hookup a USB-2-UART converter (get one here from Sparkfun: http://sfe.io/p9873 for $15) on lines PA_9 (USART1_TX) and PA_10 (USART1_RX) (don't forget to connect ground as well). I was not able to use the Nucleo USART2 default connection (/dev/ttyACM0), hence the external USB-2-USART connection. Then create a simple C program to write 0x7F on the USART connection. If you are in DFU mode, it will reply with one byte: 0x79. I use Ubuntu, so my test program compiles and runs on Linux.
Also, the easiest way to test bootloader mode (aka DFU mode) is to jumper the BOOT0 line to +3.3V. These are right next to each other on the Nucleo.
Add to main.c main() routine:
// Our STM32 F072 has:
// 16k SRAM in address 0x2000 0000 - 0x2000 3FFF
*((unsigned long *)0x20003FF0) = 0xDEADBEEF;
// Reset the processor
NVIC_SystemReset();
Add some code to Libraries/sysconfig/system_stm32f0xx.c at the beginning of the SystemInit() function:
// Define our function pointer
void (*SysMemBootJump)(void);
void SystemInit (void)
{
// Check if we should go into bootloader mode.
//
// Set the main stack pointer __set_MSP() to its default value. The default
// value of the main stack pointer is found by looking at the default value
// in the System Memory start address. Do this in IAR View -> Memory. I
// tried this and it showed address: 0x200014A8 which I then tried here.
// The IAR compiler complained that it was out of range. After some
// research, I found the following from "The STM32 Cortex-M0 Programming
// Manual":
// Main Stack Pointer (MSP)(reset value). On reset, the processor
// loads the MSP with the value from address 0x00000000.
//
// So I then looked at the default value at address 0x0 and it was 0x20002250
//
// Note that 0x1fffC800 is "System Memory" start address for STM32 F0xx
//
if ( *((unsigned long *)0x20003FF0) == 0xDEADBEEF ) {
*((unsigned long *)0x20003FF0) = 0xCAFEFEED; // Reset our trigger
__set_MSP(0x20002250);
// 0x1fffC800 is "System Memory" start address for STM32 F0xx
SysMemBootJump = (void (*)(void)) (*((uint32_t *) 0x1fffC804)); // Point the PC to the System Memory reset vector (+4)
SysMemBootJump();
while (1);
}
... // The rest of the vanilla SystemInit() function
Create a simple utility to see if you are in bootloader mode (aka DFU mode). This compiles and runs on Linux. Make sure you get your serial port right. It will likely be /dev/ttyUSB0 as shown below.
//
// A bare-bones utility: Test if the STM32 is in DFU mode
// (aka bootloader mode, aka firmware update mode).
//
// If it is in DFU mode, you can send it 0x7F over a UART port and it
// will send 0x79 back.
//
// For details, see the STM32 DFU USART spec.
//
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h> // errno
#define DEFAULT_SERDEVICE "/dev/ttyUSB0"
//#define DEFAULT_SERDEVICE "/dev/ttyACM0"
int main(int argc, char **argv)
{
int fd, cooked_baud = B9600;
char *sername = DEFAULT_SERDEVICE;
struct termios oldsertio, newsertio;
unsigned char mydata[2] = {0};
mydata[0] = 0x7F;
mydata[1] = 0;
/* Not a controlling tty: CTRL-C shouldn't kill us. */
fd = open(sername, O_RDWR | O_NOCTTY);
if ( fd < 0 )
{
perror(sername);
exit(-1);
}
tcgetattr(fd, &oldsertio); /* save current modem settings */
/*
* 8 data, EVEN PARITY, 1 stop bit. Ignore modem control lines. Enable
* receive. Set appropriate baud rate. NO HARDWARE FLOW CONTROL!
*/
newsertio.c_cflag = cooked_baud | CS8 | CLOCAL | CREAD | PARENB;
/* Raw input. Ignore errors and breaks. */
newsertio.c_iflag = IGNBRK | IGNPAR;
/* Raw output. */
newsertio.c_oflag = OPOST;
/* No echo and no signals. */
newsertio.c_lflag = 0;
/* blocking read until 1 char arrives */
newsertio.c_cc[VMIN]=1;
newsertio.c_cc[VTIME]=0;
/* now clean the modem line and activate the settings for modem */
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newsertio);
// Here is where the magic happens
write(fd,&mydata[0],1);
int red = read(fd,&mydata[1],1);
if (red < 0) {
fprintf(stderr, "Error: read() failed, errno [%d], strerrer [%s]\n",
errno, strerror(errno));
}
tcsetattr(fd,TCSANOW,&oldsertio);
close(fd);
printf("Read [%d] bytes: [0x%x]\n", red, mydata[1]);
return 0;
}
Upvotes: 8