diff --git a/apps/chrg/applications/thread/chrg_comm.c b/apps/chrg/applications/thread/chrg_comm.c index 4796e4f..89f0e1e 100644 --- a/apps/chrg/applications/thread/chrg_comm.c +++ b/apps/chrg/applications/thread/chrg_comm.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_comm.c 完成日期 : -当前版本号 : V1.1.15 +当前版本号 : V1.1.19 主要功能 : 实现对外通讯协议 MODBUS 服务端,以独立线程等待处理响应请求数据。 版本历史 : 创建原始版本 说明 : @@ -27,7 +27,7 @@ #include "stm32f4xx_hal.h" #define HOLD_REG_FIRMWARE_VERSION 0x0300U /* 协议板固件版本,只读 */ -#define CHRG_FIRMWARE_VERSION 0x011FU /* 当前协议板版本:V1.1.15 */ +#define CHRG_FIRMWARE_VERSION 0x0123U /* 当前协议板版本:V1.1.19 */ rt_uint8_t ucDiscInBuf[(TOTAL_DISC_REGS+BITS_UCHAR-1)/BITS_UCHAR]; rt_uint8_t ucCoilBuf[(TOTAL_COIL_REGS+BITS_UCHAR-1)/BITS_UCHAR]; @@ -618,7 +618,7 @@ eMBException funcReadHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, /* * 协议板版本号使用独立的只读地址0x0300,不占用现有通道保持寄存器空间。 - * 当前版本V1.1.15编码为0x011F,Modbus响应按高字节在前返回01 1F。 + * 当前版本V1.1.19编码为0x0123,Modbus响应按高字节在前返回01 23。 * 该特殊地址只支持一次读取一个寄存器,避免与普通连续寄存器混用。 */ if (regAddress == HOLD_REG_FIRMWARE_VERSION) { @@ -2589,7 +2589,13 @@ static eMBException chrg_comm_apply_sink_effect(rt_uint8_t ch, protocol_voltage_only = diff_info.pro_voltage_changed && !full_handshake_change && !protocol_current_change; von_changed = (pReal->sink.volt_on != pShadow->sink.volt_on); - if ((sink_work == 0x01) && (old_on_work == 1U) && von_changed) { + /* 完整握手或协议软卸载会先清理本通道批次,V_ON由对应状态机在 + * WORK_STOP之后重新加入;普通参数变化仍在这里立即下发。 */ + if ((sink_work == 0x01) && (old_on_work == 1U) && von_changed && + !full_handshake_change && !protocol_voltage_only && + !protocol_current_change && + (pReal->sink.test.test_mode != SINK_TEST_SHORT) && + (pShadow->sink.test.test_mode != SINK_TEST_SHORT)) { rt_uint16_t reg = Eload_Start_V_ON; rt_uint16_t val = (pShadow->sink.volt_on <= 500U) ? 500U : pShadow->sink.volt_on; diff --git a/apps/chrg/applications/thread/chrg_comm.h b/apps/chrg/applications/thread/chrg_comm.h index 76e30ec..ae6882f 100644 --- a/apps/chrg/applications/thread/chrg_comm.h +++ b/apps/chrg/applications/thread/chrg_comm.h @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_comm.h 完成日期 : -当前版本号 : V1.1.15 +当前版本号 : V1.1.19 主要功能 : 实现对外通讯协议 MODBUS 服务端,以独立线程等待处理响应请求数据。 版本历史 : 创建原始版本 说明 : diff --git a/apps/chrg/applications/thread/chrg_north.h b/apps/chrg/applications/thread/chrg_north.h index a67152e..0cc23a2 100644 --- a/apps/chrg/applications/thread/chrg_north.h +++ b/apps/chrg/applications/thread/chrg_north.h @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_north.h 完成日期 : -当前版本号 : V1.1.14 +当前版本号 : V1.1.19 主要功能 : 实现北向通信交互,包括接收命令解析,发送请求命令,切换通路。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -28,7 +28,10 @@ #endif /* 南向COM批次中单通道连续收到有效回码后的最大发送条数。 */ -#define SOU_COM_BURST_MAX 10U +#define SOU_COM_BURST_MAX 8U + +/* 自动停止带载后,电压恢复时需要连续确认的新P1采样帧数。 */ +#define SINK_LOAD_RESTART_CONFIRM_COUNT 3U #define THR_NAME_NORTH "thr.nor" #define DEV_NAME_NORTH "uart3" diff --git a/apps/chrg/applications/utils/chrg_sink.c b/apps/chrg/applications/utils/chrg_sink.c index fb28740..8c52063 100644 --- a/apps/chrg/applications/utils/chrg_sink.c +++ b/apps/chrg/applications/utils/chrg_sink.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_sink.c 完成日期 : -当前版本号 : V1.1.14 +当前版本号 : V1.1.19 主要功能 : 北向数据sink请求数据填充及发送。 版本历史 : 创建原始版本 说明 : @@ -41,10 +41,12 @@ static rt_tick_t g_sink_unload_query_tick[TOTAL_SOU_CHS] = {0}; static rt_uint8_t g_sink_unload_confirm_count[TOTAL_SOU_CHS] = {0}; /* 记录各通道短路测试最近一次WORK_START的发送时间。 */ static rt_tick_t g_sink_short_start_tick[TOTAL_SOU_CHS] = {0}; -#if SINK_VON_VOLTAGE_SOURCE == SINK_VON_VOLTAGE_FROM_PROTOCOL -/* 只消费新的协议板0x20电压回码,避免同一帧重复计入稳定次数。 */ +/* 只消费新的V_On电压采样,避免同一帧重复计入稳定确认。 */ static rt_uint32_t g_sink_von_voltage_seq[TOTAL_SOU_CHS] = {0}; -#endif +/* 协议切换期间清零,避免旧电压样本累计成启动确认。 */ +static rt_uint8_t g_sink_von_stable_count[TOTAL_SOU_CHS] = {0}; +/* 自动停止带载后的恢复确认计数,独立于首次握手的电压确认计数。 */ +static rt_uint8_t g_sink_load_restart_confirm_count[TOTAL_SOU_CHS] = {0}; enum { SINK_UNLOAD_NEXT_RUNNING = 0, @@ -447,9 +449,15 @@ static int chrg_sink_work_load_stop(eIDX_SOU_CH idx, if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { return -1; } + /* 自动停止优先于同通道残留启动批次,禁止STOP后继续发送WORK_START。 */ + if (chrg_sou_com_batch_cancel(idx) != 0) { + return -1; + } if (chrg_sou_com_batch_submit(idx, ®, &val, 1U) != 0) { return -1; } + g_sink_von_stable_count[idx] = 0U; + g_sink_load_restart_confirm_count[idx] = 0U; pSW->sink.work_mode = SINK_WORK_LOAD_STANDBY; return 0; } @@ -665,15 +673,23 @@ static int chrg_sink_work_switch(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) *****************************************************************/ static int chrg_sink_work_protocol_unload(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) { - rt_uint16_t reg = REG_WORK; - rt_uint16_t value = WORK_STOP; + rt_uint16_t regs[2] = {REG_WORK, Eload_Start_V_ON}; + rt_uint16_t values[2]; - if (chrg_sou_com_batch_submit(idx, ®, &value, 1U) != 0) { + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + values[0] = WORK_STOP; + values[1] = (pSW->sink.volt_on <= 500U) ? 500U : pSW->sink.volt_on; + + if (chrg_sou_com_batch_submit(idx, regs, values, 2U) != 0) { return -1; } /* WORK_STOP回码只确认命令接收;还需连续读取两帧合格的实际电流。 */ chrg_sink_unload_confirm_reset(idx); + g_sink_von_stable_count[idx] = 0U; + g_sink_load_restart_confirm_count[idx] = 0U; pSW->sink.work_mode = SINK_WORK_UNLOAD_WAIT; return 0; } @@ -683,31 +699,35 @@ static int chrg_sink_work_protocol_unload(eIDX_SOU_CH idx, struct chrg_switch_t 函数描述: 执行协议或CC完整重握手前的南向安全断开和副继电器恢复 输入参数: idx:南向功率板通道 pSW:当前通道控制结构体 输出参数: 命令提交成功后进入SINK_WORK_HANDSHAKE_WAIT -返回说明: 0:四条命令提交成功 <0:参数或命令提交失败 -其它说明: 命令顺序固定为WORK_STOP、CURRENT=1000mA、ALL_REL_OFF、FU_REL_ON; - WORK_STOP已关闭电子负载,1000mA仅为功率板停止阶段保存的电流设定值; +返回说明: 0:两条命令提交成功 <0:参数或命令提交失败 + 其它说明: 命令顺序固定为WORK_STOP、Eload_Start_V_ON; + WORK_STOP已关闭电子负载,V_ON随后更新到功率板; 主副继电器全部断开并确认回码后再单独打开副继电器; 北向Sink继电器保持接通,低电流确认完成后才允许重新握手 *****************************************************************/ static int chrg_sink_work_handshake_prepare(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) { - rt_uint16_t reg = REG_WORK; - rt_uint16_t value = WORK_STOP; + rt_uint16_t regs[2] = {REG_WORK, Eload_Start_V_ON}; + rt_uint16_t values[2]; if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { return -1; } + values[0] = WORK_STOP; + values[1] = (pSW->sink.volt_on <= 500U) ? 500U : pSW->sink.volt_on; /* 新CC/协议事务必须抢占本通道尚未发送的旧启动和设定命令。 */ if (chrg_sou_com_batch_cancel(idx) != 0) { return -1; } - if (chrg_sou_com_batch_submit(idx, ®, &value, 1U) != 0) { + if (chrg_sou_com_batch_submit(idx, regs, values, 2U) != 0) { return -1; } /* 协议或CC切换先停止电子负载,连续2次确认低电流后再操作继电器。 */ chrg_sink_unload_confirm_reset(idx); + g_sink_von_stable_count[idx] = 0U; + g_sink_load_restart_confirm_count[idx] = 0U; pSW->sink.work_mode = SINK_WORK_HANDSHAKE_WAIT; return 0; } @@ -719,7 +739,7 @@ static int chrg_sink_work_handshake_prepare(eIDX_SOU_CH idx, full_handshake:RT_TRUE为完整重握手 RT_FALSE为协议参数软卸载 输出参数: 确认成功后将sink.work_mode切换为SINK_WORK_WAIT 返回说明: 0:继续等待或确认成功 <0:状态查询或优先量测请求失败 -其它说明: 软卸载只要求WORK_STOP收到匹配回码后建立量测基线;完整重握手 + 其它说明: 软卸载要求WORK_STOP和V_ON收到匹配回码后建立量测基线;完整重握手 还要求CURRENT=1000mA、ALL_REL_OFF和FU_REL_ON回码成功,但保持北向Sink继电器接通 *****************************************************************/ static int chrg_sink_work_current_wait(eIDX_SOU_CH idx, @@ -831,13 +851,14 @@ static int chrg_sink_TEST_DYNA_ON(rt_uint8_t ch, struct chrg_switch_t *pSW) *****************************************************************/ static int chrg_sink_TEST_SHORT_ON(rt_uint8_t ch, struct chrg_switch_t *pSW) { - rt_uint16_t regs[3] = {ModbusRTU_ShortTest_Hold_Time,ModbusRTU_ShortTest_CMD_MODE_FLAG, REG_WORK}; - rt_uint16_t vals[3] = {0,0,0}; + rt_uint16_t regs[4] = {Eload_Start_V_ON, ModbusRTU_ShortTest_Hold_Time, + ModbusRTU_ShortTest_CMD_MODE_FLAG, REG_WORK}; + rt_uint16_t vals[4] = {SINK_SHORT_TEST_VON_MV, 0, 0, 0}; /* 外部工程固定短路测试保持60秒且不带载。 */ - vals[0] = 60000; - vals[1] = ModbusRTU_DYNA_CMD_MODE_RUN_NO_CURR; - vals[2] = WORK_START; - if (chrg_sou_com_batch_submit((eIDX_SOU_CH)ch, regs, vals, 3) == 0) { + vals[1] = 60000; + vals[2] = ModbusRTU_DYNA_CMD_MODE_RUN_NO_CURR; + vals[3] = WORK_START; + if (chrg_sou_com_batch_submit((eIDX_SOU_CH)ch, regs, vals, 4) == 0) { g_sink_short_start_tick[ch] = rt_tick_get(); return 0; } @@ -897,10 +918,10 @@ static rt_uint16_t chrg_sink_von_voltage_get(const struct chrg_switch_t *pSW) int chrg_sink_volt_judge(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) { rt_uint8_t stable_confirm_count; + rt_uint32_t voltage_seq; static rt_uint8_t volt_low[TOTAL_SOU_CHS] = {0}; static rt_uint8_t no_curr[TOTAL_SOU_CHS] = {0}; static rt_uint8_t volt_set_ok[TOTAL_SOU_CHS] = {0}; - static rt_uint8_t volt_stable_count[TOTAL_SOU_CHS] = {0}; static rt_tick_t running_start_tick[TOTAL_SOU_CHS] = {0}; rt_uint16_t von_voltage; @@ -908,18 +929,32 @@ int chrg_sink_volt_judge(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) return -1; } #if SINK_VON_VOLTAGE_SOURCE == SINK_VON_VOLTAGE_FROM_PROTOCOL - if (pSW->Sink_voltage_seq == g_sink_von_voltage_seq[idx]) { + voltage_seq = pSW->Sink_voltage_seq; +#else + voltage_seq = chrg_eload_get_sample_seq(idx); +#endif + if (voltage_seq == g_sink_von_voltage_seq[idx]) { + return chrg_sink_work(idx, pSW); + } + g_sink_von_voltage_seq[idx] = voltage_seq; + /* 卸载和协议重配尚未完成时,旧电压只能用于推进安全流程,不能触发WORK_START。 */ + if ((pSW->sink.work_mode == SINK_WORK_UNLOAD_WAIT) || + (pSW->sink.work_mode == SINK_WORK_HANDSHAKE_WAIT) || + ((pSW->sink.work_mode != SINK_WORK_RUNING) && + (pSW->sink.work_mode != SINK_WORK_LOAD_STANDBY) && + (pSW->sub != IDX_GET_SINK_VC))) { + g_sink_von_stable_count[idx] = 0U; + g_sink_load_restart_confirm_count[idx] = 0U; return chrg_sink_work(idx, pSW); } - g_sink_von_voltage_seq[idx] = pSW->Sink_voltage_seq; -#endif von_voltage = chrg_sink_von_voltage_get(pSW); stable_confirm_count = chrg_sink_voltage_confirm_count( pSW->sink.pro_loadv); if ((pSW->sink.On_work == 1) && (pSW->sink.test.test_mode == 0)) { if (von_voltage <= WORK_MIN_VOLT) { - volt_stable_count[idx] = 0; + g_sink_von_stable_count[idx] = 0U; + g_sink_load_restart_confirm_count[idx] = 0U; if (volt_set_ok[idx] == 1 && ++volt_low[idx] >= 50) { volt_set_ok[idx] = 0; no_curr[idx] = 0; @@ -933,47 +968,58 @@ int chrg_sink_volt_judge(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) pSW->change_flag = 0; if (pSW->sink.work_mode == SINK_WORK_LOW_MONITOR) { /* 副继电器恢复后首次重新检测到输入电压,只启动协议重握手。 */ - volt_stable_count[idx] = 0; + g_sink_von_stable_count[idx] = 0U; no_curr[idx] = 0; pSW->sink.work_mode = SINK_WORK_WAIT; } else if ((pSW->sink.work_mode == SINK_WORK_LOW) || (pSW->sink.work_mode == SINK_WORK_LOW_AUX_WAIT)) { /* 掉电安全批次完成前,不得进入RUNING。 */ - volt_stable_count[idx] = 0; + g_sink_von_stable_count[idx] = 0U; no_curr[idx] = 0; } else if (von_voltage > pSW->sink.volt_on) { if ((pSW->sink.work_mode != SINK_WORK_RUNING) && (pSW->sink.work_mode != SINK_WORK_LOAD_STANDBY)) { - if (volt_stable_count[idx] < stable_confirm_count) { - volt_stable_count[idx]++; + if (g_sink_von_stable_count[idx] < stable_confirm_count) { + g_sink_von_stable_count[idx]++; } - if (volt_stable_count[idx] >= stable_confirm_count) { + if (g_sink_von_stable_count[idx] >= stable_confirm_count) { /* 连续达到当前目标对应的确认帧数后进入RUNING。 */ - volt_stable_count[idx] = 0; + g_sink_von_stable_count[idx] = 0U; pSW->sink.work_mode = SINK_WORK_RUNING; running_start_tick[idx] = rt_tick_get(); no_curr[idx] = 0; } } else if (pSW->sink.work_mode == SINK_WORK_LOAD_STANDBY) { - volt_stable_count[idx] = 0; no_curr[idx] = 0; if (chrg_sink_load_should_start(pSW)) { - /* 因输入电压不足停止带载后,电压恢复时重新进入稳定确认。 */ - pSW->sink.work_mode = SINK_WORK_WAIT; + /* 自动停止后只接受新的电压采样;达到恢复确认帧数才允许重启。 */ + if (g_sink_load_restart_confirm_count[idx] < + SINK_LOAD_RESTART_CONFIRM_COUNT) { + g_sink_load_restart_confirm_count[idx]++; + } + if (g_sink_load_restart_confirm_count[idx] >= + SINK_LOAD_RESTART_CONFIRM_COUNT) { + g_sink_load_restart_confirm_count[idx] = 0U; + pSW->sink.work_mode = SINK_WORK_RUNING; + running_start_tick[idx] = rt_tick_get(); + } + } else { + g_sink_load_restart_confirm_count[idx] = 0U; } } else if ((pSW->Now_current > 200) || (pSW->sink.loadc < 200)) { - volt_stable_count[idx] = 0; + g_sink_von_stable_count[idx] = 0U; no_curr[idx] = 0; } else if ((rt_tick_t)(rt_tick_get() - running_start_tick[idx]) >= rt_tick_from_millisecond(SINK_CURRENT_START_GRACE_MS)) { - volt_stable_count[idx] = 0; + g_sink_von_stable_count[idx] = 0U; if (++no_curr[idx] >= 2) { pSW->sink.work_mode = SINK_WORK_WAIT; no_curr[idx] = 0; } } } else { - volt_stable_count[idx] = 0; + g_sink_von_stable_count[idx] = 0U; + g_sink_load_restart_confirm_count[idx] = 0U; no_curr[idx] = 0; /* 配置电压源低于负载设置的V_On时,立即停止电子负载。 */ if (pSW->sink.work_mode == SINK_WORK_RUNING) { @@ -985,7 +1031,7 @@ int chrg_sink_volt_judge(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) volt_low[idx] = 0; no_curr[idx] = 0; volt_set_ok[idx] = 0; - volt_stable_count[idx] = 0; + g_sink_von_stable_count[idx] = 0U; } return chrg_sink_work(idx, pSW); } diff --git a/apps/chrg/applications/utils/chrg_sink.h b/apps/chrg/applications/utils/chrg_sink.h index 88bfee7..9c6c009 100644 --- a/apps/chrg/applications/utils/chrg_sink.h +++ b/apps/chrg/applications/utils/chrg_sink.h @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_sink.c 完成日期 : -当前版本号 : V1.1.14 +当前版本号 : V1.1.19 主要功能 : 北向数据sink请求数据填充及发送,命令字定义。 版本历史 : 创建原始版本 说明 : @@ -23,6 +23,8 @@ struct chrg_switch_t; #define SINK_TEST_DYNA 3 /* 短路测试期间按该周期补发WORK_START,防止功率板因超时停止。 */ #define SINK_SHORT_START_INTERVAL_MS 50U +/* 短路测试不受普通V_ON门槛限制,功率板使用最低启动电压。 */ +#define SINK_SHORT_TEST_VON_MV 500U //短路测试结构体 struct test_short { rt_uint16_t hold_time; // 短路测试时间 @@ -212,8 +214,8 @@ enum { /* Sink电压稳定确认参数,供chrg_sink_volt_judge使用。 */ #define SINK_VOLT_5V_TARGET_MV 5000U -#define SINK_VOLT_CONFIRM_NORMAL_COUNT 5U -#define SINK_VOLT_CONFIRM_5V_COUNT 15U +#define SINK_VOLT_CONFIRM_NORMAL_COUNT 3U +#define SINK_VOLT_CONFIRM_5V_COUNT 12U #define SINK_CURRENT_START_GRACE_MS 200U /* 主继电器闭合后的0V持续监控:由北向轮询线程每轮调用,掉电立即转掉电保护。 */ diff --git a/apps/chrg/codex_von_restart_verify.log b/apps/chrg/codex_von_restart_verify.log new file mode 100644 index 0000000..d573f4b --- /dev/null +++ b/apps/chrg/codex_von_restart_verify.log @@ -0,0 +1,21 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_north_pkg.c... +compiling chrg_vcom.c... +compiling chrg_thread.c... +compiling chrg_lcd.c... +compiling chrg_rel.c... +compiling chrg_eload.c... +compiling chrg_south.c... +compiling chrg_sink.c... +compiling chrg_north.c... +compiling chrg_source.c... +compiling chrg_roll_nor.c... +compiling chrg_roll_sou.c... +compiling chrg_comm.c... +linking... +Program Size: Code=151456 RO-data=15928 RW-data=9356 ZI-data=16052 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:05 diff --git a/docs/修改日志.md b/docs/修改日志.md index c6b2ce1..469c7b1 100644 --- a/docs/修改日志.md +++ b/docs/修改日志.md @@ -1,5 +1,60 @@ # 修改日志 +## V1.1.19 - 2026-09-10 + +### 短路测试绕过 V_ON 门槛 + +- 短路测试启动批次增加 `Eload_Start_V_ON=500mV`,保证5V输入时不因用户设置的6V V_ON而拒绝短路启动。 +- 短路测试期间忽略普通 V_ON 更新,保持功率板使用500mV短路启动门槛。 +- 退出短路测试时由初始化流程恢复用户设定的 V_ON。 +- 协议板版本号更新为 `V1.1.19`,固件版本寄存器更新为 `0x0123`。 + +### 验证记录 + +- 待硬件验证:V_ON=6V、输入PD5V时,短路模式仍能启动并按周期发送 `WORK_START`。 +- 待硬件验证:退出短路模式后,功率板恢复用户设定的 V_ON。 + +## V1.1.18 - 2026-09-10 + +### 修复 CC 切换时 V_ON 丢失 + +- 修复 V_ON 与 CC 线同时修改时,V_ON 命令先入队后被完整握手清队列取消的问题。 +- 协议软卸载和完整握手统一按 `WORK_STOP -> Eload_Start_V_ON` 顺序下发。 +- 握手或命令重试时会重新携带当前 V_ON,避免功率板继续使用旧阈值。 +- 协议板版本号更新为 `V1.1.18`,固件版本寄存器更新为 `0x0122`。 + +### 验证记录 + +- 待硬件验证:同时修改 V_ON 与 CC 线,确认功率板收到 `Eload_Start_V_ON`。 +- 待硬件验证:确认 CC 切换失败重试、协议电压切换和其他通道调度正常。 + +## V1.1.17 - 2026-09-10 + +### 协议切换期间禁止误启动 + +- 将 V_On 稳定确认计数提升为通道级状态,并在协议软卸载、完整握手和自动停止时清零。 +- `UNLOAD_WAIT`、`HANDSHAKE_WAIT` 以及协议配置未完成前,电压采样只推进安全流程,不允许进入 `RUNING` 或发送 `WORK_START`。 +- 协议/模式配置完成并回到 `IDX_GET_SINK_VC` 后,才重新累计 V_On 稳定确认帧,避免停止后约40ms因旧电压再次启动。 +- 协议板版本号更新为 `V1.1.17`,固件版本寄存器更新为 `0x0121`。 + +### 验证记录 + +- 待硬件验证:协议电压切换时确认 `WORK_STOP` 后、协议配置完成前不再出现 `WORK_START`。 +- 待硬件验证:确认协议切换、自动停止恢复和三个通道并行调度不受影响。 + +## V1.1.16 - 2026-09-09 + +### Sink 自动停止后的恢复保护 + +- 新增 `SINK_LOAD_RESTART_CONFIRM_COUNT`,位于 `chrg_north.h`,当前设为 `2U`。 +- 功率板电压作为 V_On 判定源时,仅消费新的 P1 采样帧,旧采样不再重复累计稳定次数。 +- 自动停止带载时取消本通道残留南向批次,再单独下发 `WORK_STOP`,避免残留 `WORK_START` 在停止后执行。 +- 自动恢复带载前必须收到连续 `2` 帧新的、满足 `V_On` 的电压采样。 + +### 验证记录 + +- 待硬件验证:触发 CH1 自动停止后,确认停止后 2 帧新P1到达前不再出现 `WORK_START`;同时确认其它通道南向命令正常调度。 + ## V1.1.15 - 2026-09-09 ### 运行中 V_On 更新 diff --git a/docs/协议板Boot_UPK打包规则.md b/docs/协议板Boot_UPK打包规则.md new file mode 100644 index 0000000..2829092 --- /dev/null +++ b/docs/协议板Boot_UPK打包规则.md @@ -0,0 +1,84 @@ +# 协议板 Boot:UPK 打包规则 + +## 1. 适用范围 + +本文用于实现协议板 App 固件的 `.upk` 打包和解析工具,适配 `apps/boot` 当前 Boot。传输层 YMODEM 规则见[协议板Boot_YMODEM上位机实现规则.md](协议板Boot_YMODEM上位机实现规则.md)。 + +## 2. 1K 文件布局 + +当前 Boot 收到第一个文件数据帧时,只取其中前 96 字节作为 UPK 头,**会忽略该帧其余数据**。因此打包文件必须使用固定 1024 字节首数据块: + +```text +输出 .upk 文件 +偏移 0x000 ~ 0x05F : 96 字节 UPK 逻辑头 +偏移 0x060 ~ 0x3FF : 928 字节 0x00 填充 +偏移 0x400 起 : package body(未加密 BIN 或 AES 加密后的 body) +``` + +- `.upk` 实际文件大小:`1024 + pkg_size`。 +- YMODEM 元数据包的文件大小必须为上述实际文件大小。 +- 上位机必须从偏移 0 开始以 `STX/1024` 发送;第一个 STX 是头区,真正 body 从第二个 STX 开始。 +- YMODEM 最后一帧的 `0x1A` 补齐仅属于传输层,不能计入文件大小、`raw_size` 或 `pkg_size`。 + +界面中的“表头尺寸 `1024 (ymodem1k)`”应改名为“YMODEM 首数据块/头区尺寸”,并固定为 1024;UPK 逻辑头固定为 96,不能让用户修改。 + +## 3. 输入、分区与版本 + +| 项目 | 规则 | +| --- | --- | +| 输入文件 | 仅协议板 App 裸 `bin`,例如 `apps/chrg/build/chrg.bin`;拒绝 `.hex`、`.elf`、已有 `.upk`。 | +| `name[4]` | 固定 ASCII `upk\0`。 | +| `part_name[16]` | 仅允许 `app`、`download`、`factory`,ASCII NUL 结尾;协议板常规升级默认 `app`,Boot 会修正为 `download` 后安装到 `app`。 | +| `fw_old_ver[16]` | 目标设备当前版本,前 4 字节为数值 `major, minor, fix, build`,其余 `0x00`。 | +| `fw_new_ver[16]` | 新固件版本,编码同上。 | +| 版本输入 | 界面应分别提供“当前/旧版本”与“新版本”;不能只保留一个版本号,且不得写 ASCII 版本字符串。 | +| `user_string[16]` | ASCII 水印最多 15 字节,NUL 结尾,其余 `0x00`。 | +| `timestamp` | 当前 Unix 时间戳,`uint32` 小端。 | +| 大小限制 | `raw_size <= 262144` 且 `pkg_size <= 262144`,即 App/download 分区各 256KB。 | + +## 4. 加密、大小与 CRC + +1. `raw_body` 为原始 `.bin`:`raw_size = len(raw_body)`,`raw_crc = crc32_boot(raw_body)`。 +2. 未加密:`package_body = raw_body`,`pkg_size = raw_size`,`pkg_crc = raw_crc`,`config[1]=0x00`。 +3. AES:`config[1]=0x01`,使用 AES-256-CBC;Key=`0123456789ABCDEF0123456789ABCDEF`,IV=`0123456789ABCDEF`。加密前 body 必须补齐到 16 字节整数倍;`pkg_size`、`pkg_crc`对应加密后的 body。Key、IV 必须与 Boot 编译配置一致。 +4. `config[0]`、`config[2]`、`config[3]` 固定 `0x00`。 +5. `head_crc = crc32_boot(逻辑头前 92 字节)`;最后 4 字节按小端写入 `head_crc`。 +6. 所有 `uint32` 字段按 STM32 小端序写入。 + +`crc32_boot()` 必须与 `apps/boot/application/func/utils.c` 的 `crc32_step(0xFFFFFFFF, data, size)` 完全一致;不能直接使用未校验的库默认 CRC32。 + +## 5. 96 字节逻辑头字段 + +```text +0x00 name[4] +0x04 config[4] +0x08 fw_old_ver[16] +0x18 fw_new_ver[16] +0x28 user_string[16] +0x38 part_name[16] +0x48 raw_size uint32 little-endian +0x4C pkg_size uint32 little-endian +0x50 timestamp uint32 little-endian +0x54 raw_crc uint32 little-endian +0x58 pkg_crc uint32 little-endian +0x5C head_crc uint32 little-endian +``` + +## 6. 打包完成前的强制自检 + +1. 重新解析刚生成的 `.upk`。 +2. 校验总长度是否为 `1024 + pkg_size`,并检查 `0x60~0x3FF` 全部为 `0x00`。 +3. 校验 `name`、分区名、版本字段、大小限制和 `head_crc`。 +4. 校验 `pkg_crc`;未加密时还必须校验 `raw_crc`。加密时使用相同 Key/IV 解密后校验 `raw_crc`。 +5. 解析页显示 `raw_size`、`pkg_size`、全部 CRC、加密状态、分区、旧/新版本和时间戳;任一项异常时禁止输出成功文件。 + +## 7. 当前样本 + +工程中的 `apps/chrg/chrg.upk` 已验证:实际长度 `155816`,`pkg_size=154792`,两者相差 1024,符合本规则。 + +## 8. 代码依据 + +- `apps/boot/application/include/firmware.h` +- `apps/boot/application/func/firmware.c` +- `apps/boot/application/func/app.c` +- `apps/boot/application/func/utils.c` diff --git a/docs/协议板Boot_YMODEM上位机实现规则.md b/docs/协议板Boot_YMODEM上位机实现规则.md index 1cd9e1f..47da7b4 100644 --- a/docs/协议板Boot_YMODEM上位机实现规则.md +++ b/docs/协议板Boot_YMODEM上位机实现规则.md @@ -16,8 +16,9 @@ | 启动依据 | 等待 Boot 发出 ASCII `C`(`0x43`)后再开始传输 | | Boot 上电等待 | 首次等待主机开始约 5 秒;超时后可能跳转 App | | 传输中的帧间等待 | Boot 约 10 秒;上位机不得使相邻有效帧间隔达到 10 秒 | +| 单帧发送方式 | 一个完整 YMODEM 帧必须连续写入串口;不得把同一帧拆开并在中间产生明显空闲间隔 | -Boot 每约 1 秒发送一次 `C`。若没有收到 `C`,上位机不可直接发送 YMODEM 首帧;应提示设备未进入 Boot,或重新复位/触发其进入 Boot。 +Boot 每约 1 秒发送一次 `C`。若没有收到 `C`,上位机不可直接发送 YMODEM 首帧;应提示设备未进入 Boot,或重新复位/触发其进入 Boot。Boot 的接收层按 UART 收帧处理,不会为上位机重组被空闲间隔拆断的 YMODEM 包。 ## 3. YMODEM 常量与帧格式 @@ -54,7 +55,7 @@ CRC 覆盖 `Data` 的全部固定长度字节(128 或 1024),并按 **高 1. 元数据包必须使用 `SOH`,`PacketNo=0x00`,反码为 `0xFF`。 2. 首个文件数据包使用 `PacketNo=0x01`;每得到 ACK 后序号加 1,按 8 位自然回绕。 -3. 数据包正常使用 `STX`(1024 字节);最后一包不足 1024 字节时仍应补齐到 1024 字节,建议补 `0x1A`。 +3. 本 Boot 的 `.upk` 使用 1K 首数据块布局,因此文件数据包必须使用 `STX`(1024 字节);最后一包不足 1024 字节时仍应补齐到 1024 字节,建议补 `0x1A`。 4. Boot 校验包号、包号反码、帧长度与 CRC。任一校验失败会回复 NAK。 5. 收到 NAK 时,必须重发**原完整帧**,包号不得递增。该 Boot 不将“前一帧重复包”当作成功帧,因此不能依赖重复包自动 ACK。 @@ -79,10 +80,12 @@ protocol_v1.2.upk\0 123456\0 [其余填 0] Boot 在接收数据后按 UPK 格式处理,而不是把它当作裸固件。 - 头部标识必须为 `UPK_IDENTIFIER=0x006B7075`(`"upk"`)。 -- UPK 头为 1 字节对齐、总计 96 字节,包含版本、目标分区、原始/包体大小、CRC 等字段。 -- 工程当前启用 AES-256-CBC 解密;上位机职责仅为原样传输既有打包工具产生的 `.upk`,无需也不得自行改变加密、CRC 或头内容。 +- UPK 逻辑头为 1 字节对齐、总计 96 字节,包含版本、目标分区、原始/包体大小、CRC 等字段。 +- 工程启用了 AES-256-CBC 解密组件,但不强制加密;`config[1]=0` 的未加密包和 `config[1]=1` 的 AES-256-CBC 包均可被当前 Boot 处理。 - Boot 根据 UPK 头确定下载与安装流程;当前配置会先写 download 分区,再更新 app 分区。 +打包输出的 `.upk` 必须符合[协议板Boot_UPK打包规则.md](协议板Boot_UPK打包规则.md),特别是 1024 字节首数据块布局。YMODEM 元数据中的文件大小使用该 `.upk` 的实际字节数。 + ## 7. 完整正常时序 ```text @@ -91,9 +94,9 @@ PC -> Boot: SOH, 00, FF, 元数据, CRC Boot -> PC : ACK Boot -> PC : C (必须等待此 C) -PC -> Boot: STX/SOH, 01, FE, 数据块, CRC +PC -> Boot: STX, 01, FE, 数据块, CRC Boot -> PC : ACK -PC -> Boot: STX/SOH, 02, FD, 数据块, CRC +PC -> Boot: STX, 02, FD, 数据块, CRC Boot -> PC : ACK ... (直至全部 .upk 数据发送完成) @@ -168,15 +171,17 @@ WAIT_END_ACK | 事件 | 上位机处理 | | --- | --- | | 收到 ACK | 仅推进到下一协议状态;数据包 ACK 后才递增包号 | -| 收到 NAK | 立即重发当前完整帧,不改变包号和文件偏移 | -| 单帧等待超时 | 重发当前完整帧;建议最多连续 3 次 | -| 重发次数耗尽 | 发送 CAN 两次,标记升级传输失败 | +| 收到明确 NAK | 重发当前完整帧;建议最多连续 3 次 | +| 等待 ACK 超时 | 标记“传输结果不确定”,停止本次会话;重新进入 Boot 后从元数据包开始完整传输 | +| 明确 NAK 重发耗尽 | 发送 CAN 两次,标记升级传输失败 | | 收到 CAN | 立即停止,标记 Boot 取消升级 | | 用户取消 | 发送 CAN 两次,停止发送后续字节 | | 无 C | 不开始发送,提示设备未进入 Boot 或串口参数/连线错误 | Boot 对单个 CAN 会进入取消流程并回 ACK;上位机仍建议按标准发送两个 CAN,并接受可能出现的 ACK。 +> **当前 Boot 的可靠性限制:** 成功接收一个数据包后,Boot 已把期望包号加一;若该包的 ACK 在串口上丢失,上位机重发同一包会被 Boot 判为“重复包”并回复 NAK,而不是再次回复 ACK。因此“ACK 超时后无条件重发当前包”不能可靠恢复。本文规则可在直连、稳定的串口环境中正常升级;若要支持 ACK 丢失后的可靠自动重发,需要修改 Boot,使其收到“期望包号减一”的重复帧且 CRC 正确时直接回复 ACK、不再次写入数据。 + `WAIT_*_ACK` 的建议超时为 3~10 秒,元数据后的 `WAIT_C_DATA`、结束阶段的 `WAIT_C_END` 建议为 10 秒。上位机可将串口接收设计为持续缓存并按字节解析,忽略调试文本或仅识别 `C/ACK/NAK/CAN` 控制字符,避免因分段到达造成误判。 ## 10. 成功判定与日志