实验结果如上图APP1区上电后等待服务器下发固件包确认完毕后开始传输通过帧池缓存收到的数据同时使用crc校验检验每帧的数据是否正确加入传输完成帧通知mcu传输结束并校验总数据crc是否正确传输完进入request_upgrade_task修改更新标志位并开始擦除重写flash。void request_upgrade_task(void *pvParameters) { (void)pvParameters; while (1) { /* 等待 task_ota_flash 发来的升级通知 */ xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); if (xWifisendTaskHandle ! NULL) xTaskNotify(xWifisendTaskHandle, 0x02, eSetValueWithOverwrite); ota_config_t config; config.magic OTA_MAGIC; config.update_flag 1; config.version CURRENT_VERSION; config.firmware_size g_firmware_size; config.crc32 g_firmware_crc; __disable_irq(); //关闭全局中断防止读写过程被打断 // 擦除配置区所在页写入 config flash_erase(CONFIG_BASE, sizeof(ota_config_t)); flash_write(CONFIG_BASE, (uint8_t*)config, sizeof(ota_config_t)); // 软复位 NVIC_SystemReset(); } }/* Flash 写任务 */ void task_ota_flash(void *pvParameters) { fw_frame_t frame; uint32_t write_addr; uint32_t received_count 0; /* 已成功写入的数据帧数用于收齐校验 */ int32_t erased_page -1; /* 已擦除的 OTA 区页号-1 表示本次尚未擦除 */ while (1) { if (xQueueReceive(xFrameQueue, frame, portMAX_DELAY) ! pdTRUE) continue; /* 判断是否是结束帧 */ if (frame.current_packet PACKET_END) { uint32_t fw_size 0; uint32_t fw_crc 0; /* 结束帧负载data[0:4]固件总长(小端), data[4:8]整包 CRC32(小端) */ if (frame.data_len 8) { fw_size (uint32_t)frame.slot-buf[0] | ((uint32_t)frame.slot-buf[1] 8) | ((uint32_t)frame.slot-buf[2] 16) | ((uint32_t)frame.slot-buf[3] 24); fw_crc (uint32_t)frame.slot-buf[4] | ((uint32_t)frame.slot-buf[5] 8) | ((uint32_t)frame.slot-buf[6] 16) | ((uint32_t)frame.slot-buf[7] 24); } frame_pool_free(frame.slot); /* 收齐校验已收数据帧数必须等于总包数否则丢弃本次升级 */ if (fw_size ! 0 received_count frame.total_packets) { g_firmware_size fw_size; g_firmware_crc fw_crc; xTaskNotify(xrequest_upgrade_taskHandle, 0x01, eSetValueWithOverwrite); } received_count 0; erased_page -1; /* 本次传输结束下次重新擦除 */ continue; /* 结束帧不写 Flash直接继续 */ } /* 新一轮传输开始清零计数并强制重新擦除 */ if (frame.current_packet 0) { received_count 0; erased_page -1; } write_addr OTA_BUFFER_BASE ((uint32_t)frame.current_packet * DATA_FRAME_LEN); /* 边界保护不能写到 config 区及以后 */ if (write_addr DATA_FRAME_LEN CONFIG_BASE) { frame_pool_free(frame.slot); continue; } /* Flash 只能把 1 写成 0写入前必须先擦除所在页按页惰性擦除每 4 帧一次 */ { int32_t page (int32_t)((write_addr - OTA_BUFFER_BASE) / FLASH_PAGE_SIZE); if (page ! erased_page) { if (flash_erase(OTA_BUFFER_BASE (uint32_t)page * FLASH_PAGE_SIZE, FLASH_PAGE_SIZE) ! Status_OK) { frame_pool_free(frame.slot); continue; } erased_page page; } } /* 写 Flash只写实际长度不写 padding */ if (flash_write(write_addr, frame.slot-buf, frame.data_len) Status_OK) received_count; /* 写入成功计入已收帧数 */ else { /* 写失败可以记录错误忽略 */ } /* 归还 slot */ frame_pool_free(frame.slot); } }在数据解析任务中使用状态机判断当前接收状态void wifi_get_task(void *pvParameters) { uint8_t byte; uint8_t line[AT_LINE_MAX]; size_t line_len 0; parser_t parser; /* 初始化解析器 */ memset(parser, 0, sizeof(parser)); parser.state 0; /* ST_HEADER_1 */ while (1) { /* 一次读一个字节 */ if (xStreamBufferReceive(uartStreamBuffer, byte, 1, portMAX_DELAY) ! 1) continue; /* ---------- 状态机 ---------- */ switch (parser.state) { /* 状态 0等 0x55同时处理 AT 行 */ case 0: /* ST_HEADER_1 */ /* AT 行处理遇到 \n 提交一行 */ if (byte \n line_len 0) { line[line_len] \0; xQueueSend(atQueue, line, 0); line_len 0; } else if (byte ! \r) { if (line_len AT_LINE_MAX - 1) line[line_len] byte; } /* 检测帧头起点 */ if (byte FRAME_HEADER_1) { parser.state 1; /* ST_HEADER_2 */ } break; /* 状态 1等 0x44 */ case 1: /* ST_HEADER_2 */ if (byte FRAME_HEADER_2) { /* 帧头确认从帧池分配 slot */ parser.slot frame_pool_alloc(); if (parser.slot NULL) { parser.state 0; /* 无空闲 slot丢弃这一帧 */ break; } parser.crc32 0; parser.crc_byte_idx 0; parser.data_idx 0; parser.state 2; /* ST_TOTAL */ } else if (byte FRAME_HEADER_1) { /* 连续 0x55保持在 ST_HEADER_2 */ parser.state 1; } else { /* 不是 0x44回起点当前字节按 AT 处理 */ if (byte \n line_len 0) { line[line_len] \0; xQueueSend(atQueue, line, 0); line_len 0; } else if (byte ! \r) { if (line_len AT_LINE_MAX - 1) line[line_len] byte; } parser.state 0; } break; /* 状态 2读总包数 */ case 2: /* ST_TOTAL */ parser.total_packets byte; parser.state 3; /* ST_CURRENT */ break; /* 状态 3读当前包号 */ case 3: /* ST_CURRENT */ parser.current_packet byte; parser.data_len 0; parser.len_byte_idx 0; parser.state 4; /* ST_LEN */ break; /* 状态 4读实际长度2 字节小端 */ case 4: /* ST_LEN */ parser.data_len | (uint16_t)((uint16_t)byte (parser.len_byte_idx * 8)); parser.len_byte_idx; if (parser.len_byte_idx 2) { if (parser.data_len 0 || parser.data_len DATA_FRAME_LEN) { /* 非法长度回起点释放 slot */ frame_pool_free(parser.slot); parser.slot NULL; parser.state 0; } else { parser.crc_byte_idx 0; parser.state 5; /* ST_CRC */ } } break; /* 状态 5读 4 字节 CRC32小端 */ case 5: /* ST_CRC */ parser.crc32 | ((uint32_t)byte) (parser.crc_byte_idx * 8); parser.crc_byte_idx; if (parser.crc_byte_idx 4) { parser.data_idx 0; parser.state 6; /* ST_DATA */ } break; /* 状态 6读数据固定 DATA_FRAME_LEN 字节 */ case 6: /* ST_DATA */ parser.slot-buf[parser.data_idx] byte; if (parser.data_idx DATA_FRAME_LEN) { parser.state 7; /* ST_TAIL_1 */ } break; /* 状态 7等 0x33 */ case 7: /* ST_TAIL_1 */ if (byte FRAME_TAIL_1) { parser.state 8; /* ST_TAIL_2 */ } else { frame_pool_free(parser.slot); parser.slot NULL; parser.state 0; } break; /* 状态 8等 0x22帧完成 */ case 8: /* ST_TAIL_2 */ if (byte FRAME_TAIL_2) { /* 帧完成校验 CRC */ parser.crc_calc calculate_crc32_ex( parser.total_packets, parser.current_packet, parser.data_len, parser.slot-buf ); if (parser.crc_calc parser.crc32) { /* CRC 通过交给 Flash 写入 */ fw_frame_t frame; frame.total_packets parser.total_packets; frame.current_packet parser.current_packet; frame.data_len parser.data_len; frame.crc32 parser.crc32; frame.slot parser.slot; if (xQueueSend(xFrameQueue, frame, 0) ! pdTRUE) { /* 队列满归还 slot */ frame_pool_free(parser.slot); } /* 注意slot 在这里不 free由 Flash 任务 free */ } else { /* CRC 失败归还 slot */ frame_pool_free(parser.slot); } parser.slot NULL; parser.state 0; line_len 0; } else { frame_pool_free(parser.slot); parser.slot NULL; parser.state 0; } break; default: parser.state 0; break; } } }APP1区同样基于freertos来写更新程序仅为精简串口输出程序。如有需要可以到我的github仓库拉取0571xu/bootloader_APP1code一起学习daydayup