最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c - STM32F407 Software Bootloader - Problem with receiving UART data - Stack Overflow

programmeradmin6浏览0评论

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
  • Is 50ms delay between sending chunks giving the STM32 enough time to write each chunk? Try increasing it. – pmacfarlane Commented Mar 26 at 18:36
  • @dinajs, It is good that code has while(FLASH->SR & FLASH_SR_BSY && timeout > 0) { timeout--; } if(timeout == 0) { yet lacks similar iteration limits with while(FLASH->SR & FLASH_SR_BSY); Consider adding timeout checks in more places. – chux Commented Mar 26 at 21:13
Add a comment  | 

1 Answer 1

Reset to default 1

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

  1. The above assumes that the USART transmission is reliable [in both directions].

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

  1. Put a checksum on each chunk sent.
  2. Each chunk should have the target address to burn for the chunk.
  3. Firmware should validate the checksum before attempting to burn the block. If the checksum fails, it should send a "NAK-xmit" (e.g. N).
  4. If the sender gets a NAK, it should resend the chunk. After a few unsuccessful retries, it should report an error.
  5. After burning a block, it should be compared to the RAM buffer. If the match fails, send a "NAK-burn" failure (e.g. B).
  6. Again, if both receive and burn are successful, send the ACK (e.g. A)
  7. 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?

发布评论

评论列表(0)

  1. 暂无评论