2024-09-01 02:35 AM
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.
Solved! Go to Solution.
2024-09-08 09:25 AM
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();
}
2024-09-01 10:54 PM
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.
2024-09-02 12:08 AM
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!
2024-09-02 10:46 AM
Okay so it's confirmed the jump into bootloader isn't happening, once SysMemBootJump() is initiated the mcu restarts.
2024-09-02 11:12 AM
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.
2024-09-03 03:19 AM
Thank you for the idea, i'll try this in a bit and report back on results.
2024-09-08 12:35 AM
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;
}
Not sure what i'm missing at this point and sadly can't find any working examples for this MCU series.
2024-09-08 09:25 AM
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();
}
2024-09-08 09:37 AM
Simple, Serial must have even parity config.
2024-09-08 11:14 AM - edited 2024-09-08 11:15 AM
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.