I am trying to make software bootloader for my STM32F407 project and I ran into some issue. The whole idea is that I send data using UART protocol from my PC to STM32F407. Data that I am sending is firmware that should be written in STM32F407 flash memory(Software bootloader starts at 0x080000000 and ends at 0x08004000 and main application starts at 0x08004000). Issues occurs when I am sending firmware, STM receives first chunk and then it tries to write it in flash, but during flash writing process, rest of the firmware data is lost(it is never recieved by STM).
This is my gui.py file:
import time
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import serial
import serial.tools.list_ports
import os
class FirmwareUpdater:
def __init__(self, root):
self.root = root
self.root.title("STM32F407 Firmware Upgrader")
self.root.geometry("600x400")
self.binary_file = None
self.serial_port = None
self.create_gui()
def create_gui(self):
# File Selection Frame
file_frame = ttk.LabelFrame(self.root, text="Firmware File", padding="10")
file_frame.pack(fill="x", padx=10, pady=5)
self.file_path_var = tk.StringVar()
ttk.Label(file_frame, textvariable=self.file_path_var).pack(side="left", fill="x", expand=True)
ttk.Button(file_frame, text="Browse", command=self.browse_file).pack(side="right")
# Serial Port Frame
serial_frame = ttk.LabelFrame(self.root, text="Serial Port Settings", padding="10")
serial_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(serial_frame, text="Port:").grid(row=0, column=0, padx=5)
self.port_combo = ttk.Combobox(serial_frame, width=20)
self.port_combo.grid(row=0, column=1, padx=5)
# Progress Frame
progress_frame = ttk.LabelFrame(self.root, text="Upload Progress", padding="10")
progress_frame.pack(fill="x", padx=10, pady=5)
self.progress_bar = ttk.Progressbar(progress_frame, orient=tk.HORIZONTAL, length=300, mode='determinate')
self.progress_bar.pack(fill="x", pady=5)
self.status_var = tk.StringVar(value="Ready")
ttk.Label(progress_frame, textvariable=self.status_var).pack()
# Control Buttons
button_frame = ttk.Frame(self.root)
button_frame.pack(fill="x", padx=10, pady=5)
ttk.Button(button_frame, text="Start", command=self.start_upload).pack(side="right", padx=5)
# Initialize ports list
self.get_ports()
def get_ports(self):
ports = ["/dev/ttyUSB0","/dev/ttyUSB1","/dev/ttyUSB2"]
self.port_combo['values'] = ports
if ports:
self.port_combo.set(ports[0])
def browse_file(self):
filename = filedialog.askopenfilename(
filetypes=[("Binary files", "*.bin")]
)
if filename:
self.binary_file = filename
self.file_path_var.set(os.path.basename(filename))
def start_upload(self):
if not self.binary_file:
messagebox.showerror("Error", "Please select a firmware file first!")
return
if not self.port_combo.get():
messagebox.showerror("Error", "Please select a serial port!")
return
try:
print("Starting upload process...")
# Just open the port once
self.serial_port = serial.Serial(
port=self.port_combo.get(),
baudrate=115200,
)
# Read binary file
with open(self.binary_file, 'rb') as f:
firmware_data = f.read()
# Start the upload process
self.upload_firmware(firmware_data)
except Exception as e:
messagebox.showerror("Error", f"An error occurred: {str(e)}")
def upload_firmware(self, firmware_data):
try:
print(f"Total firmware size: {len(firmware_data)} bytes")
# Send hold signal and wait for ACK
self.send_hold_signal()
# Send metadata and wait for ACK
metadata = self.get_metadata(firmware_data)
self.serial_port.write(metadata)
# Send firmware in chunks
chunk_size = 64 # Smaller chunks
for i in range(0, len(firmware_data), chunk_size):
chunk = firmware_data[i:i + chunk_size]
self.serial_port.write(chunk)
self.serial_port.flush() # Ensure data is sent
time.sleep(0.05) # 50ms delay between chunks
# Send final checksum
checksum = self.calculate_checksum_firmware(firmware_data)
self.serial_port.write(checksum)
print("Upload complete")
except Exception as e:
print(f"Upload Error: {e}")
raise
def get_metadata(self, firmware_data):
preamble = bytes([0x53,0x54,0x4D,0x33,0x32,0x5F,0x42,0x4F,0x4F,0x54,0x4C,0x4F,0x41,0x44,0x45,0x52,0x5F,0x56,0x30,0x31])
firmware_size = len(firmware_data).to_bytes(4, 'little')
checksum = self.calculate_checksum_metadata(preamble + firmware_size)
return preamble + firmware_size + checksum
def calculate_checksum_metadata(self, data):
checksum = 0x00000000
for i in range(0, len(data), 4):
word = int.from_bytes(data[i:i+4], byteorder='little')
checksum ^= word
return checksum.to_bytes(4, 'little')
def calculate_checksum_firmware(self,data):
checksum = 0x00000000
for byte in data:
checksum ^= byte
return checksum.to_bytes(4, 'little')
def add_padding_bytes(self, data):
padding_bytes = (4 - (len(data) % 4)) % 4
return data + b'\x00' * padding_bytes
def send_hold_signal(self):
self.serial_port.write(b'\x55\x66\x77\x88')
if __name__ == "__main__":
root = tk.Tk()
app = FirmwareUpdater(root)
root.mainloop()
Here is my bootloader.c:
#include "bootloader.h"
#include "usart.h"
#include "debug_usart.h"
#include "flash.h"
#include "delay.h"
BootloaderMetadata metadata;
void sendChecksum(uint32_t checksum) {
uint8_t byte;
// Send the checksum byte by byte in little-endian (least significant byte first)
byte = checksum & 0xFF;
byte = (checksum >> 8) & 0xFF;
byte = (checksum >> 16) & 0xFF;
byte = (checksum >> 24) & 0xFF;
}
void sendExactValue(uint32_t value) {
sendUSART2((value >> 24) & 0xFF); // Most significant byte first
sendUSART2((value >> 16) & 0xFF);
sendUSART2((value >> 8) & 0xFF);
sendUSART2(value & 0xFF); // Least significant byte last
}
void jumpToApplication(void) {
// Verify reset vector contains valid address
uint32_t* reset_vector = (uint32_t*)(0x08004000 + 4);
if ((*reset_vector & 0x2FFE0000) != 0x20000000) {
return; // Invalid reset vector
}
// Function pointer to reset handler in application
void (*app_reset_handler)(void) = (void*)(*reset_vector);
// Set vector table offset to application start
SCB->VTOR = 0x08004000;
// Set main stack pointer
__set_MSP(*(uint32_t*)0x08004000);
// Jump to application
app_reset_handler();
}
void initBootloader(void) {
// Send bootloader active signal
sendUSART1(BOOTLOADER_ACTIVE1);
sendUSART1(BOOTLOADER_ACTIVE2);
sendUSART1(BOOTLOADER_ACTIVE3);
sendUSART1(BOOTLOADER_ACTIVE4);
sendUSART2('F');
unlockFlash();
eraseFlash();
sendUSART2('G');
// Wait for hold command
uint8_t cmd1 = receiveUSART1();
uint8_t cmd2 = receiveUSART1();
uint8_t cmd3 = receiveUSART1();
uint8_t cmd4 = receiveUSART1();
if(cmd1 == HOLD_COMMAND1 && cmd2 == HOLD_COMMAND2 && cmd3 == HOLD_COMMAND3 && cmd4 == HOLD_COMMAND4) {
// Received correct hold command
sendUSART1(ACK);
// Receiving metadata and checking for its validity
if(receiveMetadata()){ // Metadata correctly received, starting with firmware send
if(receiveFirmware(metadata.firmwareSize)){ // If firmware is correctly received, jump to application execution
jumpToApplication();
}
}else{
jumpToApplication();
};
//while(1); // Temporary - just to show we're in bootloader mode
} else {
// No valid hold command, jump to application
jumpToApplication();
}
}
uint8_t receiveMetadata(void) {
uint32_t checksum = 0x00000000;
uint32_t i;
uint32_t j;
// Receive and checksum preamble
for (i = 0; i < 5; ++i) {
uint32_t preambleWord = 0x00000000;
for(j = 0; j < 4; ++j){
uint8_t byte = receiveUSART1();
preambleWord |= (uint32_t)(byte << (j * 8)); // Little-endian
}
metadata.preamble[i] = preambleWord;
checksum ^= preambleWord;
}
// Receive and add firmware size to checksum (little-endian)
metadata.firmwareSize = 0x00000000;
for (i = 0; i < 4; ++i) {
uint8_t byte = receiveUSART1();
metadata.firmwareSize |= (uint32_t)(byte << (i * 8));
}
checksum ^= metadata.firmwareSize;
// Receive metadata checksum (little-endian)
metadata.metadataChecksum = 0x00000000;
for (i = 0; i < 4; ++i) {
uint8_t byte = receiveUSART1();
metadata.metadataChecksum |= (uint32_t)(byte << (i * 8));
}
//sendChecksum(checksum);
//sendChecksum(metadata.metadataChecksum); // Debug
if (checksum == metadata.metadataChecksum) {
sendUSART1(ACK);
return 1;
} else {
sendUSART1(NACK);
return 0;
}
return 0;
}
uint8_t receiveFirmware(uint32_t firmwareSize) {
uint32_t currentAddress = 0x08004000;
uint32_t bytesReceived = 0;
uint32_t checksum = 0x00000000;
uint8_t buffer[64]; // Smaller buffer to match Python chunks
uint32_t i;
while(bytesReceived < firmwareSize) {
uint32_t bytesToRead = (firmwareSize - bytesReceived >= 64) ?
64 : (firmwareSize - bytesReceived);
// Receive chunk
for(i = 0; i < bytesToRead; i++) {
buffer[i] = receiveUSART1();
sendUSART2(buffer[i]);
checksum ^= buffer[i];
}
writeFlash(currentAddress, buffer, bytesToRead);
currentAddress += bytesToRead;
bytesReceived += bytesToRead;
}
// Get final checksum
uint32_t receivedChecksum = 0;
for(i = 0; i < 4; i++) {
uint8_t byte = receiveUSART1();
receivedChecksum |= (uint32_t)(byte << (i * 8));
}
if(checksum == receivedChecksum) {
sendUSART1(ACK);
return 1;
}
sendUSART1(NACK);
return 0;
}
Here is my flash.c file:
#include "flash.h"
#include "led.h"
void unlockFlash(void) {
// Enable flash operation interrupts first
FLASH->CR |= (FLASH_CR_EOPIE) | (1 << 25); // There is no CR_ERRIE for some reason
// Enable Flash interrupt in NVIC
NVIC_EnableIRQ(FLASH_IRQn);
// Check if flash is already unlocked
if(FLASH->CR & FLASH_CR_LOCK) {
// Write unlock sequence
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
}
void lockFlash(void){
FLASH->CR |= FLASH_CR_LOCK;
}
void eraseFlash(void) {
// 1. Check that no Flash memory operation is ongoing
while(FLASH->SR & FLASH_SR_BSY);
// 2. Set the SER bit and select sector 1
FLASH->CR |= FLASH_CR_SER; // Set Sector Erase bit
FLASH->CR &= ~(0xF << 3); // Clear sector number bits
FLASH->CR |= (1 << 3); // Set sector number 1 (SNB)
// 3. Set the STRT bit
FLASH->CR |= FLASH_CR_STRT;
// 4. Wait for BSY bit to be cleared
while(FLASH->SR & FLASH_SR_BSY);
// Clear the SER bit
FLASH->CR &= ~FLASH_CR_SER;
}
void writeFlash(uint32_t address, uint8_t* data, uint32_t length) {
//sendUSART2('1');
uint32_t* word_data = (uint32_t*)data;
uint32_t words = length / 4;
uint32_t i;
//sendUSART2('2');
// Check if address is in valid range
if(address < 0x08004000 || address >= 0x08008000) {
sendUSART2('E'); // Error: address out of range
return;
}
// Unlock OPTCR first
FLASH->OPTKEYR = 0x08192A3B;
FLASH->OPTKEYR = 0x4C5D6E7F;
//sendUSART2('3');
// Clear any pending errors
FLASH->SR |= (FLASH_SR_PGAERR | FLASH_SR_PGPERR | FLASH_SR_PGSERR | FLASH_SR_WRPERR);
// Set parallelism to 32-bit
FLASH->CR &= ~FLASH_CR_PSIZE_1;
FLASH->CR |= FLASH_CR_PSIZE_1;
//sendUSART2('4');
for(i = 0; i < words; i++) {
//sendUSART2('5');
// Check for errors before each write
if(FLASH->SR & (FLASH_SR_PGAERR | FLASH_SR_PGPERR | FLASH_SR_PGSERR | FLASH_SR_WRPERR)) {
sendUSART2('X'); // Error detected
sendExactValue(FLASH->SR); // Send error status
return;
}
while(FLASH->SR & FLASH_SR_BSY);
FLASH->CR |= FLASH_CR_PG;
*((volatile uint32_t*)(address + (i * 4))) = word_data[i];
// Wait with timeout
uint32_t timeout = 100000;
while(FLASH->SR & FLASH_SR_BSY && timeout > 0) {
timeout--;
}
if(timeout == 0) {
sendUSART2('T'); // Timeout error
return;
}
FLASH->CR &= ~FLASH_CR_PG;
//sendUSART2('6');
}
// sendUSART2('7');
}
void FLASH_IRQHandler(void) {
// End of operation
if(FLASH->SR & FLASH_SR_EOP) {
FLASH->SR |= FLASH_SR_EOP; // Clear flag
FLASH->CR &= ~FLASH_CR_PG; // Clear PG bit
turnOnSuccessLed();// Success LED
}
// Error handling
if(FLASH->SR & (FLASH_SR_PGAERR | FLASH_SR_PGPERR | FLASH_SR_PGSERR)) {
FLASH->SR |= (FLASH_SR_PGAERR | FLASH_SR_PGPERR | FLASH_SR_PGSERR);
FLASH->CR &= ~FLASH_CR_PG;
turnOnErrorLed();// Error LED
}
}
Please ignore debug prints and comment lines.
while(FLASH->SR & FLASH_SR_BSY && timeout > 0) { timeout--; } if(timeout == 0) {yet lacks similar iteration limits withwhile(FLASH->SR & FLASH_SR_BSY);Consider adding timeout checks in more places.