Jumping from application into bootloader [ STM32U585CIU6 ]
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.

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

The communication diagram can be simplified to the following:
| RECEIVER | SENDER |
| 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();
}
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;
}
After several days of troubleshooting this i'm still sadly unsuccessful.
Any help will be greatly appreciated.
