diff --git a/apps/chrg/applications/thread/chrg_comm.c b/apps/chrg/applications/thread/chrg_comm.c index 445bb1d..14387f8 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.26 +当前版本号 : V1.1.31 主要功能 : 实现对外通讯协议 MODBUS 服务端,以独立线程等待处理响应请求数据。 版本历史 : 创建原始版本 说明 : @@ -955,6 +955,41 @@ static rt_uint16_t src_type_lookup(rt_uint16_t reg_val,struct chrg_switch_t *pSW } return 0; } +/***************************************************************** +函数名称: chrg_comm_reg_decode +函数描述: 将上位机分区地址解码为0起始通道号和通道内寄存器偏移 +输入参数: partition:分区高字节 address:分区低字节地址 + ch:通道输出 reg_offset:通道内偏移输出 +输出参数: ch、reg_offset +返回说明: 0:地址有效 <0:分区、地址或参数无效 +其它说明: 统一供0x06和0x10使用,避免两条写入路径采用不同通道算法 +*****************************************************************/ +static int chrg_comm_reg_decode(rt_uint8_t partition, + rt_uint16_t address, eIDX_SOU_CH *ch, rt_uint16_t *reg_offset) +{ + rt_uint16_t regs_per_channel; + + if ((ch == RT_NULL) || (reg_offset == RT_NULL)) { + return -1; + } + if (partition == HOLD_REG_SINK_BASE) { + regs_per_channel = TOTAL_SINK_CH_REGS; + } else if (partition == HOLD_REG_SRC_BASE) { + regs_per_channel = TOTAL_SRC_CH_REGS; + } else if (partition == HOLD_REG_SYS_BASE) { + regs_per_channel = TOTAL_SYS_CH_REGS; + } else { + return -1; + } + if (address >= (regs_per_channel * TOTAL_SOU_CHS)) { + return -1; + } + + *ch = (eIDX_SOU_CH)(address / regs_per_channel); + *reg_offset = address % regs_per_channel; + return 0; +} + /***************************************************************** 函数名称: funcWriteHoldingRegister 函数描述: 写单个保持寄存器(0x06功能码) @@ -963,7 +998,7 @@ static rt_uint16_t src_type_lookup(rt_uint16_t reg_val,struct chrg_switch_t *pSW 输出参数: - 返回说明: MB_EX_NONE=成功, 其他=Modbus异常码 其它说明: 新协议采用标准0x06功能码,寄存器地址区分功能区块: - 0x0000~0x0073 负载, 0x0080~0x0097 电源, 0x00A0~0x00A7 系统 + 高字节选择分区,低字节按每通道50个地址计算通道和寄存器偏移 *****************************************************************/ eMBException funcWriteHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, rt_uint8_t *resFrame, rt_uint16_t *resLen) @@ -975,8 +1010,9 @@ eMBException funcWriteHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, rt_uint8_t regAddress_H = 0; rt_uint16_t sou_value = 0; /* ---- 提取通道号和区块内偏移 ---- */ - eIDX_SOU_CH ch; - rt_uint16_t reg_offset; // 区块内寄存器偏移 + /* 使用越界枚举作为无效初值,禁止地址校验失败后误用随机通道。 */ + eIDX_SOU_CH ch = TOTAL_SOU_CHS; + rt_uint16_t reg_offset = 0; // 区块内寄存器偏移 eMBException eStatus = MB_EX_NONE; eMBErrorCode eRegStatus; @@ -1010,26 +1046,8 @@ eMBException funcWriteHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, } /* ---- 地址范围校验 ---- */ - if(regAddress_H == HOLD_REG_SINK_BASE){ - if (REG_IS_SINK(regAddress)) { - ch = (eIDX_SOU_CH)REG_GET_SINK_CH(regAddress); - reg_offset = REG_GET_SINK_OFFSET(regAddress); - } - }else if (regAddress_H == HOLD_REG_SRC_BASE) { - if (REG_IS_SRC(regAddress)) { - ch = (eIDX_SOU_CH)REG_GET_SRC_CH(regAddress); - reg_offset = REG_GET_SRC_OFFSET(regAddress); - } - } else if (regAddress_H == HOLD_REG_SYS_BASE) { - if (REG_IS_SYS(regAddress)) { - ch = (eIDX_SOU_CH)REG_GET_SYS_CH(regAddress); - reg_offset = REG_GET_SYS_OFFSET(regAddress); - } - } else { - return MB_EX_ILLEGAL_DATA_ADDRESS; - } - - if (ch > IDX_SOU_CH4) { + if (chrg_comm_reg_decode(regAddress_H, regAddress, + &ch, ®_offset) != 0) { return MB_EX_ILLEGAL_DATA_ADDRESS; } @@ -1139,6 +1157,11 @@ eMBException funcWriteMultipleHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t #else rt_uint8_t regAddress_H = 0; rt_uint16_t regBaseOffset = 0; + rt_uint16_t regEndOffset = 0; + rt_uint16_t first_reg_offset = 0; + rt_uint16_t last_reg_offset = 0; + eIDX_SOU_CH first_ch = TOTAL_SOU_CHS; + eIDX_SOU_CH last_ch = TOTAL_SOU_CHS; #endif // ========== 公共校验逻辑(只写一遍)========== @@ -1211,17 +1234,21 @@ eMBException funcWriteMultipleHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t // 正常产品模式:分区地址逻辑 regAddress_H = reqFrame[2]; regBaseOffset = reqFrame[3]; + regEndOffset = regBaseOffset + cntReg - 1U; + /* 起止地址必须属于同一有效通道,禁止一帧0x10跨通道改写参数。 */ + if ((chrg_comm_reg_decode(regAddress_H, regBaseOffset, + &first_ch, &first_reg_offset) != 0) || + (chrg_comm_reg_decode(regAddress_H, regEndOffset, + &last_ch, &last_reg_offset) != 0) || + (first_ch != last_ch) || + (last_reg_offset != (first_reg_offset + cntReg - 1U))) { + return MB_EX_ILLEGAL_DATA_ADDRESS; + } for (int i = 0; i < cntReg; i++) { - rt_uint16_t curr_part_offset = regBaseOffset + i; - eIDX_SOU_CH ch = (eIDX_SOU_CH)(curr_part_offset / TOTAL_HOLD_CH_REGS); - rt_uint16_t ch_reg_offset = curr_part_offset % TOTAL_HOLD_CH_REGS; - - if (ch > IDX_SOU_CH4) { - //rt_kprintf("WRITE MULTI REG: CH ERROR, ch=%d (part offset=%d)\n", ch, curr_part_offset); - return MB_EX_ILLEGAL_DATA_ADDRESS; - } + eIDX_SOU_CH ch = first_ch; + rt_uint16_t ch_reg_offset = first_reg_offset + i; pSW = &g_shadow_sw[ch]; chrg_shadow_sync(ch); // 首次写同步真实状态 rt_uint16_t sou_value = u8v_to_u16(&pWriteData[i * 2]); diff --git a/apps/chrg/applications/thread/chrg_comm.h b/apps/chrg/applications/thread/chrg_comm.h index aae207d..d51e3d7 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.24 +当前版本号 : V1.1.31 主要功能 : 实现对外通讯协议 MODBUS 服务端,以独立线程等待处理响应请求数据。 版本历史 : 创建原始版本 说明 : @@ -40,10 +40,10 @@ typedef enum { // ========== 新寄存器地址映射 (使用0x06/0x10标准Modbus功能码) ========== // 详细寄存器偏移定义见 chrg_regs.h -// 判断寄存器地址属于哪个区块的宏 -#define REG_IS_SINK(addr) (((addr / 4) < TOTAL_SINK_CH_REGS)) -#define REG_IS_SRC(addr) (((addr / 4) < TOTAL_SRC_CH_REGS)) -#define REG_IS_SYS(addr) (((addr / 4) < TOTAL_SYS_CH_REGS)) +// 分区低字节按4通道连续排列,每通道占用TOTAL_xxx_CH_REGS个地址 +#define REG_IS_SINK(addr) ((addr) < (TOTAL_SINK_CH_REGS * 4U)) +#define REG_IS_SRC(addr) ((addr) < (TOTAL_SRC_CH_REGS * 4U)) +#define REG_IS_SYS(addr) ((addr) < (TOTAL_SYS_CH_REGS * 4U)) // 根据地址提取通道号和区块内偏移 #define REG_GET_SINK_CH(addr) ((addr) / TOTAL_SINK_CH_REGS) diff --git a/apps/chrg/applications/thread/chrg_roll_sou.c b/apps/chrg/applications/thread/chrg_roll_sou.c index d89f132..f3c475a 100644 --- a/apps/chrg/applications/thread/chrg_roll_sou.c +++ b/apps/chrg/applications/thread/chrg_roll_sou.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_roll_sou.c 完成日期 : -当前版本号 : V1.1.24 +当前版本号 : V1.1.31 主要功能 : 实现南向通信轮巡请求命令。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -168,23 +168,7 @@ int chrg_sou_com_batch_submit(eIDX_SOU_CH ch, const rt_uint16_t *regs, pBatch->cur_idx = 0; } - /* - * 电流调节采用最新值有效策略。若同一通道仍有尚未发送的电流命令, - * 直接覆盖旧值,避免四通道快速测试时队列积累过时的电流设置。 - * 当前命令已经发送时不覆盖,保证响应仍与原命令匹配。 - */ - if ((count == 1) && (regs[0] == (REG_CURRENT_OUT + 1))) { - for (i = pBatch->cur_idx; i < pBatch->reg_cnt; i++) { - if (pBatch->reg_list[i] == regs[0]) { - pBatch->val_list[i] = vals[0]; - pBatch->pending = RT_TRUE; - pROLL->run = RUN_COM; - rt_mutex_release(pTHR->mutex); - return 0; - } - } - } - + /* 电流命令与其它写命令一致,完整追加并等待回码,不再覆盖旧命令。 */ if ((pBatch->reg_cnt > SOU_COM_BATCH_REG_MAX) || (count > (SOU_COM_BATCH_REG_MAX - pBatch->reg_cnt))) { LOG_E("sou com batch reg full: ch=%d queued=%d append=%d max=%d", @@ -923,10 +907,13 @@ void chrg_roll_sou_thread_entry (void *data) measure_once = RT_TRUE; response_part = MB_R_PART1; chrg_led_flashing(IDX_LED3, TIMES_ONE, PULSE_TIME*2, PULSE_TIME*2); - if (idx != (rt_uint8_t)pROLL->idx) { - pROLL->idx = (eIDX_SOU_CH)idx; - chrg_south_switch((eIDX_SOU_CH)idx); + /* 插入命令可能改变物理选通,每次自动查询前必须重新选择通道。 */ + if (chrg_south_switch((eIDX_SOU_CH)idx) != 0) { + sou_measure_once_finish(idx); + rt_thread_mdelay(1); + continue; } + pROLL->idx = (eIDX_SOU_CH)idx; if (g_sou_version_ready[idx] == 0U) { version_probe = RT_TRUE; chrg_eload_read_version(ID_ELOAD); @@ -943,11 +930,14 @@ void chrg_roll_sou_thread_entry (void *data) continue; } else { // 通道启用 chrg_led_flashing(IDX_LED3, TIMES_ONE, PULSE_TIME*2, PULSE_TIME*2); - if (idx != (rt_uint8_t)pROLL->idx) { - pROLL->idx = (eIDX_SOU_CH)idx; - chrg_south_switch((eIDX_SOU_CH)idx); - pROLL->part = MB_R_PART1; + /* 不信任缓存通道,发送普通轮询前重新写入选通GPIO。 */ + if (chrg_south_switch((eIDX_SOU_CH)idx) != 0) { + CHRG_CHANNEL_NEXT(index); + rt_thread_mdelay(1); + continue; } + pROLL->idx = (eIDX_SOU_CH)idx; + pROLL->part = response_part; if (g_sou_version_ready[idx] == 0U) { version_probe = RT_TRUE; chrg_eload_read_version(ID_ELOAD); @@ -962,7 +952,11 @@ void chrg_roll_sou_thread_entry (void *data) break; case RUN_LCD: { - chrg_south_switch((eIDX_SOU_CH)single_ch); + if (chrg_south_switch((eIDX_SOU_CH)single_ch) != 0) { + sou_single_command_finish(run); + continue; + } + pROLL->idx = (eIDX_SOU_CH)single_ch; chrg_eload_write_reg(ID_ELOAD, single_reg, single_value); } break; @@ -976,9 +970,17 @@ void chrg_roll_sou_thread_entry (void *data) } batch_ch = command_ch; command_valid = RT_TRUE; - chrg_south_switch((eIDX_SOU_CH)command_ch); + /* COM队列通道必须与物理选通一致,选通失败时禁止发送本条命令。 */ + if (chrg_south_switch((eIDX_SOU_CH)command_ch) != 0) { + sou_com_command_failed(batch_ch, command_reg, command_value, + "channel switch", &retry_count[batch_ch]); + continue; + } + pROLL->idx = (eIDX_SOU_CH)command_ch; #if SOUTH_COMMAND_DEBUG - rt_kprintf("ch=%d",command_ch); + rt_kprintf("[SOU_CMD] queue_ch=%u phy_ch=%u reg=0x%04X value=%u\r\n", + command_ch + 1U, chrgsouth.idx + 1U, + command_reg, command_value); #endif chrg_eload_write_reg(ID_ELOAD, command_reg, command_value); } @@ -995,7 +997,14 @@ void chrg_roll_sou_thread_entry (void *data) rt_thread_mdelay(1); continue; } - chrg_south_switch((eIDX_SOU_CH)single_ch); + if (chrg_south_switch((eIDX_SOU_CH)single_ch) != 0) { + rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); + pROLL->updata_result = -1; + pROLL->send_flag = 0; + rt_mutex_release(pTHR->mutex); + continue; + } + pROLL->idx = (eIDX_SOU_CH)single_ch; ret = chrg_eload_send_ymodem_data(pROLL->updata_buf, pROLL->leng); rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); @@ -1007,7 +1016,11 @@ void chrg_roll_sou_thread_entry (void *data) } case RUN_CONSOLE: { - chrg_south_switch((eIDX_SOU_CH)single_ch); + if (chrg_south_switch((eIDX_SOU_CH)single_ch) != 0) { + sou_single_command_finish(run); + continue; + } + pROLL->idx = (eIDX_SOU_CH)single_ch; chrg_eload_refresh(ID_ELOAD, single_part); } break; diff --git a/apps/chrg/applications/thread/chrg_south.c b/apps/chrg/applications/thread/chrg_south.c index 3791477..c861a9a 100644 --- a/apps/chrg/applications/thread/chrg_south.c +++ b/apps/chrg/applications/thread/chrg_south.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_south.c 完成日期 : -当前版本号 : V1.1.24 +当前版本号 : V1.1.31 主要功能 : 实现南向通信交互,包括接收命令解析,发送请求命令,切换通路。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -141,6 +141,10 @@ int chrg_south_switch (eIDX_SOU_CH ch) return -1; } #endif + /* 仅在GPIO选通成功后更新物理通道,供发送日志和轮询校验使用。 */ + if (ret == 0) { + chrgsouth.idx = ch; + } return ret; } diff --git a/apps/chrg/applications/utils/chrg_eload.h b/apps/chrg/applications/utils/chrg_eload.h index ae7b0ca..4d2b253 100644 --- a/apps/chrg/applications/utils/chrg_eload.h +++ b/apps/chrg/applications/utils/chrg_eload.h @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_eload.h 完成日期 : -当前版本号 : V1.1.24 +当前版本号 : V1.1.28 主要功能 : 源载命令数据解析,封装,读写请求,寄存器定义,数据结构定义。 版本历史 : 创建原始版本 说明 : @@ -61,6 +61,9 @@ static const rt_uint8_t LOAD[30] = { #define STA1_READY (0x01) // 就绪/停止 #define STA1_RUNNING (0x02) // 运行 #define STA1_FAULT (0x03) // 故障 + #define STA1_SHORT_TEST (0x05) // 短路测试中 + #define STA1_OCP_TEST (0x06) // OCP测试中 + #define STA1_DYNA_TEST (0x07) // 动态测试中 #define REG_CV 0X07 // 电压地址 #define REG_REL_ON (0X01) // 主继电器控制寄存器 diff --git a/apps/chrg/applications/utils/chrg_sink.c b/apps/chrg/applications/utils/chrg_sink.c index cd7dfae..e691ca6 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.26 +当前版本号 : V1.1.28 主要功能 : 北向数据sink请求数据填充及发送。 版本历史 : 创建原始版本 说明 : @@ -25,11 +25,13 @@ #define SINK_UNLOAD_CONFIRM_COUNT 2U // 连续满足低电流条件的量测帧数 #define SINK_SHORT_KEEPALIVE_MS 30U // 短路测试WORK_START保活下发周期 #define SINK_INIT_NORTH_RELAY_DELAY_MS 30U // 辅助继电器回码后打开北向继电器的等待时间 +#define SINK_TEST_STATUS_RETRY_MAX 3U // 测试态退出确认的连续重发计数上限 -#define SINK_INIT_STAGE_CLEAR_RELAY 0U -#define SINK_INIT_STAGE_AUX_RELAY 1U -#define SINK_INIT_STAGE_DELAY 2U -#define SINK_INIT_STAGE_REST 3U +#define SINK_INIT_STAGE_TEST_STOP 0U +#define SINK_INIT_STAGE_TEST_STATUS 1U +#define SINK_INIT_STAGE_AUX_RELAY 2U +#define SINK_INIT_STAGE_DELAY 3U +#define SINK_INIT_STAGE_REST 4U static rt_uint8_t g_sink_init_stage[TOTAL_SOU_CHS] = {0}; static rt_tick_t g_sink_init_north_relay_tick[TOTAL_SOU_CHS] = {0}; @@ -61,6 +63,43 @@ static rt_uint32_t g_sink_von_voltage_seq[TOTAL_SOU_CHS] = {0}; 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}; +static rt_uint32_t g_sink_init_test_sample_seq[TOTAL_SOU_CHS] = {0}; +static rt_uint8_t g_sink_init_test_stop_retry[TOTAL_SOU_CHS] = {0}; +static rt_uint32_t g_sink_stop_test_sample_seq[TOTAL_SOU_CHS] = {0}; +static rt_uint8_t g_sink_stop_test_stop_retry[TOTAL_SOU_CHS] = {0}; +static rt_bool_t g_sink_stop_test_confirmed[TOTAL_SOU_CHS] = {RT_FALSE}; + +static rt_uint8_t chrg_sink_power_status_get(eIDX_SOU_CH idx) +{ + rt_uint16_t status1; + + if (idx >= TOTAL_SOU_CHS) { + return 0U; + } + status1 = chrgsouth.eload[idx].status1; + if (((status1 & 0x00FFU) == 0U) && ((status1 >> 8) != 0U)) { + status1 >>= 8; + } + return (rt_uint8_t)status1; +} + +static rt_bool_t chrg_sink_power_test_active(eIDX_SOU_CH idx) +{ + rt_uint8_t status1 = chrg_sink_power_status_get(idx); + + return (status1 == STA1_SHORT_TEST) || + (status1 == STA1_OCP_TEST) || + (status1 == STA1_DYNA_TEST); +} + +static rt_bool_t chrg_sink_test_exit_required(eIDX_SOU_CH idx, + const struct chrg_switch_t *pSW) +{ + return chrg_sink_power_test_active(idx) || + ((pSW != RT_NULL) && + ((pSW->sink.test.test_mode != SINK_NO_TEST) || + (pSW->sink.test.test_mode_old != SINK_NO_TEST))); +} enum { SINK_UNLOAD_NEXT_RUNNING = 0, @@ -304,7 +343,7 @@ static int chrg_sink_init_submit_stage(eIDX_SOU_CH idx, total = chrg_sink_init_build_commands(pSW, regs, vals); switch (g_sink_init_stage[idx]) { - case SINK_INIT_STAGE_CLEAR_RELAY: + case SINK_INIT_STAGE_TEST_STOP: start = 0U; count = 1U; break; @@ -344,7 +383,11 @@ static int chrg_sink_work_init(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) return -1; } g_sink_init_in_progress[idx] = RT_TRUE; - g_sink_init_stage[idx] = SINK_INIT_STAGE_CLEAR_RELAY; + /* 功率板回读处于Short/OCP/DYNA时,先用WORK_STOP退出测试态。 */ + g_sink_init_stage[idx] = chrg_sink_test_exit_required(idx, pSW) ? + SINK_INIT_STAGE_TEST_STOP : SINK_INIT_STAGE_AUX_RELAY; + g_sink_init_test_sample_seq[idx] = 0U; + g_sink_init_test_stop_retry[idx] = 0U; g_sink_init_north_relay_tick[idx] = 0U; /* 入队失败也保留事务和等待入口,由等待分支持续重提当前阶段。 */ pSW->sink.On_work = 1; @@ -398,7 +441,29 @@ static int chrg_sink_work_init_aux_wait(eIDX_SOU_CH idx, if (batch_result <= 0) { return chrg_sink_init_submit_stage(idx, pSW); } - if (g_sink_init_stage[idx] == SINK_INIT_STAGE_CLEAR_RELAY) { + if (g_sink_init_stage[idx] == SINK_INIT_STAGE_TEST_STOP) { + g_sink_init_stage[idx] = SINK_INIT_STAGE_TEST_STATUS; + g_sink_init_test_sample_seq[idx] = chrg_eload_get_sample_seq(idx); + return (chrg_sou_measure_once_submit(idx) == 0) ? 0 : -1; + } + if (g_sink_init_stage[idx] == SINK_INIT_STAGE_TEST_STATUS) { + rt_uint32_t sample_seq = chrg_eload_get_sample_seq(idx); + + if (sample_seq == g_sink_init_test_sample_seq[idx]) { + return (chrg_sou_measure_once_submit(idx) == 0) ? 0 : -1; + } + g_sink_init_test_sample_seq[idx] = sample_seq; + if (chrg_sink_power_test_active(idx)) { + if (g_sink_init_test_stop_retry[idx] >= + SINK_TEST_STATUS_RETRY_MAX) { + /* 达到一轮上限后重新计数,但仍保持在安全确认流程。 */ + g_sink_init_test_stop_retry[idx] = 0U; + } + g_sink_init_test_stop_retry[idx]++; + g_sink_init_stage[idx] = SINK_INIT_STAGE_TEST_STOP; + return chrg_sink_init_submit_stage(idx, pSW); + } + g_sink_init_test_stop_retry[idx] = 0U; g_sink_init_stage[idx] = SINK_INIT_STAGE_AUX_RELAY; return chrg_sink_init_submit_stage(idx, pSW); } @@ -636,29 +701,43 @@ static int chrg_sink_work_low_aux_wait(eIDX_SOU_CH idx, 输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 输出参数: 清除运行、使能和在线标志,关闭北向继电器 返回说明: 0:关机成功 <0:停止命令提交失败 -其它说明: 短路关机先退出短路再发送WORK_STOP,普通关机只发送WORK_STOP +其它说明: 功率板处于测试态时先发送WORK_STOP;短路兼容退出命令随后发送 *****************************************************************/ static int chrg_sink_work_stop_prepare(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) { - rt_uint16_t regs[2]; - rt_uint16_t values[2]; + rt_uint16_t regs[3]; + rt_uint16_t values[3]; rt_uint8_t count = 0U; rt_bool_t was_short; + rt_bool_t power_test_active; + rt_bool_t preserve_test_retry; if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { return -1; } - was_short = (pSW->sink.test.test_mode_old == SINK_TEST_SHORT) || + preserve_test_retry = (pSW->sink.work_mode == SINK_WORK_STOP_WAIT) && + !g_sink_stop_test_confirmed[idx] && + (g_sink_stop_test_stop_retry[idx] != 0U); + power_test_active = chrg_sink_power_test_active(idx); + was_short = (power_test_active && + (chrg_sink_power_status_get(idx) == STA1_SHORT_TEST)) || + (pSW->sink.test.test_mode_old == SINK_TEST_SHORT) || (pSW->sink.test.test_mode == SINK_TEST_SHORT) || g_sink_stop_from_short[idx]; g_sink_stop_from_short[idx] = was_short; + g_sink_stop_test_sample_seq[idx] = 0U; + if (!preserve_test_retry) { + g_sink_stop_test_stop_retry[idx] = 0U; + } + g_sink_stop_test_confirmed[idx] = RT_FALSE; + /* WORK_STOP必须是退出Short/OCP/DYNA的第一条南向命令。 */ + regs[count] = REG_WORK; + values[count++] = WORK_STOP; if (was_short) { regs[count] = ModbusRTU_ShortTest_CMD_MODE_FLAG; values[count++] = ModbusRTU_DYNA_CMD_MODE_STOP; } - regs[count] = REG_WORK; - values[count++] = WORK_STOP; g_sink_init_in_progress[idx] = RT_FALSE; g_sink_init_stage_submitted[idx] = RT_FALSE; if (chrg_sou_com_batch_cancel(idx) != 0) { @@ -887,6 +966,30 @@ static int chrg_sink_work_current_wait(eIDX_SOU_CH idx, return chrg_sink_work_protocol_unload(idx, pSW); } + if ((next_action == SINK_UNLOAD_NEXT_STOP_RELAY) && + !g_sink_stop_test_confirmed[idx]) { + rt_uint32_t test_sample_seq = chrg_eload_get_sample_seq(idx); + + if (g_sink_stop_test_sample_seq[idx] == 0U) { + g_sink_stop_test_sample_seq[idx] = test_sample_seq; + return (chrg_sou_measure_once_submit(idx) == 0) ? 0 : -1; + } + if (test_sample_seq == g_sink_stop_test_sample_seq[idx]) { + return (chrg_sou_measure_once_submit(idx) == 0) ? 0 : -1; + } + g_sink_stop_test_sample_seq[idx] = test_sample_seq; + if (chrg_sink_power_test_active(idx)) { + if (g_sink_stop_test_stop_retry[idx] >= + SINK_TEST_STATUS_RETRY_MAX) { + /* 达到一轮上限后重新计数,但仍保持在安全确认流程。 */ + g_sink_stop_test_stop_retry[idx] = 0U; + } + g_sink_stop_test_stop_retry[idx]++; + return chrg_sink_work_stop_prepare(idx, pSW); + } + g_sink_stop_test_confirmed[idx] = RT_TRUE; + } + if (!g_sink_unload_batch_confirmed[idx]) { /* 从最后一条写回码之后建立基线,排除RUN_COM之前残留的旧量测。 */ g_sink_unload_sample_seq[idx] = chrg_eload_get_sample_seq(idx); diff --git a/docs/修改日志.md b/docs/修改日志.md index a6c1cd1..8a41b35 100644 --- a/docs/修改日志.md +++ b/docs/修改日志.md @@ -29,6 +29,51 @@ - `apps/chrg` 各C源文件和头文件的文件头版本统一为 `V1.1.24`。 - Bootloader版本保持 `V1.0.0` 不变。 - 后续每次修改应用代码时,必须同步更新应用版本号、对外版本编码和本修改日志。 +## V1.1.31 - 2026-09-16 + +### 通道保护代码注释 + +- 补充上位机地址解码、非法通道初值、跨通道写保护的中文注释。 +- 补充南向物理选通同步、自动轮询重新选通及电流命令顺序入队的中文注释。 +- 协议板版本号更新为 `V1.1.31`,固件版本寄存器更新为 `0x012F`。 + +## V1.1.30 - 2026-09-16 + +### 南向通道选择与地址校验 + +- 南向物理选通成功后统一记录 `chrgsouth.idx`,自动轮询每次发送前重新选通目标通道。 +- COM、LCD、升级和控制台发送路径同步更新当前通道,选通失败时禁止继续发送。 +- 单寄存器写初始化通道变量,并通过统一解码函数拒绝非法分区和越界地址。 +- 多寄存器写按每通道50个地址计算通道,并拒绝跨通道写入。 +- `SOUTH_COMMAND_DEBUG` 日志增加队列通道、实际物理通道、寄存器和值。 +- 协议板版本号更新为 `V1.1.30`,固件版本寄存器更新为 `0x012E`。 + +## V1.1.29 - 2026-09-16 + +### 取消电流命令覆盖去重 + +- 删除南向队列对未发送电流命令的覆盖逻辑。 +- 同一通道新增的电流设置按提交顺序逐条发送,并分别等待功率板回码。 +- 协议板版本号更新为 `V1.1.29`,固件版本寄存器更新为 `0x012D`。 +- 待验证:快速连续设置电流时,确认每条电流命令都能在南向日志和功率板侧收到。 + +## V1.1.28 - 2026-09-16 + +### 测试态退出结果确认 + +- 初始化发送 `WORK_STOP` 后,通过新的 `P1` 量测重新读取功率板 `status1`。 +- 只有确认 `status1` 不再是 `0x05/0x06/0x07`,才继续打开辅助继电器和后续初始化命令。 +- 关机流程同样在关闭南向继电器前确认测试态已退出;每连续重发 `WORK_STOP` 3 次后重新计数,仍未退出时保持安全等待并继续重试。 +- 协议板版本号更新为 `V1.1.28`,固件版本寄存器更新为 `0x012C`。 + +## V1.1.27 - 2026-09-16 + +### 功率板测试状态退出保护 + +- 新增功率板 `status1=0x05/0x06/0x07` 的 Short、OCP、DYNA 状态定义。 +- 初始化前识别功率板测试状态,优先发送 `WORK_STOP` 后再继续初始化。 +- 负载关机流程调整为 `WORK_STOP` 优先,避免测试退出命令被后续启动状态覆盖。 +- 上位机版本寄存器 `0x0300` 更新为 `0x012B`(`V1.1.27`)。 ## V1.1.23 - 2026-09-11