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.
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.
Share Improve this question asked Mar 26 at 16:58 dinajsdinajs 1181 silver badge6 bronze badges 2 |1 Answer
Reset to default 1Having the sender do a sleep(0.05)
is fragile. Using any sleep
is fragile.
A simple/easy fix is to have the firmware loader/burner send an "ACK" (e.g. A
) to the sender after receiving a chunk and [successfully] burning it.
This should solve your timing/overrun issues.
A few issues ...
The above assumes that the USART transmission is reliable [in both directions].
It also assumes that the burn operation always completes successfully.
You've already implemented some of what I'd do [I've done several such loaders].
A few suggestions ...
- Put a checksum on each chunk sent.
- Each chunk should have the target address to burn for the chunk.
- Firmware should validate the checksum before attempting to burn the block. If the checksum fails, it should send a "NAK-xmit" (e.g.
N
). - If the sender gets a NAK, it should resend the chunk. After a few unsuccessful retries, it should report an error.
- After burning a block, it should be compared to the RAM buffer. If the match fails, send a "NAK-burn" failure (e.g.
B
). - Again, if both receive and burn are successful, send the ACK (e.g.
A
) - If either side doesn't receive an ack of some sort for something it has sent [after a timeout], it should resend the packet it sent.
Although rare on a short/direct cable, it is possible to lose byte sync and/or frame sync if a corrupted byte is sent or a byte is dropped.
So, there is an overly elaborate protocol for sending framed data over a UART. See section 3.1 of: PPP in HDLC-like Framing
This is usually intended for USARTs that support SDLC/HDLC directly.
But, there is an encoding that can resync to a frame using an ordinary UART. I developed this in the 1990s before finding out that it was already part of an existing protocol. I can't seem to find the [real] documentation for this encoding, so here's a simple example:
The [simplified] frame format (in binary):
FLAG | TYPE | DATA ... | CHKSUM | FLAG
TYPE | DATA ... | CHKSUM | FLAG
For a given FLAG
byte, (e.g. 0x7e), a data byte of 0x7e would be misinterpreted as the flag. So, there is an encoding. Treat this as pseudocode:
// send "escaped" messages
// these are arbitary:
#define FLAG 0x7E // packet framing byte
#define ESC 0xA5 // escape encoding for data
typedef unsigned char byte;
int sent_flag = 0; // 1=last sent byte was FLAG
int recv_flag = 0; // 1=last received byte was FLAG
// usart_send -- send raw byte to USART
void usart_send(byte chr);
// usart_recv -- receive raw byte from USART
void usart_recv(byte chr);
// data_send -- send "escaped" byte
void
data_send(byte *chr)
{
switch (chr) {
case FLAG:
usart_send(ESC);
usart_send(0x01);
break;
case ESC:
usart_send(ESC);
usart_send(0x02);
break;
default:
usart_send(chr);
break;
}
}
void
msg_send(const byte *buf,int msglen)
{
if (! sent_flag)
usart_send(FLAG);
unsigned int chksum = 0;
const byte *cur = buf;
const byte *ebuf = cur + msglen;
for (; cur < ebuf; ++cur) {
chksum += chr;
data_send(chr);
}
data_send(-chksum)
usart_send(FLAG);
sent_flag = 1;
}
int
msg_recv(byte *buf,int maxlen)
{
byte chr;
byte chr = 0;
// wait for message start (usually if error on prior packet or resyncing
// in the middle)
if (! recv_flag) {
while (1) {
chr = usart_recv();
if (chr == FLAG)
break;
}
}
unsigned int chksum = 0;
byte *cur = msg;
byte *ebuf = cur + maxlen;
while (cur < ebuf) {
chr = usart_recv();
// end-of-packet (or error)
if (chr == FLAG)
break;
// ordinary byte
if (chr != ESC) {
chksum += chr;
*cur++ = chr;
continue;
}
// get the "encoded" char
chr = usart_recv();
// protocol error
if ((chr != 0x01) && (chr != 0x02))
break;
// a data byte that is the same bit pattern as FLAG or ESC
byte data = 0;
switch (chr) {
case 0x01:
data = FLAG;
break;
case 0x02:
data = ESC;
break;
}
*cur++ = data;
chksum += data;
}
// remember that the last received byte was the FLAG
recv_flag = (chr == FLAG);
// validate the checksum
if (chksum != 0)
msglen = -1;
else
msglen = cur - buf;
return msglen;
}
Note that in the "real" protocol, the ending FLAG byte for a packet can also serve "double duty" as the starting FLAG for the next packet. In other words, between packets, there is only one FLAG between them and not two. That is illustrated above.
With such an encoding, you can eliminate much of your "preamble" and "hold" stuff.
UPDATE:
I couldn't find it at the time, but I've previously answered a question where I posted the "FLAG/ESC" protocol: executing commands via sockets with popen() Unlike the code above, it has real working code.
Also, I found a simple explanation for the PPP escape byte sequence: How do you properly catch the start-byte in parsing serial communication?
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. – chux Commented Mar 26 at 21:13