凌動微MM32 芯片串口升級OTA功能開發4. 擦除功能

  • 一、工作流程
  • 要用到的擦除指令格式:
  • 二、上位機修改
  • 1. 計算擦除區域
  • 三、主控處理
  • 1. 一些注意的要點
  • 2. 主控解析擦除指令解析
  • 3. 擦除指令處理
  • 4. 擦除的實現代碼
  • 運行示例:

凌動微MM32 芯片串口升級OTA功能開發4. 擦除功能_#stm32

一、工作流程

  1. 打開串口
  2. 發送握手包
  3. 收到握手應答後 → 發送擦除應用區指令
  4. 收到擦除應答後 → 繼續後續步驟(發送固件、校驗等)

要用到的擦除指令格式:

命令碼: 0x14
類型: 0x01(控制類型)
地址: 0x08004000(應用區起始地址)
數據: 4 字節大端序的擦除大小(0x0001C000 = 112KB)

二、上位機修改

1. 計算擦除區域

按 bin 文件大小計算,並向上取整到頁大小(1KB)的整數倍。工作原理:

  • 如果 bin 文件是 50KB,擦除大小 = 50KB(向上取整到 1KB 的整數倍)
  • 如果 bin 文件是 50.5KB,擦除大小 = 51KB(向上取整)
  • 如果 bin 文件是 112KB,擦除大小 = 112KB(整個應用區)

修改代碼:

"""
    def simulate_upgrade(self, log: LogCallback, set_progress: ProgressCallback) -> None:
        """
        模擬一個升級流程,方便聯調 UI。

        實際實現中,應改為在線程 / 異步中執行,並通過回調更新進度和日誌。
        """
        self._stop_requested = False  # 重置停止標誌
        try:
            # 打開串口
            log(f"打開串口 {self.comm.port} (波特率: {self.comm.baudrate})...")
            self.comm.open()
            set_progress(10)
            QApplication.processEvents()
            
            # 快速檢查並接收主控主動發送的日誌(如 "boot..."),非阻塞
            self.comm.receive_and_log_text(log)
            QApplication.processEvents()

            # 發送握手包
            log("發送握手包...")
            set_progress(20)
            QApplication.processEvents()

            if self.send_handshake(log):
                log("握手成功!")
                set_progress(30)
                QApplication.processEvents()

                # 檢查停止請求
                if self._stop_requested:
                    log("收到停止請求,停止升級流程...")
                    return

                # 發送擦除應用區指令
                log("發送擦除應用區指令...")
                set_progress(40)
                QApplication.processEvents()

                # 根據bin文件大小計算擦除大小(向上取整到頁大小的整數倍)
                bin_size = os.path.getsize(self.bin_path)
                FLASH_PAGE_SIZE = 0x400  # 1KB
                erase_size = ((bin_size + FLASH_PAGE_SIZE - 1) // FLASH_PAGE_SIZE) * FLASH_PAGE_SIZE
                log(f"固件文件大小: {bin_size} 字節 ({bin_size/1024:.2f}KB)")
                log(f"擦除大小: {erase_size} 字節 ({erase_size/1024:.2f}KB)")

                if self.send_erase_flash(log, size=erase_size):
                    log("擦除應用區成功")
                    set_progress(50)
                else:
                    log("❌ 擦除應用區失敗,停止升級")
                    set_progress(0)
                    return
                QApplication.processEvents()

                # 檢查停止請求
                if self._stop_requested:
                    log("收到停止請求,停止升級流程...")
                    return

                # 模擬後續步驟
                steps: List[Tuple[str, int]] = [
                    ("發送固件數據...", 80),
                    ("校驗固件...", 95),
                    ("升級完成。", 100),
                ]

                for msg, val in steps:
                    # 檢查停止請求
                    if self._stop_requested:
                        log("收到停止請求,停止升級流程...")
                        break

                    log(msg)
                    set_progress(val)
                    QApplication.processEvents()
                    time.sleep(0.5)  # 模擬處理時間
            else:
                log("❌ 握手失敗,停止升級")
                set_progress(0)

        except Exception as e:
            log(f"升級過程出錯: {e}")
            set_progress(0)
        finally:
            if self.comm.is_open():
                self.comm.close()
                log("串口已關閉")

三、主控處理

1. 一些注意的要點

  1. 地址保護:只能擦除應用程序區(0x08004000 ~ 0x0801FFFF),不能擦除引導程序區
  2. 頁對齊:擦除大小必須是Flash頁大小(1KB)的整數倍
  3. 逐頁擦除:Flash按頁擦除,需要循環處理每一頁
  4. 錯誤處理:擦除失敗時及時鎖定Flash並返回錯誤碼

2. 主控解析擦除指令解析

需要從上位機指令裏解析出來需要擦除的Flash大小。

bool OTA_Protocol_GetCommandPacket(OTA_CommandPacket_t* cmd_packet)
{
    if (cmd_packet == NULL || !g_protocol_ctx.frame_valid) {
        return false;
    }
    
    /* 數據包起始位置:幀頭(2) + 序號(1) + 序號異或(1) + 包長度(2) = 6 */
    uint8_t* packet_data = &g_protocol_ctx.rx_buffer[6];
    
    /* 解析命令包 */
    cmd_packet->cmd = packet_data[0];      /* 命令碼:0x14 */
    cmd_packet->type = packet_data[1];     /* 類型:0x01 */
    
    /* 解析地址(大端序) */
    cmd_packet->addr = ((uint32_t)packet_data[2] << 24) |
                       ((uint32_t)packet_data[3] << 16) |
                       ((uint32_t)packet_data[4] << 8) |
                       (uint32_t)packet_data[5];
    
    /* 解析保留字段 */
    cmd_packet->reserved = ((uint32_t)packet_data[6] << 24) |
                           ((uint32_t)packet_data[7] << 16) |
                           ((uint32_t)packet_data[8] << 8) |
                           (uint32_t)packet_data[9];
    
    /* 解析數據(擦除大小) */
    if (g_protocol_ctx.packet_len > 10) {
        cmd_packet->data_len = g_protocol_ctx.packet_len - 10;
        memcpy(cmd_packet->data, &packet_data[10], cmd_packet->data_len);
    } else {
        cmd_packet->data_len = 0;
    }
    
    return true;
}

3. 擦除指令處理

bool OTA_CmdHandler_HandleEraseFlash(OTA_CommandPacket_t* cmd_packet)
{
    OTA_ResponsePacket_t response = {0};
    uint32_t erase_addr;
    uint32_t erase_size;
    BootFlash_Error_t flash_err;
    
    if (cmd_packet == NULL) {
        return false;
    }
    
    /* 初始化應答包 */
    response.cmd = cmd_packet->cmd;
    response.addr = cmd_packet->addr;
    response.reserved = 0;
    response.data_len = 0;
    
    /* 檢查是否正在處理擦除操作(防止重複擦除) */
    if (g_erase_in_progress) {
        response.status = OTA_STATUS_ERROR;
        return OTA_Protocol_SendResponse(&response);
    }
    
    /* 設置擦除進行中標誌 */
    g_erase_in_progress = true;
    
    /* 解析擦除地址 */
    erase_addr = cmd_packet->addr;
    
    /* 解析擦除大小(從data字段,4字節大端序) */
    if (cmd_packet->data_len < 4) {
        g_erase_in_progress = false;
        response.status = OTA_STATUS_PARAM_ERR;
        return OTA_Protocol_SendResponse(&response);
    }
    
    erase_size = ((uint32_t)cmd_packet->data[0] << 24) |
                 ((uint32_t)cmd_packet->data[1] << 16) |
                 ((uint32_t)cmd_packet->data[2] << 8) |
                 (uint32_t)cmd_packet->data[3];
    
    /* 調用Flash擦除函數 */
    /* 注意:擦除操作可能耗時較長(112KB約需數秒到數十秒) */
    flash_err = BootFlash_Erase(erase_addr, erase_size);
    
    /* 擦除完成,清除標誌 */
    g_erase_in_progress = false;
    
    /* 根據錯誤碼設置應答狀態 */
    switch (flash_err) {
        case BOOT_FLASH_OK:
            response.status = OTA_STATUS_SUCCESS;
            break;
            
        case BOOT_FLASH_ERR_PARAM:
        case BOOT_FLASH_ERR_SIZE:
            response.status = OTA_STATUS_PARAM_ERR;
            break;
            
        case BOOT_FLASH_ERR_ADDR:
            response.status = OTA_STATUS_ADDR_ERR;
            break;
            
        case BOOT_FLASH_ERR_ERASE:
            response.status = OTA_STATUS_FLASH_ERR;
            break;
            
        default:
            response.status = OTA_STATUS_ERROR;
            break;
    }
    
    /* 發送應答 */
    return OTA_Protocol_SendResponse(&response);
}

4. 擦除的實現代碼

BootFlash_Error_t BootFlash_Erase(uint32_t addr, uint32_t size)
{
    uint32_t end_addr;
    FLASH_Status flash_status;
    uint32_t page_addr;
    uint32_t pages_to_erase;
    
    /* 參數檢查 */
    if (size == 0) {
        return BOOT_FLASH_ERR_PARAM;
    }
    
    /* 計算結束地址 */
    end_addr = addr + size;
    
    /* 檢查地址範圍:只能擦除應用程序區 */
    if (addr < APPLICATION_START || addr > APPLICATION_END) {
        return BOOT_FLASH_ERR_ADDR;
    }
    
    if (end_addr > APPLICATION_END || end_addr < addr) {
        return BOOT_FLASH_ERR_ADDR;
    }
    
    /* 檢查擦除大小是否為頁大小的整數倍 */
    if ((size % FLASH_PAGE_SIZE) != 0) {
        return BOOT_FLASH_ERR_SIZE;
    }
    
    /* 計算需要擦除的頁數 */
    pages_to_erase = size / FLASH_PAGE_SIZE;
    
    /* 解鎖Flash */
    FLASH_Unlock();
    
    /* 按頁擦除 */
    page_addr = addr;
    for (uint32_t i = 0; i < pages_to_erase; i++) {
        flash_status = FLASH_ErasePage(page_addr);
        if (flash_status != FLASH_COMPLETE) {
            /* 擦除失敗,鎖定Flash並返回錯誤 */
            FLASH_Lock();
            return BOOT_FLASH_ERR_ERASE;
        }
        page_addr += FLASH_PAGE_SIZE;
    }
    
    /* 鎖定Flash */
    FLASH_Lock();
    
    /* 擦除成功 */
    return BOOT_FLASH_OK;
}

運行示例:

凌動微MM32 芯片串口升級OTA功能開發4. 擦除功能_#嵌入式硬件_02

本項目代碼開源在:https://gitee.com/xundh/xundh-arm-m0-ota
本項目主體代碼由AI生成。