凌動微MM32 芯片串口升級OTA功能開發4. 擦除功能
- 一、工作流程
- 要用到的擦除指令格式:
- 二、上位機修改
- 1. 計算擦除區域
- 三、主控處理
- 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. 一些注意的要點
- 地址保護:只能擦除應用程序區(
0x08004000 ~ 0x0801FFFF),不能擦除引導程序區 - 頁對齊:擦除大小必須是Flash頁大小(1KB)的整數倍
- 逐頁擦除:Flash按頁擦除,需要循環處理每一頁
- 錯誤處理:擦除失敗時及時鎖定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;
}
運行示例:
本項目代碼開源在:https://gitee.com/xundh/xundh-arm-m0-ota
本項目主體代碼由AI生成。