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();
}

 

 

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 >>  & 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.

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 >>  & 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;
}

 

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 , 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.