775 lines
23 KiB
C
775 lines
23 KiB
C
#include "app_iap.h"
|
||
#include "app_flash.h"
|
||
#include "hw_can.h"
|
||
#include "stm32f1xx_hal.h"
|
||
#include <string.h>
|
||
#include "cmsis_os2.h" // ::CMSIS:RTOS2
|
||
/* ============================================================
|
||
* IAP 模块实现
|
||
*
|
||
* 核心设计: 先握手再传数据
|
||
* 1. 握手阶段: 0x10进入会话 → 0x22读版本 → 0x27安全访问
|
||
* 2. 数据传输: 0x34声明大小 → 0x36 01开始 → 纯数据帧 → 0x37结束
|
||
* 3. 校验烧写: 0x31触发 → 0x11跳转APP
|
||
*
|
||
* 数据模式(DATA_MODE)下:
|
||
* 每帧8字节全部是固件数据,无SID/序号开销
|
||
* 按固件size计数,收够自动退出数据模式
|
||
* 0x36 01命令帧不包含数据,仅触发状态切换
|
||
* ============================================================ */
|
||
|
||
/* IAP 上下文 */
|
||
static iap_ctx_t iap_ctx;
|
||
|
||
/* 乒乓缓冲区: DATA_MODE下CAN数据先入缓冲区, 满后批量写Flash */
|
||
#define IAP_BUF_SIZE 1024U /* 每个缓冲区1KB(128帧×8字节) */
|
||
#define IAP_BUF_NUM 2U /* 两个缓冲区乒乓 */
|
||
static uint8_t iap_buf[IAP_BUF_NUM][IAP_BUF_SIZE];
|
||
static uint8_t iap_active_buf; /* 当前写入的缓冲区索引(0或1) */
|
||
static uint16_t iap_buf_pos; /* 当前缓冲区写入位置 */
|
||
static uint8_t iap_flush_pending; /* 有缓冲区待写入Flash标志 */
|
||
static uint8_t iap_flush_buf; /* 待写入Flash的缓冲区索引 */
|
||
static uint16_t iap_flush_len; /* 待写入Flash的数据长度 */
|
||
|
||
/* ===================== 内部辅助函数 ===================== */
|
||
|
||
/* 发送UDS正响应 */
|
||
static void iap_send_pos(uint8_t sid, uint8_t *extra, uint8_t extra_len)
|
||
{
|
||
uint8_t resp[8] = {0};
|
||
resp[0] = UDS_POS_RESP(sid);
|
||
for (uint8_t i = 0; i < extra_len && i < 7; i++)
|
||
resp[1 + i] = extra[i];
|
||
hwCanSend(IAP_CAN_TX_ID, resp, 8);
|
||
}
|
||
|
||
/* 发送UDS否定响应 */
|
||
static void iap_send_neg(uint8_t sid, uint8_t nrc)
|
||
{
|
||
uint8_t resp[8] = {0};
|
||
resp[0] = UDS_NEG_RESP;
|
||
resp[1] = sid;
|
||
resp[2] = nrc;
|
||
hwCanSend(IAP_CAN_TX_ID, resp, 8);
|
||
}
|
||
|
||
/* 擦除App区所有页 */
|
||
static uint8_t iap_erase_app_pages(void)
|
||
{
|
||
for (uint32_t i = 0; i < IAP_APP_PAGE_NUM; i++)
|
||
{
|
||
if (!flash_erase_page(IAP_APP_ADDR + i * FLASH_PAGE_SIZE))
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* 将缓冲区数据批量写入Flash(只Unlock/Lock一次) */
|
||
static uint8_t iap_flush_buffer(uint32_t addr, const uint8_t *data, uint32_t len)
|
||
{
|
||
uint32_t i = 0;
|
||
|
||
HAL_FLASH_Unlock();
|
||
|
||
/* 非字对齐起始: 按半字编程到对齐 */
|
||
/* STM32F1 Flash 只支持半字(16bit)编程,不支持字节编程 */
|
||
while ((addr + i) % 4 != 0 && i + 2 <= len)
|
||
{
|
||
uint16_t hw = *(const uint16_t *)&data[i];
|
||
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr + i, hw) != HAL_OK)
|
||
{
|
||
HAL_FLASH_Lock();
|
||
return 0;
|
||
}
|
||
i += 2;
|
||
}
|
||
|
||
/* 按字(32bit)批量编程 */
|
||
while (i + 4 <= len)
|
||
{
|
||
uint32_t word = *(const uint32_t *)&data[i];
|
||
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, word) != HAL_OK)
|
||
{
|
||
HAL_FLASH_Lock();
|
||
return 0;
|
||
}
|
||
i += 4;
|
||
}
|
||
|
||
/* 剩余不足4字节: 按半字编程 */
|
||
while (i + 2 <= len)
|
||
{
|
||
uint16_t hw = *(const uint16_t *)&data[i];
|
||
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr + i, hw) != HAL_OK)
|
||
{
|
||
HAL_FLASH_Lock();
|
||
return 0;
|
||
}
|
||
i += 2;
|
||
}
|
||
|
||
/* 剩余1字节(奇数长度): 补0xFF组成半字编程 */
|
||
if (i < len)
|
||
{
|
||
uint16_t hw = (uint16_t)data[i] | 0xFF00U;
|
||
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr + i, hw) != HAL_OK)
|
||
{
|
||
HAL_FLASH_Lock();
|
||
return 0;
|
||
}
|
||
i++;
|
||
}
|
||
|
||
HAL_FLASH_Lock();
|
||
return 1;
|
||
}
|
||
|
||
/* ===================== 断电续传辅助函数 ===================== */
|
||
|
||
/* 从进度位图计算续传偏移量
|
||
* 位图为1的位表示该块未完成, 为0表示已完成
|
||
* 找到第一个为1的位, 其索引即为续传起点块号 */
|
||
static uint32_t iap_calc_offset(uint32_t fw_size)
|
||
{
|
||
uint32_t total_blocks = (fw_size + IAP_BUF_SIZE - 1) / IAP_BUF_SIZE;
|
||
uint32_t bitmap_lo = flash_read_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_BITMAP_LO);
|
||
uint32_t bitmap_hi = flash_read_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_BITMAP_HI);
|
||
|
||
for (uint32_t i = 0; i < total_blocks; i++)
|
||
{
|
||
uint32_t bitmap = (i < 32) ? bitmap_lo : bitmap_hi;
|
||
uint32_t bit = i % 32;
|
||
if (bitmap & (1U << bit))
|
||
{
|
||
/* 该位为1: 此块未完成, 从这里开始续传 */
|
||
return i * IAP_BUF_SIZE;
|
||
}
|
||
}
|
||
/* 所有块都已完成 */
|
||
return fw_size;
|
||
}
|
||
|
||
/* 完成1KB块后更新进度位图(将对应位清0)
|
||
* 利用 Flash 只能 1→0 的物理特性, 无需擦除参数页
|
||
* 每次仅写1个字(~40μs), 不阻塞CAN接收 */
|
||
static void iap_update_bitmap(uint32_t block_idx)
|
||
{
|
||
uint32_t addr;
|
||
uint32_t bit = block_idx % 32;
|
||
|
||
if (block_idx < 32)
|
||
addr = IAP_PARAM_ADDR + IAP_PARAM_OFF_BITMAP_LO;
|
||
else
|
||
addr = IAP_PARAM_ADDR + IAP_PARAM_OFF_BITMAP_HI;
|
||
|
||
uint32_t cur_val = flash_read_word(addr);
|
||
uint32_t new_val = cur_val & ~(1U << bit);
|
||
|
||
/* 仅当位需要变化时才写入(1→0) */
|
||
if (new_val != cur_val)
|
||
{
|
||
HAL_FLASH_Unlock();
|
||
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, new_val);
|
||
HAL_FLASH_Lock();
|
||
}
|
||
}
|
||
|
||
/* 全新升级: 初始化参数区(擦除后写入 flag + fw_size) */
|
||
static uint8_t iap_param_init(uint32_t fw_size)
|
||
{
|
||
/* 擦除参数页(擦除后 flag/bitmap 全 0xFF, 即 UPGRADING 状态 + 所有块未完成) */
|
||
if (!flash_erase_page(IAP_PARAM_ADDR))
|
||
return 0;
|
||
|
||
/* 写入升级标志 */
|
||
if (!flash_write_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_FLAG, IAP_FLAG_UPGRADING))
|
||
return 0;
|
||
|
||
/* 写入固件大小 */
|
||
if (!flash_write_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_FW_SIZE, fw_size))
|
||
return 0;
|
||
|
||
/* bitmap 保持擦除态 0xFFFFFFFF(所有块未完成), 无需额外写入 */
|
||
return 1;
|
||
}
|
||
|
||
/* ===================== 阶段1: 握手处理 ===================== */
|
||
|
||
/* 0x10 会话控制 */
|
||
static void iap_handle_session(uint8_t *data, uint8_t dlc)
|
||
{
|
||
if (dlc < 2)
|
||
{
|
||
iap_send_neg(UDS_SESSION_CONTROL, NRC_REQUEST_OUT_OF_RANGE);
|
||
return;
|
||
}
|
||
|
||
/* data[1]=子功能: 0x01默认会话, 0x02扩展会话, 0x03编程会话 */
|
||
if (data[1] >= 0x02)
|
||
{
|
||
/* App模式收到0x10 0x02: 写编程请求标志, 软复位进入Bootloader */
|
||
if (iap_is_app_mode())
|
||
{
|
||
iap_send_pos(UDS_SESSION_CONTROL, &data[1], 1); /* 先回响应 */
|
||
iap_set_prog_request(); /* 写0x12121212到参数区 */
|
||
HAL_Delay(50); /* 确保Flash写入和CAN发送完成 */
|
||
NVIC_SystemReset(); /* 软复位, 进入Bootloader */
|
||
}
|
||
|
||
/* Bootloader模式: 进入编程会话 */
|
||
iap_ctx.state = IAP_STATE_SESSION;
|
||
iap_ctx.security_unlocked = 0;
|
||
}
|
||
|
||
uint8_t sub = data[1];
|
||
iap_send_pos(UDS_SESSION_CONTROL, &sub, 1);
|
||
}
|
||
|
||
/* 0x22 读数据(版本/握手) */
|
||
static void iap_handle_read_data(uint8_t *data, uint8_t dlc)
|
||
{
|
||
if (dlc < 3)
|
||
{
|
||
iap_send_neg(UDS_READ_DATA, NRC_REQUEST_OUT_OF_RANGE);
|
||
return;
|
||
}
|
||
|
||
/* data[1..2]=DID, 这里简化: 回 "IAP01" */
|
||
uint8_t resp[7] = {0};
|
||
resp[0] = data[1]; /* DID高字节回显 */
|
||
resp[1] = data[2]; /* DID低字节回显 */
|
||
resp[2] = 'I';
|
||
resp[3] = 'A';
|
||
resp[4] = 'P';
|
||
resp[5] = '0';
|
||
resp[6] = '1';
|
||
iap_send_pos(UDS_READ_DATA, resp, 7);
|
||
}
|
||
|
||
/* 0x27 安全访问(Seed/Key) */
|
||
static void iap_handle_security(uint8_t *data, uint8_t dlc)
|
||
{
|
||
if (dlc < 2)
|
||
{
|
||
iap_send_neg(UDS_SECURITY_ACCESS, NRC_REQUEST_OUT_OF_RANGE);
|
||
return;
|
||
}
|
||
|
||
uint8_t sub = data[1];
|
||
|
||
if (sub == 0x01)
|
||
{
|
||
/* 请求Seed: 返回固定Seed(简化版) */
|
||
uint8_t resp[7] = {0};
|
||
resp[0] = 0x01;
|
||
resp[1] = 0x12;
|
||
resp[2] = 0x34;
|
||
resp[3] = 0x56;
|
||
resp[4] = 0x78;
|
||
iap_send_pos(UDS_SECURITY_ACCESS, resp, 5);
|
||
}
|
||
else if (sub == 0x02)
|
||
{
|
||
/* 发送Key: 简化版,固定Key = Seed按位取反 */
|
||
/* 上位机应发送 0xED 0xCB 0xA9 0x87 */
|
||
if (dlc >= 6 &&
|
||
data[2] == 0xED && data[3] == 0xCB &&
|
||
data[4] == 0xA9 && data[5] == 0x87)
|
||
{
|
||
iap_ctx.security_unlocked = 1;
|
||
uint8_t ack = 0x02;
|
||
iap_send_pos(UDS_SECURITY_ACCESS, &ack, 1);
|
||
}
|
||
else
|
||
{
|
||
iap_send_neg(UDS_SECURITY_ACCESS, NRC_GENERAL_REJECT);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
iap_send_neg(UDS_SECURITY_ACCESS, NRC_REQUEST_OUT_OF_RANGE);
|
||
}
|
||
}
|
||
|
||
/* ===================== 阶段2: 数据传输处理 ===================== */
|
||
|
||
/* 0x34 请求下载(支持断电续传) */
|
||
static void iap_handle_request_download(uint8_t *data, uint8_t dlc)
|
||
{
|
||
/* 必须在会话中且已解锁 */
|
||
if (iap_ctx.state != IAP_STATE_SESSION)
|
||
{
|
||
iap_send_neg(UDS_REQUEST_DOWNLOAD, NRC_CONDITIONS_NOT_CORRECT);
|
||
return;
|
||
}
|
||
|
||
/* data[0]=0x34, data[1..4]=固件大小(大端) */
|
||
if (dlc < 5)
|
||
{
|
||
iap_send_neg(UDS_REQUEST_DOWNLOAD, NRC_REQUEST_OUT_OF_RANGE);
|
||
return;
|
||
}
|
||
|
||
uint32_t fw_size = ((uint32_t)data[1] << 24) |
|
||
((uint32_t)data[2] << 16) |
|
||
((uint32_t)data[3] << 8) |
|
||
((uint32_t)data[4]);
|
||
|
||
if (fw_size == 0 || fw_size > IAP_APP_SIZE)
|
||
{
|
||
iap_send_neg(UDS_REQUEST_DOWNLOAD, NRC_REQUEST_OUT_OF_RANGE);
|
||
return;
|
||
}
|
||
|
||
/* ===== 断电续传判断 ===== */
|
||
uint32_t offset = 0;
|
||
uint32_t saved_flag = flash_read_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_FLAG);
|
||
uint32_t saved_size = flash_read_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_FW_SIZE);
|
||
|
||
if (saved_flag == IAP_FLAG_UPGRADING && saved_size == fw_size)
|
||
{
|
||
/* 续传模式: 固件大小匹配, 从进度位图计算断点 */
|
||
offset = iap_calc_offset(fw_size);
|
||
if (offset >= fw_size)
|
||
{
|
||
/* 所有块已完成但未校验, 直接进入校验阶段 */
|
||
offset = fw_size;
|
||
}
|
||
/* 续传模式不擦除 App 区, 保留已写入数据 */
|
||
}
|
||
else
|
||
{
|
||
/* 全新升级: 擦除 App 区 + 初始化参数区 */
|
||
if (!iap_erase_app_pages())
|
||
{
|
||
iap_send_neg(UDS_REQUEST_DOWNLOAD, NRC_GENERAL_REJECT);
|
||
return;
|
||
}
|
||
if (!iap_param_init(fw_size))
|
||
{
|
||
iap_send_neg(UDS_REQUEST_DOWNLOAD, NRC_GENERAL_REJECT);
|
||
return;
|
||
}
|
||
offset = 0;
|
||
}
|
||
|
||
/* 进入下载状态, 等待0x36 01开始数据 */
|
||
iap_ctx.state = IAP_STATE_DOWNLOADING;
|
||
iap_ctx.fw_size = fw_size;
|
||
iap_ctx.fw_received = offset; /* 续传: 跳过已接收部分 */
|
||
iap_ctx.write_addr = IAP_APP_ADDR + offset; /* 续传: 从断点开始写 */
|
||
|
||
/* 初始化乒乓缓冲区 */
|
||
iap_active_buf = 0;
|
||
iap_buf_pos = 0;
|
||
iap_flush_pending = 0;
|
||
iap_flush_buf = 0;
|
||
iap_flush_len = 0;
|
||
|
||
/* 正响应: 0x74 + 每帧字节数 + offset(4字节大端) */
|
||
/* 上位机根据 offset 决定从固件何处开始发送 */
|
||
uint8_t extra[5];
|
||
extra[0] = 0x08; /* 每帧8字节数据 */
|
||
extra[1] = (uint8_t)(offset >> 24);
|
||
extra[2] = (uint8_t)(offset >> 16);
|
||
extra[3] = (uint8_t)(offset >> 8);
|
||
extra[4] = (uint8_t)(offset);
|
||
iap_send_pos(UDS_REQUEST_DOWNLOAD, extra, 5);
|
||
}
|
||
|
||
/* 0x36 传输数据(命令帧: 0x36 01, 之后进入纯数据模式) */
|
||
static void iap_handle_transfer_data(uint8_t *data, uint8_t dlc)
|
||
{
|
||
if (iap_ctx.state != IAP_STATE_DOWNLOADING)
|
||
{
|
||
iap_send_neg(UDS_TRANSFER_DATA, NRC_CONDITIONS_NOT_CORRECT);
|
||
return;
|
||
}
|
||
|
||
/* 命令帧: data[0]=0x36, data[1]=0x01(块序号) */
|
||
if (dlc < 2 || data[1] != 0x01)
|
||
{
|
||
iap_send_neg(UDS_TRANSFER_DATA, NRC_REQUEST_OUT_OF_RANGE);
|
||
return;
|
||
}
|
||
|
||
/* 进入纯数据模式: 此后每帧8字节直接写Flash, 无响应 */
|
||
iap_ctx.state = IAP_STATE_DATA_MODE;
|
||
|
||
/* 命令帧本身不含数据, 仅回正响应 */
|
||
iap_send_pos(UDS_TRANSFER_DATA, NULL, 0);
|
||
}
|
||
|
||
/* 纯数据模式处理: 乒乓缓冲区
|
||
* CAN数据写入活跃缓冲区, 满后切换到另一个缓冲区
|
||
* 上一轮满缓冲区在下一帧到来时写入Flash(利用帧间隔并行) */
|
||
static void iap_handle_data_frame(uint8_t *data, uint8_t dlc)
|
||
{
|
||
uint32_t remaining = iap_ctx.fw_size - iap_ctx.fw_received;
|
||
uint32_t write_len = (dlc < remaining) ? dlc : remaining;
|
||
|
||
/* 步骤1: 如果有待写入Flash的缓冲区, 先写入(上一轮切换遗留)
|
||
* 此时CAN中断可以继续往frame_buf写入, 实现半并行 */
|
||
if (iap_flush_pending)
|
||
{
|
||
if (!iap_flush_buffer(iap_ctx.write_addr,
|
||
iap_buf[iap_flush_buf],
|
||
iap_flush_len))
|
||
{
|
||
iap_ctx.state = IAP_STATE_DOWNLOADING;
|
||
iap_send_neg(UDS_TRANSFER_DATA, NRC_GENERAL_REJECT);
|
||
return;
|
||
}
|
||
iap_ctx.write_addr += iap_flush_len;
|
||
iap_flush_pending = 0;
|
||
|
||
/* 断电续传: 更新进度位图(将刚完成的块对应位清0) */
|
||
uint32_t completed_bytes = iap_ctx.write_addr - IAP_APP_ADDR;
|
||
uint32_t block_idx = (completed_bytes - 1) / IAP_BUF_SIZE;
|
||
iap_update_bitmap(block_idx);
|
||
}
|
||
|
||
/* 步骤2: 将当前帧数据写入活跃缓冲区 */
|
||
if (write_len > 0)
|
||
{
|
||
memcpy(&iap_buf[iap_active_buf][iap_buf_pos], data, write_len);
|
||
iap_buf_pos += write_len;
|
||
iap_ctx.fw_received += write_len;
|
||
}
|
||
|
||
/* 步骤3: 缓冲区满 或 固件接收完 → 切换缓冲区, 标记待写入 */
|
||
if (iap_buf_pos >= IAP_BUF_SIZE || iap_ctx.fw_received >= iap_ctx.fw_size)
|
||
{
|
||
iap_flush_buf = iap_active_buf;
|
||
iap_flush_len = iap_buf_pos;
|
||
iap_flush_pending = 1;
|
||
|
||
/* 切换到另一个缓冲区 */
|
||
iap_active_buf = 1 - iap_active_buf;
|
||
iap_buf_pos = 0;
|
||
}
|
||
|
||
/* 步骤4: 固件接收完, 将最后一块写入Flash并退出 */
|
||
if (iap_ctx.fw_received >= iap_ctx.fw_size)
|
||
{
|
||
if (iap_flush_pending)
|
||
{
|
||
if (!iap_flush_buffer(iap_ctx.write_addr,
|
||
iap_buf[iap_flush_buf],
|
||
iap_flush_len))
|
||
{
|
||
iap_ctx.state = IAP_STATE_DOWNLOADING;
|
||
iap_send_neg(UDS_TRANSFER_DATA, NRC_GENERAL_REJECT);
|
||
return;
|
||
}
|
||
iap_ctx.write_addr += iap_flush_len;
|
||
iap_flush_pending = 0;
|
||
|
||
/* 断电续传: 更新最后一块的进度位图 */
|
||
uint32_t completed_bytes = iap_ctx.write_addr - IAP_APP_ADDR;
|
||
uint32_t block_idx = (completed_bytes - 1) / IAP_BUF_SIZE;
|
||
iap_update_bitmap(block_idx);
|
||
}
|
||
|
||
iap_ctx.state = IAP_STATE_TRANSFER_DONE;
|
||
iap_send_pos(UDS_TRANSFER_DATA, NULL, 0);
|
||
}
|
||
/* 数据模式中: 不发响应, 提高传输效率 */
|
||
}
|
||
|
||
/* 0x37 传输退出 */
|
||
static void iap_handle_transfer_exit(void)
|
||
{
|
||
if (iap_ctx.state != IAP_STATE_TRANSFER_DONE)
|
||
{
|
||
iap_send_neg(UDS_TRANSFER_EXIT, NRC_CONDITIONS_NOT_CORRECT);
|
||
return;
|
||
}
|
||
|
||
iap_send_pos(UDS_TRANSFER_EXIT, NULL, 0);
|
||
}
|
||
|
||
/* ===================== 阶段3: 校验烧写处理 ===================== */
|
||
|
||
/* 0x31 例程控制(擦除Flash / CRC校验) */
|
||
static void iap_handle_routine_control(uint8_t *data, uint8_t dlc)
|
||
{
|
||
if (dlc < 4)
|
||
{
|
||
iap_send_neg(UDS_ROUTINE_CONTROL, NRC_REQUEST_OUT_OF_RANGE);
|
||
return;
|
||
}
|
||
|
||
/* data[1]=子功能, data[2..3]=例程ID */
|
||
uint16_t routine_id = ((uint16_t)data[2] << 8) | data[3];
|
||
|
||
if (routine_id == 0xFF01) /* EraseFlash: 擦除App区 */
|
||
{
|
||
/* 续传模式(flag=UPGRADING)不擦除, 保留已写入数据 */
|
||
uint32_t saved_flag = flash_read_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_FLAG);
|
||
if (saved_flag != IAP_FLAG_UPGRADING)
|
||
{
|
||
if (!iap_erase_app_pages())
|
||
{
|
||
iap_send_neg(UDS_ROUTINE_CONTROL, NRC_GENERAL_REJECT);
|
||
return;
|
||
}
|
||
}
|
||
iap_ctx.state = IAP_STATE_SESSION;
|
||
uint8_t extra[3] = {data[1], data[2], data[3]};
|
||
iap_send_pos(UDS_ROUTINE_CONTROL, extra, 3);
|
||
}
|
||
else if (routine_id == 0xFF02) /* VerifyCRC: 读回Flash计算CRC32 */
|
||
{
|
||
/* CRC32校验: 按字节计算(与Python binascii.crc32一致) */
|
||
uint32_t calc_crc = 0xFFFFFFFFU;
|
||
uint32_t addr = IAP_APP_ADDR;
|
||
|
||
while (addr < IAP_APP_ADDR + iap_ctx.fw_size)
|
||
{
|
||
uint8_t byte = *(volatile uint8_t *)addr;
|
||
calc_crc ^= byte;
|
||
for (uint8_t i = 0; i < 8; i++)
|
||
{
|
||
if (calc_crc & 1)
|
||
calc_crc = (calc_crc >> 1) ^ 0xEDB88320U;
|
||
else
|
||
calc_crc >>= 1;
|
||
}
|
||
addr++;
|
||
}
|
||
calc_crc ^= 0xFFFFFFFFU;
|
||
|
||
iap_ctx.state = IAP_STATE_SESSION;
|
||
|
||
/* 正响应: 0x71 + sub + routine_id + CRC32(4字节大端) */
|
||
uint8_t extra[7];
|
||
extra[0] = data[1]; /* 子功能回显 */
|
||
extra[1] = data[2]; /* RoutineID高字节 */
|
||
extra[2] = data[3]; /* RoutineID低字节 */
|
||
extra[3] = (uint8_t)(calc_crc >> 24);
|
||
extra[4] = (uint8_t)(calc_crc >> 16);
|
||
extra[5] = (uint8_t)(calc_crc >> 8);
|
||
extra[6] = (uint8_t)(calc_crc);
|
||
iap_send_pos(UDS_ROUTINE_CONTROL, extra, 7);
|
||
}
|
||
else
|
||
{
|
||
iap_send_neg(UDS_ROUTINE_CONTROL, NRC_REQUEST_OUT_OF_RANGE);
|
||
}
|
||
}
|
||
|
||
/* 0x11 ECU复位(跳转APP) */
|
||
static void iap_handle_ecu_reset(uint8_t *data, uint8_t dlc)
|
||
{
|
||
if (dlc < 2)
|
||
{
|
||
iap_send_neg(UDS_ECU_RESET, NRC_REQUEST_OUT_OF_RANGE);
|
||
return;
|
||
}
|
||
|
||
/* 正响应 */
|
||
uint8_t sub = data[1];
|
||
iap_send_pos(UDS_ECU_RESET, &sub, 1);
|
||
|
||
if (data[1] == 0x01) /* 硬复位 */
|
||
{
|
||
/* CRC校验通过后上位机发0x11, 写COMPLETE标志 */
|
||
iap_write_flag(IAP_FLAG_COMPLETE);
|
||
HAL_Delay(100); /* 确保CAN响应帧发送完成 */
|
||
/* 直接跳转到App, 不依赖Bootloader */
|
||
iap_jump_to_app(IAP_APP_ADDR);
|
||
/* 跳转失败则复位 */
|
||
NVIC_SystemReset();
|
||
}
|
||
}
|
||
|
||
/* ===================== 公开函数 ===================== */
|
||
|
||
void iap_init(void)
|
||
{
|
||
memset(&iap_ctx, 0, sizeof(iap_ctx));
|
||
iap_ctx.state = IAP_STATE_IDLE;
|
||
}
|
||
|
||
/* 判断当前是否运行在App地址(同一份代码, 通过链接地址区分Bootloader/App) */
|
||
uint8_t iap_is_app_mode(void)
|
||
{
|
||
return ((uint32_t)&iap_process_frame >= IAP_APP_ADDR) ? 1 : 0;
|
||
}
|
||
|
||
void iap_process_frame(uint32_t id, uint8_t *data, uint8_t dlc)
|
||
{
|
||
if (id != IAP_CAN_RX_ID || dlc == 0)
|
||
return;
|
||
|
||
/* 数据模式: 每帧8字节纯数据, 不解析SID */
|
||
if (iap_ctx.state == IAP_STATE_DATA_MODE)
|
||
{
|
||
iap_handle_data_frame(data, dlc);
|
||
return;
|
||
}
|
||
|
||
/* 命令模式: 解析SID */
|
||
uint8_t sid = data[0];
|
||
|
||
switch (sid)
|
||
{
|
||
case UDS_SESSION_CONTROL: /* 0x10 进入会话 */
|
||
iap_handle_session(data, dlc);
|
||
break;
|
||
|
||
case UDS_READ_DATA: /* 0x22 读版本/握手 */
|
||
iap_handle_read_data(data, dlc);
|
||
break;
|
||
|
||
case UDS_SECURITY_ACCESS: /* 0x27 安全访问 */
|
||
iap_handle_security(data, dlc);
|
||
break;
|
||
|
||
case UDS_REQUEST_DOWNLOAD: /* 0x34 请求下载 */
|
||
iap_handle_request_download(data, dlc);
|
||
break;
|
||
|
||
case UDS_TRANSFER_DATA: /* 0x36 开始传输 */
|
||
iap_handle_transfer_data(data, dlc);
|
||
break;
|
||
|
||
case UDS_TRANSFER_EXIT: /* 0x37 传输退出 */
|
||
iap_handle_transfer_exit();
|
||
break;
|
||
|
||
case UDS_ROUTINE_CONTROL: /* 0x31 校验烧写 */
|
||
iap_handle_routine_control(data, dlc);
|
||
break;
|
||
|
||
case UDS_ECU_RESET: /* 0x11 跳转APP */
|
||
iap_handle_ecu_reset(data, dlc);
|
||
break;
|
||
|
||
default:
|
||
iap_send_neg(sid, NRC_SERVICE_NOT_SUPPORTED);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void iap_jump_to_app(uint32_t app_addr)
|
||
{
|
||
/* 检查栈顶地址是否合法 */
|
||
if ((*(volatile uint32_t *)app_addr & 0xFFFE0000) != 0x20000000)
|
||
return;
|
||
#if 0
|
||
// __disable_irq();
|
||
|
||
// HAL_DeInit();
|
||
// SysTick->CTRL = 0;
|
||
// NVIC->ICER[0] = 0xFFFFFFFF;
|
||
// NVIC->ICPR[0] = 0xFFFFFFFF;
|
||
|
||
// SCB->VTOR = app_addr;
|
||
|
||
// uint32_t app_sp = *(volatile uint32_t *)app_addr;
|
||
// uint32_t app_pc = *(volatile uint32_t *)(app_addr + 4);
|
||
|
||
// __set_MSP(app_sp);
|
||
// __set_PSP(app_sp);
|
||
|
||
// __enable_irq();
|
||
// ((void (*)(void))app_pc)();
|
||
#else
|
||
__set_FAULTMASK(1);
|
||
// 或者使用 __disable_irq();
|
||
// 3. 执行软件复位[reference:13]
|
||
HAL_NVIC_SystemReset();
|
||
#endif
|
||
}
|
||
|
||
uint32_t iap_check_flag(void)
|
||
{
|
||
return *(volatile uint32_t *)IAP_PARAM_ADDR;
|
||
}
|
||
|
||
uint8_t iap_write_flag(uint32_t flag)
|
||
{
|
||
if (!flash_erase_page(IAP_PARAM_ADDR))
|
||
return 0;
|
||
return flash_write_word(IAP_PARAM_ADDR, flag);
|
||
}
|
||
|
||
/* ===================== 编程请求(APP→Bootloader通信) ===================== */
|
||
|
||
// uint8_t iap_check_prog_request(void)
|
||
// {
|
||
// /* 直接读Flash, 不需要HAL初始化, 可在HAL_Init之前调用 */
|
||
// return (*(volatile uint32_t *)(IAP_PARAM_ADDR + IAP_PARAM_OFF_PROG_REQ)
|
||
// == IAP_PROG_REQUEST_MAGIC) ? 1 : 0;
|
||
// }
|
||
|
||
uint8_t iap_set_prog_request(void)
|
||
{
|
||
/* APP收到0x10 0x02时调用: 写0x12121212到参数区偏移0x10
|
||
* 该位置擦除后为0xFFFFFFFF, 写0x12121212是1→0, 合法 */
|
||
return flash_write_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_PROG_REQ,
|
||
IAP_PROG_REQUEST_MAGIC);
|
||
}
|
||
|
||
|
||
|
||
uint8_t iap_clear_prog_request(void)
|
||
{
|
||
/* Bootloader停留后调用: 擦除参数页清除编程请求
|
||
* Flash只能1→0, 无法直接写0xFFFFFFFF, 必须擦除
|
||
* 擦除后所有参数区数据恢复0xFFFFFFFF, 后续IAP会重新初始化 */
|
||
uint32_t prog_req = flash_read_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_PROG_REQ);
|
||
if(prog_req == IAP_PROG_REQUEST_MAGIC){
|
||
flash_write_word_root(IAP_PARAM_ADDR + IAP_PARAM_OFF_PROG_REQ,
|
||
IAP_PROG_CLEAR_MAGIC);
|
||
osDelay(100);
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
uint8_t iap_erase_app(void)
|
||
{
|
||
return iap_erase_app_pages();
|
||
}
|
||
|
||
uint32_t iap_crc32(const uint8_t *data, uint32_t len)
|
||
{
|
||
uint32_t crc = 0xFFFFFFFFU;
|
||
for (uint32_t i = 0; i < len; i++)
|
||
{
|
||
crc ^= data[i];
|
||
for (uint8_t j = 0; j < 8; j++)
|
||
{
|
||
if (crc & 1)
|
||
crc = (crc >> 1) ^ 0xEDB88320U;
|
||
else
|
||
crc >>= 1;
|
||
}
|
||
}
|
||
return crc ^ 0xFFFFFFFFU;
|
||
}
|
||
|
||
iap_ctx_t *iap_get_ctx(void)
|
||
{
|
||
return &iap_ctx;
|
||
}
|
||
|
||
uint32_t iap_resume_check(void)
|
||
{
|
||
/* 上电时检查是否有未完成的升级
|
||
* 返回续传偏移量(0=无续传或全新升级, >0=从该偏移续传) */
|
||
uint32_t flag = iap_check_flag();
|
||
|
||
if (flag != IAP_FLAG_UPGRADING)
|
||
return 0;
|
||
|
||
uint32_t fw_size = flash_read_word(IAP_PARAM_ADDR + IAP_PARAM_OFF_FW_SIZE);
|
||
if (fw_size == 0 || fw_size == 0xFFFFFFFFU || fw_size > IAP_APP_SIZE)
|
||
return 0;
|
||
|
||
return iap_calc_offset(fw_size);
|
||
}
|