Skip to main content
Graduate
September 1, 2024
Solved

Jumping from application into bootloader [ STM32U585CIU6 ]

  • September 1, 2024
  • 2 replies
  • 3387 views

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

    This topic has been closed for replies.
    Best answer by Zer0-bit

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

     

    2 replies

    Graduate II
    September 2, 2024

    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.

    Zer0-bitAuthor
    Graduate
    September 2, 2024

    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-bitAuthor
    Graduate
    September 2, 2024

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

    Graduate II
    September 2, 2024

    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.

    Zer0-bitAuthor
    Graduate
    September 3, 2024

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