cancel
Showing results for 
Search instead for 
Did you mean: 

Jumping from application into bootloader [ STM32U585CIU6 ]

Zer0-bit
Associate II

Hi all, for the past few days i've been struggling to make this jump work as it refuses to communicate after the jump is initiated.
Below you can find the code i'm using to try and make the jump, perhaps the order how things are de-initialised and initialised is to blame but i tried to follow the order as per the diagram in the AN2606 doc.

Zer0bit_0-1725180570209.png

As a boot patter i've selected the highlighted pattern in the screenshot below:

Zer0bit_1-1725180975323.png


The communication diagram can be simplified to the following:

RECEIVERSENDER
STM32U585 ( USART2)ESP32-S3 (USART1) 

 

Here's the function that supposedly should perform the jump into bootloader form application (this code runs on the STM32U585CIU6):

 

UART_HandleTypeDef huart2; void jumpToBootloader() { void (*SysMemBootJump)(void); volatile uint32_t addr = 0x0BF90000; HAL_RCC_DeInit(); SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0; __disable_irq(); HAL_DeInit(); // Enable the USART2 clock __HAL_RCC_USART2_CLK_ENABLE(); // Enable the GPIOA clock (assuming TX/RX are on PA2/PA3) __HAL_RCC_GPIOA_CLK_ENABLE(); // Configure the GPIO pins for USART2 TX (PA2) and RX (PA3) GPIO_InitTypeDef GPIO_InitStruct = { 0 }; GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // Configure the UART Handle huart2.Instance = USART2; // USART2 instance huart2.Init.BaudRate = 115200; // Set baud rate to 115200 huart2.Init.WordLength = UART_WORDLENGTH_8B; // 8 data bits huart2.Init.StopBits = UART_STOPBITS_1; // 1 stop bit huart2.Init.Parity = UART_PARITY_NONE; // No parity huart2.Init.Mode = UART_MODE_TX_RX; // Enable TX and RX huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; // No hardware flow control huart2.Init.OverSampling = UART_OVERSAMPLING_16; // Oversampling by 16 // Initialize the UART if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } FLASH_OBProgramInitTypeDef OB_Data; OB_Data.OptionType = OPTIONBYTE_USER | OPTIONBYTE_BOOTADDR; OB_Data.USERType = OB_USER_TZEN | OB_USER_NBOOT0 | OB_USER_NSWBOOT0; OB_Data.USERConfig = OB_TZEN_DISABLE | OB_NBOOT0_SET | OB_NBOOT0_RESET; OB_Data.BootAddrConfig = OB_BOOTADDR_NS1; OB_Data.BootAddr = 0x017F200; uint32_t Nboot0_OB = READ_BIT(FLASH->OPTR, FLASH_OPTR_nBOOT0); uint32_t NSWBoot0_OB = READ_BIT(FLASH->OPTR, FLASH_OPTR_nSWBOOT0); uint32_t TZEN_OB = READ_BIT(FLASH->OPTR, FLASH_OPTR_TZEN); if (Nboot0_OB != OB_NBOOT0_SET || TZEN_OB != OB_TZEN_DISABLE || NSWBoot0_OB != OB_NBOOT0_RESET) { HAL_FLASH_Unlock(); HAL_FLASH_OB_Unlock(); HAL_FLASHEx_OBProgram(&OB_Data); HAL_FLASH_OB_Launch(); } SysMemBootJump = (void (*)(void)) (*((uint32_t*)(addr + 4))); __set_PRIMASK(1); __set_MSP(*(uint32_t*)addr); SysMemBootJump(); }
View more

 

 

Also here's the set of functions that should initiate the communication after the jump is made:

 

/* Application -> Bootloader -> Application */ #define USART_TIMEOUT 1000U #define ACK 0x79 #define NACK 0x1F bool waitForAck() { unsigned long startTime = millis(); while (millis() - startTime < USART_TIMEOUT) { if (Serial1.available()) { uint8_t receivedByte = Serial1.read(); if (receivedByte == ACK) { return true; } else if (receivedByte == NACK) { LOG_ERROR("Received NACK..."); return false; } } } LOG_ERROR("Timed out waiting for ACK..."); return false; } // USART1 on the ESP32-S3 is connected to USART2 on the STM32U585 // Send the 0x7F byte to trigger the STM32 bootloader // As per AN2606 - 84.2 Bootloader selection bool sendSyncByte() { Serial1.write(0x7F); // Send the synchronization byte Serial1.flush(); // Ensure the byte is transmitted // Wait for acknowledgment from the STM32 return waitForAck(); } bool sendCommand(uint8_t command, uint8_t complement) { Serial1.write(command); Serial1.write(complement); // Send complement of the command Serial1.flush(); return waitForAck(); } bool sendAddress(uint32_t address) { uint8_t addrBytes[4]; addrBytes[0] = (address >> 24) & 0xFF; addrBytes[1] = (address >> 16) & 0xFF; addrBytes[2] = (address >> 8) & 0xFF; addrBytes[3] = (address >> 0) & 0xFF; // Send address followed by XOR checksum uint8_t checksum = addrBytes[0] ^ addrBytes[1] ^ addrBytes[2] ^ addrBytes[3]; Serial1.write(addrBytes, 4); Serial1.write(checksum); Serial1.flush(); return waitForAck(); } bool eraseFlash() { // Full Chip Erase Command: 0x43 and complement 0xBC if (!sendCommand(0x43, 0xBC)) { return false; } // Send specific erase command (0xFF for full erase) Serial1.write(0xFF); Serial1.write(0x00); // Complement of 0xFF Serial1.flush(); return waitForAck(); } bool writeMemory(uint32_t address, const char* filepath) { File file = tempFS.open(filepath, FILE_READ); if (!file) { Serial.println("Failed to open binary file."); return false; } // Send Write Memory command: 0x31 and complement 0xCE if (!sendCommand(0x31, 0xCE)) { file.close(); return false; } // Send target address if (!sendAddress(address)) { file.close(); return false; } uint8_t buffer[256]; // Maximum of 256 bytes per write operation while (file.available()) { size_t bytesRead = file.read(buffer, sizeof(buffer)); // Length byte: number of bytes to write - 1 uint8_t length = bytesRead - 1; Serial1.write(length); // Write the data and calculate the checksum uint8_t checksum = length; for (size_t i = 0; i < bytesRead; i++) { Serial1.write(buffer[i]); checksum ^= buffer[i]; } // Send the checksum Serial1.write(checksum); Serial1.flush(); // Wait for ACK if (!waitForAck()) { file.close(); return false; } } file.close(); return true; } bool startApplication(uint32_t address) { // Go Command: 0x21 and complement 0xDE if (!sendCommand(0x21, 0xDE)) { return false; } // Send target address return sendAddress(address); } bool stmCommsSendFirmwareFile(const char* filepath) { // Trigger the STM32 to reboot into bootloader mode mcuComms.sendMessage(McuCommsMessageType::MCUC_FW_CORE_UPGRADE); LOG_INFO("Serial1 current BAUD rate: %d", Serial1.baudRate()); Serial1.updateBaudRate(115200); LOG_INFO("Updated BAUD rate to: %d", Serial1.baudRate()); while (!Serial1.available()) { delay(100); // aavoid busy wait LOG_DEBUG("Waiting for Serial1 to start after baud change.") } // Send sync byte to signal USARTx comms if (!sendSyncByte()) { LOG_ERROR("Bootloader did not acknowledge The USARTx Sync Byte."); Serial1.updateBaudRate(3000000); return false; } // Erase Flash Memory if (!eraseFlash()) { LOG_ERROR("Failed to erase flash."); return false; } // Write Binary to Flash (starting at user code start address: 0x8000000) uint32_t userStartAddress = 0x8000000; if (!writeMemory(userStartAddress, filepath)) { LOG_ERROR("Failed to write binary to flash."); return false; } // Start Application if (!startApplication(userStartAddress)) { LOG_ERROR("Failed to start application."); return false; } LOG_INFO("Firmware successfully flashed and started."); Serial1.updateBaudRate(3000000); while (!Serial1.available()) { delay(100); // aavoid busy wait LOG_DEBUG("Waiting for Serial1 to start after baud revert.") } return true; }
View more

 


After several days of troubleshooting this i'm still sadly unsuccessful.

Any help will be greatly appreciated.

1 ACCEPTED SOLUTION

Accepted Solutions

Another small update, i was able to jump form application into system bootloader and connect via UART using CubeProgrammer which means the STM32U585 jump works all well, i just need to figure out why it won't reply to the SENDER device.

Here's the revised code that jumps into bootloader:

void jumpToBootloader() { void (*SysMemBootJump)(void); volatile uint32_t addr = 0x0BF90000; /* Disable all interrupts */ __disable_irq(); /* Disable Systick timer */ SysTick->CTRL = 0; /* Set the clock to the default state */ HAL_RCC_DeInit(); /* Clear Interrupt Enable Register & Interrupt Pending Register */ for (uint8_t i = 0; i < sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0]); i++) { NVIC->ICER[i] = 0xFFFFFFFF; NVIC->ICPR[i] = 0xFFFFFFFF; } /* Re-enable all interrupts */ __enable_irq(); SysMemBootJump = (void (*)(void)) (*((uint32_t*)(addr + 4))); __set_MSP(*(uint32_t*)addr); SysMemBootJump(); }

 

View solution in original post

9 REPLIES 9
Techn
Senior III

AN2606 talk about jimp to built-in boot loader or system boot loader. Are you trying to jump to the system boot loader and trying to communicate over that to program the device or have your own boot loader?

the best way to check if the processor jumps to system boot loader is to use the STM32Cube programmer.

If you feel a post has answered your question, please click "Accept as Solution".

Hello and thank you for taking your time to reply :)
Good remark on the bootloader type as i indeed forgot to mention the jump is meant for the system bootloader with the goal to program the device, basically implementing OTA.
Also a good idea regarding using Cube Programmer to verify if the jump is actually happening, thank you!

Zer0-bit
Associate II

Okay so it's confirmed the jump into bootloader isn't happening, once SysMemBootJump() is initiated the mcu restarts.

In your code i see more missunderstanding... When you read and use Pattern xx no jumping is required, but reset MCU. Too in both method reset or jump no UART config help. System code do itself inits and work only on pins listed in AN2606, not any pins you setup...

As first step connect STLINK and setup manualy OB as you plan test. After validate OB setup simply reset or power off on and System loader is started.

Thank you for the idea, i'll try this in a bit and report back on results.

Okay so got some time on my hands and did some further troubleshooting of this, i can reliably connect to the bootloader with the following 2 methods:
1. Setting manually the option bits and BOOTADDR0 -> restart -> connect using Cube programmer via UART2.
2. Pulling PH3/BOOT0 high during boot and connect using Cube programmer via UART2.

 

But so far i have not been successful in jumping to bootloader from application and connecting via UART2, please see below the revised code.


STM32U585 side function, which is the RECEIVER mcu on UART2 :

void jumpToBootloader() { void (*SysMemBootJump)(void); volatile uint32_t addr = 0x0BF90000; HAL_RCC_DeInit(); HAL_DeInit(); SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0; __disable_irq(); SysMemBootJump = (void (*)(void)) (*((uint32_t*)(addr + 4))); __set_MSP(*(uint32_t*)addr); SysMemBootJump(); }


And here's the connecting routines used by the SENDER mcu on UART1:

#define ACK 0x79 #define NACK 0x1F bool waitForAck() { uint8_t receivedByte = Serial1.read(); if (receivedByte == ACK) { return true; } else if (receivedByte == NACK) { LOG_ERROR("Received NACK..."); return false; } LOG_ERROR("NO bootloader comms..."); return false; } // USART1 on the ESP32-S3 is connected to USART2 on the STM32U585 // Send the 0x7F byte to trigger the STM32 bootloader // As per AN2606 - 84.2 Bootloader selection bool sendSyncByte() { Serial1.write(0x7F); // Send the synchronization byte // Wait for acknowledgment from the STM32 return waitForAck(); } bool sendCommand(uint8_t command, uint8_t complement) { Serial1.write(command); Serial1.write(complement); // Send complement of the command return waitForAck(); } bool sendAddress(uint32_t address) { uint8_t addrBytes[4]; addrBytes[0] = (address >> 24) & 0xFF; addrBytes[1] = (address >> 16) & 0xFF; addrBytes[2] = (address >> 8) & 0xFF; addrBytes[3] = (address >> 0) & 0xFF; // Send address followed by XOR checksum uint8_t checksum = addrBytes[0] ^ addrBytes[1] ^ addrBytes[2] ^ addrBytes[3]; Serial1.write(addrBytes, 4); Serial1.write(checksum); return waitForAck(); } bool eraseFlash() { // Full Chip Erase Command: 0x43 and complement 0xBC if (!sendCommand(0x43, 0xBC)) { return false; } // Send specific erase command (0xFF for full erase) Serial1.write(0xFF); Serial1.write(0x00); // Complement of 0xFF return waitForAck(); } bool writeMemory(uint32_t address, const char* filepath) { File file = tempFS.open(filepath, FILE_READ); if (!file) { Serial.println("Failed to open binary file."); return false; } // Send Write Memory command: 0x31 and complement 0xCE if (!sendCommand(0x31, 0xCE)) { file.close(); return false; } // Send target address if (!sendAddress(address)) { file.close(); return false; } uint8_t buffer[256]; // Maximum of 256 bytes per write operation while (file.available()) { size_t bytesRead = file.read(buffer, sizeof(buffer)); // Length byte: number of bytes to write - 1 uint8_t length = bytesRead - 1; Serial1.write(length); // Write the data and calculate the checksum uint8_t checksum = length; for (size_t i = 0; i < bytesRead; i++) { Serial1.write(buffer[i]); checksum ^= buffer[i]; } // Send the checksum Serial1.write(checksum); // Wait for ACK if (!waitForAck()) { file.close(); return false; } } file.close(); return true; } bool startApplication(uint32_t address) { // Go Command: 0x21 and complement 0xDE if (!sendCommand(0x21, 0xDE)) { return false; } // Send target address return sendAddress(address); } bool stmCommsSendFirmwareFile(const char* filepath) { // Trigger the STM32 to jumpToBootloader mcuComms.sendMessage(McuCommsMessageType::MCUC_FW_CORE_UPGRADE); // Send sync byte to signal USARTx comms while (!sendSyncByte()) { static uint32_t timeout = millis(); LOG_ERROR("Bootloader did not acknowledge The USARTx Sync Byte."); delay(500); if (millis() - timeout > 5000) return false; } // Erase Flash Memory if (!eraseFlash()) { LOG_ERROR("Failed to erase flash."); return false; } // Write Binary to Flash (user code start address: 0x8000000) uint32_t userStartAddress = 0x8000000; if (!writeMemory(userStartAddress, filepath)) { LOG_ERROR("Failed to write binary to flash."); return false; } // Start Application if (!startApplication(userStartAddress)) { LOG_ERROR("Failed to start application."); return false; } LOG_INFO("Firmware successfully flashed and started."); return true; }
View more

 

Not sure what i'm missing at this point and sadly can't find any working examples for this MCU series.

 

Another small update, i was able to jump form application into system bootloader and connect via UART using CubeProgrammer which means the STM32U585 jump works all well, i just need to figure out why it won't reply to the SENDER device.

Here's the revised code that jumps into bootloader:

void jumpToBootloader() { void (*SysMemBootJump)(void); volatile uint32_t addr = 0x0BF90000; /* Disable all interrupts */ __disable_irq(); /* Disable Systick timer */ SysTick->CTRL = 0; /* Set the clock to the default state */ HAL_RCC_DeInit(); /* Clear Interrupt Enable Register & Interrupt Pending Register */ for (uint8_t i = 0; i < sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0]); i++) { NVIC->ICER[i] = 0xFFFFFFFF; NVIC->ICPR[i] = 0xFFFFFFFF; } /* Re-enable all interrupts */ __enable_irq(); SysMemBootJump = (void (*)(void)) (*((uint32_t*)(addr + 4))); __set_MSP(*(uint32_t*)addr); SysMemBootJump(); }

 

Simple, Serial must have even parity config.

I wish this was the answer :grinning_face_with_sweat:, i have Serial already configured in even parity mode by default on both ends, basically like below.

 

 

Serial1.begin(921600, SERIAL_8E1);

 

 

 Thanks for the idea though!

While i was writing this reply i ha another thought hitting me, CubeProgramer starts in even parity mode and flow control disabled, so nuff said disabling Serial1 flow control on the SENDING device side has worked and i can connect now.