diff --git a/apps/chrg/applications/thread/chrg_comm.c b/apps/chrg/applications/thread/chrg_comm.c index 8a23a10..2836911 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.37 +当前版本号 : V1.1.39 主要功能 : 实现对外通讯协议 MODBUS 服务端,以独立线程等待处理响应请求数据。 版本历史 : 创建原始版本 说明 : @@ -27,7 +27,7 @@ #include "stm32f4xx_hal.h" #define HOLD_REG_FIRMWARE_VERSION 0x0300U /* 协议板固件版本,只读 */ -#define CHRG_FIRMWARE_VERSION 0x0135U /* 当前协议板版本:V1.1.37 */ +#define CHRG_FIRMWARE_VERSION 0x0137U /* 当前协议板版本:V1.1.39 */ rt_uint8_t ucDiscInBuf[(TOTAL_DISC_REGS+BITS_UCHAR-1)/BITS_UCHAR]; diff --git a/apps/chrg/applications/thread/chrg_north.c b/apps/chrg/applications/thread/chrg_north.c index e65e24d..888a245 100644 --- a/apps/chrg/applications/thread/chrg_north.c +++ b/apps/chrg/applications/thread/chrg_north.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_north.c 完成日期 : -当前版本号 : V1.1.24 +当前版本号 : V1.1.39 主要功能 : 实现北向通信交互,包括接收命令解析,发送请求命令,切换通路。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -332,6 +332,50 @@ int chrg_north_switch (eIDX_NOR_CH ch) chrgios[IDX_GPO_NOR_SW_B].base, (eIDX_CH)ch); } +#define NORTH_SWITCH_SETTLE_MS 2U +#define NORTH_RX_RESET_TIMEOUT_MS 30U + +/* + * 开始一次北向请求:切换物理通道后先让接收线程清理旧事务, + * 再为当前通道生成新的内部事务号,避免旧通道尾帧被误配给新请求。 + * 事务号和清理序号都不进入北向协议帧,只用于协议板内部并发隔离。 + */ +rt_uint32_t chrg_north_request_begin(eIDX_NOR_CH ch) +{ + struct chrg_north_t *pNOR = &chrgnorth; + rt_uint32_t reset_request; + rt_tick_t start_tick; + + if ((ch >= TOTAL_NOR_CHS) || (pNOR->tty == RT_NULL)) { + return 0U; + } + pNOR->idx = ch; + /* MUX切换稳定后再请求接收线程清理,避免旧通道尾包残留到新请求。 */ + rt_thread_mdelay(NORTH_SWITCH_SETTLE_MS); + pNOR->transaction_id[ch]++; + if (pNOR->transaction_id[ch] == 0U) { + pNOR->transaction_id[ch] = 1U; + } + /* 请求号采用非零序号;回绕时跳过0,便于区分未初始化状态。 */ + reset_request = pNOR->rx_reset_request + 1U; + if (reset_request == 0U) { + reset_request = 1U; + } + pNOR->rx_reset_request = reset_request; + /* 接收线程观察到请求号变化后执行实际清理,发送线程只负责发起请求。 */ + (void)chrg_tty_break_recv(pNOR->tty); + start_tick = rt_tick_get(); + while (pNOR->rx_reset_ack != reset_request) { + if ((rt_tick_t)(rt_tick_get() - start_tick) >= + rt_tick_from_millisecond(NORTH_RX_RESET_TIMEOUT_MS)) { + LOG_W("north rx reset timeout: ch=%d", ch + 1U); + return 0U; + } + rt_thread_mdelay(1); + } + return pNOR->transaction_id[ch]; +} + /***************************************************************** 函数名称: chrg_north_send 函数描述: 北向通道数据发送 @@ -670,6 +714,8 @@ static int chrg_north_pickup_frame (struct rt_ringbuffer *rb, } pFrame->pMB->length = pFrame->len_msg; pFrame->pMB->payload = RT_NULL; + pFrame->pMB->channel = (rt_uint8_t)idx; + pFrame->pMB->transaction_id = chrgnorth.transaction_id[idx]; pFrame->pMB->payload = (rt_uint8_t *)rt_malloc(pFrame->len_msg); if (RT_NULL == pFrame->pMB->payload) { LOG_E("malloc buf failed."); @@ -711,6 +757,28 @@ static int chrg_north_pickup_frame (struct rt_ringbuffer *rb, return cnt; } +static void chrg_north_rx_reset_request_process(struct chrg_north_t *pNOR, + struct chrg_tty_t *pTTY, eIDX_NOR_CH *pFrameIdx) +{ + rt_uint8_t discard[32]; + + if ((pNOR == RT_NULL) || (pTTY == RT_NULL) || + (pFrameIdx == RT_NULL) || + (pNOR->rx_reset_ack == pNOR->rx_reset_request)) { + return; + } + /* + * 只由北向接收线程执行:在同一上下文中复位环形缓冲区、未完成帧, + * 并排空UART驱动FIFO,避免与接收操作并发访问这些状态。 + */ + rt_ringbuffer_reset(pNOR->rb); + chrg_north_frame_reset(&pNOR->frame, RT_TRUE); + while (rt_device_read(pTTY->dev, 0, discard, sizeof(discard)) > 0) { + } + *pFrameIdx = pNOR->idx; + pNOR->rx_reset_ack = pNOR->rx_reset_request; +} + /***************************************************************** 函数名称: chrg_north_thread_entry 函数描述: 北向通道数据接收线程体 @@ -756,6 +824,10 @@ void chrg_north_thread_entry (void *data) } while (1) { + if (pNOR->rx_reset_ack != pNOR->rx_reset_request) { + chrg_north_rx_reset_request_process(pNOR, pTTY, &frame_idx); + continue; + } if (pNOR->frame.findhead && ((rt_tick_t)(rt_tick_get() - pNOR->frame.last_tick) >= rt_tick_from_millisecond(NORTH_FRAME_TIMEOUT_MS))) { @@ -765,6 +837,11 @@ void chrg_north_thread_entry (void *data) rt_memset(pNOR->rx_buf, 0, sizeof(pNOR->rx_buf)); len = chrg_tty_recv(pTTY, pNOR->rx_buf, sizeof(pNOR->rx_buf)); + if (pNOR->rx_reset_ack != pNOR->rx_reset_request) { + /* 切换请求到达期间读出的字节属于旧事务,整批丢弃。 */ + chrg_north_rx_reset_request_process(pNOR, pTTY, &frame_idx); + continue; + } if (len <= 0) { rt_thread_mdelay(1); continue; @@ -779,6 +856,7 @@ void chrg_north_thread_entry (void *data) rt_ringbuffer_reset(pNOR->rb); chrg_north_frame_reset(&pNOR->frame, RT_TRUE); frame_idx = pNOR->idx; + continue; } put_len = rt_ringbuffer_put(pNOR->rb, pNOR->rx_buf, len); diff --git a/apps/chrg/applications/thread/chrg_north.h b/apps/chrg/applications/thread/chrg_north.h index 18ffbe5..10230f1 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.24 +当前版本号 : V1.1.39 主要功能 : 实现北向通信交互,包括接收命令解析,发送请求命令,切换通路。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -133,6 +133,11 @@ struct chrg_north_t { struct rt_ringbuffer *rb; struct pickup_info_t frame; rt_uint32_t config_version[TOTAL_NOR_CHS]; // COM配置版本,用于识别等待期间到达的旧回码 + /* 每个物理通道独立递增;仅在协议板内部标识一次北向请求。 */ + volatile rt_uint32_t transaction_id[TOTAL_NOR_CHS]; + /* 通道切换时由发送线程递增请求号,接收线程完成清理后写回确认号。 */ + volatile rt_uint32_t rx_reset_request; + volatile rt_uint32_t rx_reset_ack; struct chrg_trim trim[TOTAL_NOR_CHS]; rt_uint8_t rx_buf[SIZE_BUF_TTY]; rt_uint8_t free_flag; @@ -178,6 +183,9 @@ extern void chrg_sink_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx, *****************************************************************/ extern int chrg_north_switch (eIDX_NOR_CH ch); +/* 物理通道切换后清理旧接收数据并建立新事务,返回0表示准备失败。 */ +extern rt_uint32_t chrg_north_request_begin(eIDX_NOR_CH ch); + /***************************************************************** 函数名称: chrg_north_send 函数描述: 北向通道数据发送 diff --git a/apps/chrg/applications/thread/chrg_roll_nor.c b/apps/chrg/applications/thread/chrg_roll_nor.c index 45d0307..30c128d 100644 --- a/apps/chrg/applications/thread/chrg_roll_nor.c +++ b/apps/chrg/applications/thread/chrg_roll_nor.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_roll_nor.c 完成日期 : -当前版本号 : V1.1.35 +当前版本号 : V1.1.39 主要功能 : 实现北向通信轮巡请求命令。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -27,7 +27,8 @@ #define LOG_TAG "chrg.rollnor" #include -#define NORTH_RESPONSE_TIMEOUT_MS 50U // 单次北向请求等待匹配回码的总时间 +#define NORTH_RESPONSE_TIMEOUT_MEASURE_MS 80U // 0x20量测回码等待时间,避免单通道阻塞过久 +#define NORTH_RESPONSE_TIMEOUT_CONFIG_MS 150U // 协议/CC配置回码等待时间,给功率板生效留出时间 #define NORTH_REQUEST_MAX_ATTEMPTS 1U // 每次轮到通道只发送并等待一次,超时后留给下一轮 #define NORTH_POLL_INTERVAL_MS 10U // 一个通道事务结束后到下一通道的轮询间隔 #define SOURCE_PROTOCOL_WAIT_MS 100U // 每通道0xD0应答后的协议生效保护时间 @@ -593,15 +594,17 @@ static rt_uint8_t chrg_roll_nor_expected_cmd(const struct chrg_switch_t *pSW) /***************************************************************** 函数名称: chrg_roll_nor_response_matches -函数描述: 校验北向回码与当前请求的通道角色、命令和固定长度是否匹配 +函数描述: 校验北向回码与当前请求的通道、事务号、角色、命令和固定长度是否匹配 输入参数: pMB:已通过north.c结构及CRC校验的回码 expected_idx:请求通道 - expected_id:请求角色 expected_cmd:请求命令 + expected_id:请求角色 expected_cmd:请求命令 transaction_id:请求内部事务号 输出参数: - 返回说明: 0:回码匹配 <0:参数错误或回码与请求不匹配 -其它说明: 本函数只判断请求归属,业务解析在配置版本复核通过后执行 +其它说明: 通道和事务号只用于协议板内部,不改变北向协议帧;本函数只判断请求归属, + 业务解析在配置版本复核通过后执行 *****************************************************************/ static int chrg_roll_nor_response_matches(const struct mb_msg_t *pMB, - rt_uint8_t expected_idx, eIDX_ID expected_id, rt_uint8_t expected_cmd) + rt_uint8_t expected_idx, eIDX_ID expected_id, rt_uint8_t expected_cmd, + rt_uint32_t transaction_id) { if ((pMB == RT_NULL) || (pMB->payload == RT_NULL) || (pMB->length < MIN_FRAME_LENGTH) || @@ -616,6 +619,10 @@ static int chrg_roll_nor_response_matches(const struct mb_msg_t *pMB, pMB->payload[3], expected_cmd); return -2; } + if ((pMB->channel != expected_idx) || + (pMB->transaction_id != transaction_id)) { + return -5; + } if ((expected_cmd == SRC_CMD_GET_VOLTAGE) && (pMB->length != 21U)) { return -3; @@ -679,7 +686,7 @@ static void chrg_roll_nor_drain_mailbox(struct chrg_thread_t *pTN) *****************************************************************/ static int chrg_roll_nor_wait_response(struct chrg_thread_t *pTN, rt_uint8_t expected_idx, eIDX_ID expected_id, rt_uint8_t expected_cmd, - struct mb_msg_t **matched_response) + rt_uint32_t transaction_id, struct mb_msg_t **matched_response) { rt_tick_t start_tick; rt_tick_t timeout_tick; @@ -694,7 +701,9 @@ static int chrg_roll_nor_wait_response(struct chrg_thread_t *pTN, } *matched_response = RT_NULL; start_tick = rt_tick_get(); - timeout_tick = rt_tick_from_millisecond(NORTH_RESPONSE_TIMEOUT_MS); + timeout_tick = rt_tick_from_millisecond( + (expected_cmd == SRC_CMD_GET_VOLTAGE) ? + NORTH_RESPONSE_TIMEOUT_MEASURE_MS : NORTH_RESPONSE_TIMEOUT_CONFIG_MS); while (1) { elapsed = (rt_tick_t)(rt_tick_get() - start_tick); @@ -711,7 +720,7 @@ static int chrg_roll_nor_wait_response(struct chrg_thread_t *pTN, } if (chrg_roll_nor_response_matches(pMB, expected_idx, - expected_id, expected_cmd) == 0) { + expected_id, expected_cmd, transaction_id) == 0) { *matched_response = pMB; return 0; } @@ -841,6 +850,7 @@ static int chrg_roll_nor_auto_request(rt_uint8_t idx, rt_uint8_t expected_cmd = 0; // 本次请求期望收到的命令码 rt_uint8_t max_attempts; // 本次请求允许的总发送次数 eIDX_ID expected_id = ID_SINK; // 本次请求发送时的角色快照 + rt_uint32_t transaction_id = 0; // 本次请求内部事务号 rt_uint32_t request_version = 0; // 本次请求发送时的COM配置版本 int send_ret; // 当前命令的串口发送结果 int wait_ret; // 当前命令的回码等待结果 @@ -862,6 +872,7 @@ static int chrg_roll_nor_auto_request(rt_uint8_t idx, expected_id = pSW->id; expected_cmd = chrg_roll_nor_expected_cmd(pSW); request_version = pNOR->config_version[idx]; + transaction_id = pNOR->transaction_id[idx]; /* 新事务发送前清理已结束或已超时事务遗留的邮箱回码。 */ chrg_roll_nor_drain_mailbox(pTN); } @@ -875,7 +886,7 @@ static int chrg_roll_nor_auto_request(rt_uint8_t idx, if (send_ret > 0) { wait_ret = chrg_roll_nor_wait_response(pTN, idx, - expected_id, expected_cmd, &pMB); + expected_id, expected_cmd, transaction_id, &pMB); if ((wait_ret == 0) && (pMB != RT_NULL)) { rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); if ((pNOR->config_version[idx] != request_version) || @@ -1047,8 +1058,12 @@ void chrg_roll_nor_thread_entry (void *data) continue; } else { chrg_led_flashing(IDX_LED2, TIMES_ONE, PULSE_TIME*2, PULSE_TIME*2); - chrg_north_switch((eIDX_NOR_CH)idx); - chrgnorth.idx = idx; + if ((chrg_north_switch((eIDX_NOR_CH)idx) != 0) || + (chrg_north_request_begin((eIDX_NOR_CH)idx) == 0U)) { + CHRG_CHANNEL_NEXT(index); + rt_thread_mdelay(NORTH_POLL_INTERVAL_MS); + continue; + } pROLL->idx = idx; (void)chrg_roll_nor_auto_request(idx, pSW, pTHR, pTN, pNOR); diff --git a/apps/chrg/applications/utils/chrg_def.h b/apps/chrg/applications/utils/chrg_def.h index 87a211a..e9f5585 100644 --- a/apps/chrg/applications/utils/chrg_def.h +++ b/apps/chrg/applications/utils/chrg_def.h @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_def.h 完成日期 : -当前版本号 : V1.1.37 +当前版本号 : V1.1.39 主要功能 : 全局宏定义,常量,及结构体。 版本历史 : 创建原始版本 说明 : @@ -15,8 +15,8 @@ /* CHRG application version. Keep this value synchronized with the changelog. */ #define CHRG_VERSION_MAJOR 1U #define CHRG_VERSION_MINOR 1U -#define CHRG_VERSION_PATCH 37U -#define CHRG_VERSION_STRING "V1.1.37" +#define CHRG_VERSION_PATCH 39U +#define CHRG_VERSION_STRING "V1.1.39" /* * Channel polling mode: @@ -114,6 +114,8 @@ struct vc_pd_t { struct mb_msg_t { rt_uint16_t length; // 数据长度 rt_uint8_t *payload; // 数据区 + rt_uint8_t channel; // 接收物理通道(北向消息使用),用于匹配当前轮询通道 + rt_uint32_t transaction_id; // 北向请求内部事务号,不进入通信协议帧,仅用于丢弃旧回码 }; struct pickup_info_t { diff --git a/docs/修改日志.md b/docs/修改日志.md index 2bb3e5c..f16e6a5 100644 --- a/docs/修改日志.md +++ b/docs/修改日志.md @@ -1,5 +1,21 @@ # 修改日志 +## V1.1.39 - 2026-09-24 + +### 北向事务隔离逻辑注释 + +- 补充通道切换接收清理、内部事务号和回码匹配逻辑的中文注释。 +- 协议板版本寄存器 `0x0300` 更新为 `0x0137`。 + +## V1.1.38 - 2026-09-24 + +### 北向事务超时与通道切换隔离 + +- 北向 `0x20` 量测回码等待 `80ms`,协议和CC配置回码等待 `150ms`。 +- 北向邮箱消息增加物理通道和内部事务号,只接受当前请求对应的回码,不改变北向协议帧。 +- 物理通道切换后清空驱动接收FIFO、环形缓冲区和未完成帧,等待接收线程确认后再发送。 +- 协议板版本寄存器 `0x0300` 更新为 `0x0136`。 + ## V1.1.37 - 2026-09-21 ### Sink功率板角色同步 diff --git a/docs/功率板升级_北向转发计划.md b/docs/功率板升级_北向转发计划.md new file mode 100644 index 0000000..952715a --- /dev/null +++ b/docs/功率板升级_北向转发计划.md @@ -0,0 +1,213 @@ +# 功率板升级:北向转发计划 + +## 1. 范围与原则 + +北向链路为“PC 上位机 ↔ 协议板 App”。协议板通过功能码 `0x20` 进入指定功率板的升级代理模式,之后北向链路切换为原始 YMODEM 字节流。 + +当前功率板 Boot 仅将 YMODEM 数据区直接写入 `APP_FLASH_BASE_ADDR`,本阶段升级文件必须是可直接启动的裸 APP 镜像。若交付物带 `.upk` 头、分区或校验字段,必须先实现 Boot 解包和最终完整性校验,未完成前不得透传该 `.upk`。 + +升级期间采用以下原则: + +1. PC 是 YMODEM 主机,负责包号推进、NAK 重发和完整时序。 +2. 协议板只拼帧、校验并转发,不在内部自动重发 YMODEM 数据帧。 +3. 协议板给 PC 的 ACK、NAK、`C`、CAN 必须来自南向功率板 Boot,不允许提前伪造成功应答。 +4. 同一时刻只允许一个北向连接升级一个功率板通道。 + +## 2. 北向进入和退出命令 + +### 2.1 进入升级 + +PC 发送 8 字节 Modbus RTU 扩展命令: + +```text +Byte 0 协议板地址 +Byte 1 0x20 +Byte 2 保留,固定 0x00 +Byte 3 保留,固定 0x00 +Byte 4 功率板通道号,范围 0..TOTAL_SOU_CHS-1 +Byte 5 0xFF,进入升级 +Byte 6 CRC_L +Byte 7 CRC_H +``` + +协议板必须完成以下操作后才能回复成功: + +1. 校验地址、长度、通道和 Modbus CRC。 +2. 锁定目标通道并停止普通南向轮询。 +3. 南向发送进入升级控制帧。 +4. 收到功率板 Boot 的 ACK;若同时收到 `C`,必须保留并在北向响应完成后转发。 + +成功响应: + +```text +协议板地址 + 0x06 + CRC_L + CRC_H +``` + +广播地址 `0x00` 不回复。失败使用现有 Modbus 异常响应,不得进入原始 YMODEM 模式。 + +### 2.2 退出升级/跳转 App + +在升级模式中,PC 发送同格式的 8 字节命令,Byte 5 为 `0x00`。协议板南向发送跳转 App 控制帧;功率板 Boot 必须先发送 `ACK` 并确认 UART 发送完成,再执行跳转。协议板收到该 ACK 后退出升级模式并恢复普通轮询。 + +结束空 SOH 获得 ACK 只表示 YMODEM 传输结束;PC 仍须发送退出命令,并在恢复通信后读取功率板版本确认升级结果。 + +## 3. 北向 YMODEM 包格式 + +进入升级成功后,北向不再使用 Modbus 封装,直接传输 YMODEM 字节流。 + +| 类型 | 完整长度 | 格式 | +| --- | ---: | --- | +| 元数据 SOH | 133 | `01 + pkt + ~pkt + 128B Data + CRC16_H + CRC16_L` | +| 数据 STX | 1029 | `02 + pkt + ~pkt + 1024B Data + CRC16_H + CRC16_L` | +| 可选数据 SOH | 133 | `01 + pkt + ~pkt + 128B Data + CRC16_H + CRC16_L` | +| EOT | 1 | `04` | +| CAN | 1 或 2 | `18`,取消时建议连续两个 | + +CRC 使用 CRC-16/XMODEM:`poly=0x1021`、`init=0x0000`、不反射、`xorout=0x0000`。 + +元数据包固定为包号 0,文件名和长度对应完整功率板 `.upk` 文件: + +```text +文件名 + 0x00 + `.upk` 实际十进制长度 + 0x00 + 其余补 0x00 +``` + +数据从包号 1 开始。首个 STX 携带 `.upk` 文件偏移 `0~1023` 的原始字节,后续帧按文件偏移连续发送;协议板不解释 UPK 内部字段。最后一帧不足固定数据区时用 `0x1A` 补齐,但补齐字节不计入元数据中的 `.upk` 文件长度。 + +## 4. 北向状态机 + +```text +N_IDLE + 收到合法 0x20/进入命令 + -> N_ENTERING + +N_ENTERING + 南向确认进入 Boot + -> 回复 0x20 成功 + -> N_WAIT_C + 失败/超时 + -> 回复异常 + -> N_IDLE + +N_WAIT_C + 收到南向 C -> 原样转发 -> N_WAIT_META + +N_WAIT_META + 收到完整且本地校验正确的 SOH/包0 + -> 原样提交南向 + -> N_WAIT_META_RESP + +N_WAIT_META_RESP + 南向 ACK -> 转发 ACK + 南向 C -> 转发 C -> N_WAIT_DATA + 南向 NAK -> 转发 NAK -> N_WAIT_META + +N_WAIT_DATA + 收到完整 SOH/STX 数据帧 + -> 原样提交南向 + -> N_WAIT_DATA_RESP + 收到 EOT + -> 原样提交南向 + -> N_WAIT_EOT1_RESP + 收到 CAN + -> 原样转发南向 + -> N_ABORT + +N_WAIT_DATA_RESP + 南向 ACK -> 转发 ACK -> N_WAIT_DATA + 南向 NAK -> 转发 NAK -> N_WAIT_DATA + 南向 CAN -> 转发 CAN CAN -> N_ABORT + +N_WAIT_EOT1_RESP + 南向 NAK -> 原样转发 -> N_WAIT_EOT2 + +N_WAIT_EOT2 + 收到第二个 EOT -> 原样提交南向 -> N_WAIT_EOT2_RESP + +N_WAIT_EOT2_RESP + 南向 ACK -> 转发 ACK + 南向 C -> 转发 C -> N_WAIT_END_SOH + +N_WAIT_END_SOH + 收到包号0、128字节全0的 SOH + -> 原样提交南向 + -> N_WAIT_END_ACK + +N_WAIT_END_ACK + 南向 ACK -> 转发 ACK -> N_TRANSFER_DONE + +N_TRANSFER_DONE + 收到合法 0x20/退出命令 -> N_EXITING + +N_EXITING + 南向跳转确认 -> 回复命令成功 -> 恢复轮询 -> N_VERIFY + +N_VERIFY + PC通过恢复后的正常协议读取功率板版本 + 版本正确 -> 升级确认成功 -> N_IDLE + 版本不符/无响应 -> 升级确认失败 -> N_IDLE +``` + +## 5. 北向完整时序 + +```text +PC -> 协议板 : 0x20进入命令 +协议板 -> 功率板 : 进入升级控制帧 +功率板 -> 协议板 : ACK, C +协议板 -> PC : 0x20成功响应 +协议板 -> PC : C + +PC -> 协议板 : SOH包0元数据 +协议板 -> 功率板 : 原样SOH +功率板 -> 协议板 : ACK, C +协议板 -> PC : ACK, C + +PC -> 协议板 : STX包1 +协议板 -> 功率板 : 原样STX包1 +功率板 -> 协议板 : ACK +协议板 -> PC : ACK +...逐包停止等待... + +PC -> 协议板 : EOT +协议板 -> 功率板 : EOT +功率板 -> 协议板 : NAK +协议板 -> PC : NAK + +PC -> 协议板 : EOT +协议板 -> 功率板 : EOT +功率板 -> 协议板 : ACK, C +协议板 -> PC : ACK, C + +PC -> 协议板 : SOH包0,Data全0 +协议板 -> 功率板 : 原样SOH +功率板 -> 协议板 : ACK +协议板 -> PC : ACK + +PC -> 协议板 : 0x20退出命令 +协议板 -> 功率板 : 跳转App控制帧 +功率板 -> 协议板 : ACK(发送完成后才跳转App) +协议板 -> PC : 0x20成功响应 +协议板 : 恢复轮询并刷新功率板版本 +PC -> 协议板 : 通过正常协议查询功率板版本并确认升级结果 +``` + +## 6. 北向校验和异常处理 + +- 北向可分多次收到一帧,必须按帧头确定 133/1029/1 字节并累计完整后再转发。 +- 同一帧内部超时建议 1000 ms;超时后清空半帧并向 PC 发送 NAK。 +- 等待南向应答时禁止接受下一数据帧,严格执行停止等待。 +- 任意传输阶段收到 PC 的 CAN 时,应转发南向、终止会话并恢复普通轮询。 +- 本地包号反码或 CRC 错误:不转发南向,直接向 PC 发送 NAK。 +- 南向 NAK:原样转发 PC,由 PC 重发;协议板不得自行重发。 +- 南向应答超时:尽可能向功率板和 PC 各发送 CAN CAN,结束会话;不得盲目重发已可能写入的帧。 +- 北向 ACK 等待建议由 PC 设置为 10 秒;进入升级擦除等待建议 15 秒。 +- 升级会话空闲超过 15 秒,发送 CAN CAN、清理缓存、恢复普通轮询。 + +## 7. 北向实施清单 + +1. 删除数据帧和 EOT 的协议板内部自动重发,改为原样转发 NAK。 +2. 为两次 EOT 建立独立状态,禁止把第一次 EOT 的 NAK当成普通失败。 +3. 校验元数据包号、连续数据包号和结束空包内容。 +4. 只有收到南向真实应答后才回复 PC。 +5. 结束后执行退出命令和版本读取,区分“传输完成”与“升级确认成功”。 +6. 日志至少记录通道、状态、帧类型、包号、长度、南向应答和失败原因。 +7. 联调前确认升级文件为裸 APP 镜像;若使用 UPK,先完成 Boot 端解包和完整性校验。 diff --git a/docs/功率板升级_南向应答计划.md b/docs/功率板升级_南向应答计划.md new file mode 100644 index 0000000..e9f5115 --- /dev/null +++ b/docs/功率板升级_南向应答计划.md @@ -0,0 +1,207 @@ +# 功率板升级:南向发送与应答计划 + +## 1. 范围与原则 + +南向链路为“协议板 App ↔ 目标功率板 Boot”。当前功率板 Boot 仅将 YMODEM 数据区直接写入 `APP_FLASH_BASE_ADDR`,因此本阶段只允许传输裸 APP 镜像。协议板不修改镜像内容,只负责锁定物理通道、连续发送完整 YMODEM 帧、采集功率板的单字节应答,并将应答按原顺序交给北向状态机。UPK 解包、分区、版本、加密和最终 CRC 校验属于后续 Boot 能力,未实现前不得向本 Boot 透传 `.upk`。 + +南向升级期间停止普通 Modbus 轮询、LCD 插入命令、控制台命令和其他通道请求。升级数据不得经过普通 Modbus解析器。 + +协议板只校验 YMODEM 帧长度、包号反码和 CRC16;当前 Boot 的 APP 可写范围是 `0x08003800 + 0xB000`(44 KB),裸 APP 镜像长度必须受此范围限制。 + +## 2. 串口和控制帧 + +南向串口参数:115200 bps、8N1、无流控。每次发送前必须确认目标通道已选通,并正确控制 RS485 收发方向。 + +进入功率板 Boot: + +```text +03 06 00 0F 00 FF F8 6B +``` + +功率板正常应答: + +```text +06 43 +``` + +其中 `06` 表示进入命令执行成功,`43` 表示 Boot 已准备接收 CRC 模式 YMODEM。 + +跳转功率板 App: + +```text +03 06 00 0F 00 0F F8 2F +``` + +正常应答为 `06`;`15` 表示失败。Boot 必须在 UART 完整发送 `06` 后才关闭 DMA/中断并跳转 App,避免协议板因未收到应答而超时。 + +## 3. 南向帧和应答映射 + +| 协议板发送 | 功率板 Boot 正常回复 | 协议板处理 | +| --- | --- | --- | +| 进入升级控制帧 | `ACK + C` | ACK确认进入,C进入应答队列,按顺序交北向 | +| 元数据 SOH/包0 | `ACK + C` | 两字节均按顺序交北向 | +| 普通 SOH/STX | `ACK` | 交北向,允许下一帧 | +| 校验错误的数据帧 | `NAK` | 原样交北向,不在南向自动重发 | +| 第一次 EOT | `NAK` | 这是正常握手,原样交北向 | +| 第二次 EOT | `ACK + C` | 两字节均按顺序交北向 | +| 结束空 SOH/包0 | `ACK` | 交北向,标记传输完成 | +| CAN | `ACK`或无回复 | 停止会话并清理缓存 | +| 跳转 App 控制帧 | `ACK` | 退出升级模式并恢复轮询 | + +## 4. 南向状态机 + +```text +S_IDLE + 北向进入请求 -> 锁通道、停轮询 -> S_ENTER_TX + +S_ENTER_TX + UART发送完成 -> S_ENTER_WAIT + +S_ENTER_WAIT + ACK -> 记录进入成功 + C -> 记录Boot就绪 -> S_WAIT_META + NAK/超时 -> S_ABORT + +S_WAIT_META + 收到北向完整元数据帧 -> S_META_TX + +S_META_TX + UART发送完成 -> S_META_WAIT + +S_META_WAIT + ACK -> 转交北向 + C -> 转交北向 -> S_WAIT_DATA + NAK -> 转交北向 -> S_WAIT_META + +S_WAIT_DATA + 收到SOH/STX -> S_DATA_TX + 收到EOT -> S_EOT1_TX + +S_DATA_TX + UART发送完成 -> S_DATA_WAIT + +S_DATA_WAIT + ACK -> 转交北向 -> S_WAIT_DATA + NAK -> 转交北向 -> S_WAIT_DATA + CAN -> 转交北向 -> S_ABORT + +S_EOT1_TX + UART发送完成 -> S_EOT1_WAIT + +S_EOT1_WAIT + NAK -> 转交北向 -> S_WAIT_EOT2 + +S_WAIT_EOT2 + 收到第二次EOT -> S_EOT2_TX + +S_EOT2_TX + UART发送完成 -> S_EOT2_WAIT + +S_EOT2_WAIT + ACK -> 转交北向 + C -> 转交北向 -> S_WAIT_END_SOH + +S_WAIT_END_SOH + 收到结束空SOH -> S_END_TX + +S_END_TX + UART发送完成 -> S_END_WAIT + +S_END_WAIT + ACK -> 转交北向 -> S_TRANSFER_DONE + +S_TRANSFER_DONE + 收到退出请求 -> S_JUMP_TX + +S_JUMP_TX + UART发送完成 -> S_JUMP_WAIT + +S_JUMP_WAIT + ACK -> 确认已转交北向 -> 恢复轮询 -> S_IDLE + NAK/超时 -> S_ABORT + +S_ABORT + 清理应答队列和半帧,解除通道锁,恢复轮询 -> S_IDLE +``` + +## 5. 发送完成与计时起点 + +`chrg_send_sou_upadata_data()` 返回成功只能表示帧已提交到发送线程,不能表示 UART 已发送完成。 + +南向必须区分: + +1. 提交成功; +2. 通道切换成功; +3. UART 开始发送; +4. UART 发送完成; +5. 功率板返回应答。 + +所有应答超时必须从 UART 发送完成时开始计算,不能从放入缓存时开始。建议: + +| 阶段 | 超时 | +| --- | ---: | +| 进入升级并擦除 Flash | 15 秒 | +| 元数据应答 | 10 秒 | +| 普通数据帧应答 | 10 秒 | +| EOT/结束空包应答 | 10 秒 | +| 跳转 App 应答 | 5 秒 | + +## 6. 南向应答队列 + +功率板应答是单字节流,必须使用有序队列保存 `ACK/NAK/C/CAN`,不能使用单个覆盖变量。 + +- `ACK + C` 必须保留两个字节和原顺序。 +- 队列满时可优先丢弃等待阶段重复出现的周期 `C`,不能丢弃 ACK、NAK 或 CAN。 +- 切换通道、进入会话、退出会话前必须清空旧应答。 +- 等待当前帧 ACK 时收到周期 `C`,可以暂存或忽略,但不能将其误判为当前帧成功。 +- 普通 Modbus 调试文本或其他字节不得进入 YMODEM 应答队列。 + +## 7. 重试和异常策略 + +YMODEM 数据帧的重试由北向 PC 负责。南向代理不得在收到 NAK 或应答超时后自行重发上一帧,否则会破坏第一次 EOT 的正常 NAK 时序,并可能在 ACK 丢失时重复写入。 + +控制帧也不能无条件重发:进入命令的 ACK 丢失但功率板已经进入 Boot 时,再发送 Modbus 控制帧会被当成 YMODEM 垃圾数据。处理方式: + +- 只有收到 ACK 才确认进入升级成功;随后收到 C 才确认功率板 Boot 已准备接收元数据。若只收到 C,应记录异常并终止本次会话,不能向北向伪造进入成功响应。 +- ACK 或 C 缺失时终止本次会话,要求重新复位/重新进入,不盲目重发控制帧。 +- 南向 NAK、CAN 或应答超时应明确上报北向;不得伪造 ACK。 +- 发送失败、通道切换失败或队列溢出时立即中止会话。 +- 北向取消或会话异常终止时,尽可能向功率板连续发送两个 CAN,再清理状态并恢复普通轮询。 + +## 8. 与功率板 Boot 的接口要求 + +功率板 Boot 必须满足: + +1. 控制帧可以跨多次 DMA/空闲中断接收并累计到完整 8 字节后匹配。 +2. 进入升级时擦除成功回复 `ACK + C`,失败回复 NAK。 +3. 支持标准 YMODEM 包号、反码和 CRC16 校验。 +4. 第一次 EOT 回复 NAK,第二次 EOT 回复 `ACK + C`,结束空 SOH 回复 ACK。 +5. 明确 App 起始地址、最大 UPK/package body 长度和 Flash 擦除范围;当前记录的 `0x08003800 + 0xB000` 为 44 KB,必须与链接脚本和功率板 UPK 分区规则一致。 +6. 跳转 App 前关闭中断、DMA、SysTick和相关外设,设置 MSP 与 VTOR。 +7. 处理进入控制帧后,先清除控制帧接收缓存和长度计数,再启动 YMODEM 接收;不得将控制帧的 8 字节与元数据 SOH 拼接。 +8. UART 接收只保留一条 DMA-to-idle 重启路径;禁止 UART IRQ 与接收回调同时中止或重复启动 DMA。 +9. 跳转控制帧匹配成功后,先发送 ACK 并等待发送完成,再执行 App 跳转。 + +## 9. 南向验证顺序 + +1. 逻辑分析仪确认进入帧完整发送,波特率和通道正确。 +2. 确认功率板完整接收 8 字节并完成 Flash 擦除。 +3. 确认线路依次出现 `06 43`,协议板应答队列也依次取到 `06 43`。 +4. 使用一个元数据包和一个 STX 包验证停止等待。 +5. 验证标准两次 EOT和结束空 SOH。 +6. 验证 CRC 错误时 NAK 能原样传到 PC,并由 PC 重发。 +7. 验证 ACK 丢失、半帧、超时、CAN、断电和超出功率板 UPK 分区容量的文件。 +8. 跳转后读取功率板新版本,确认升级真正成功。 +9. 连续发送 `ACK+C`、EOT、结束空 SOH 和跳转控制帧,确认 Boot 未丢字节、重复启动 DMA 或将控制帧拼入 YMODEM 帧。 + +## 10. 南向实施清单 + +1. 删除南向收到 NAK或超时后的内部 YMODEM重发。 +2. 增加独立的 EOT1、EOT2和结束空包状态。 +3. 以 UART发送完成事件启动应答计时。 +4. 保证 ACK、C 等应答有序排队和转交。 +5. 进入和退出控制帧禁止盲目重试。 +6. 升级期间锁定通道并完全停止普通轮询。 +7. 完成后恢复轮询并提供版本确认结果。 +8. 先修复 Boot:进入控制帧后清接收缓存、跳转前回 ACK、UART 单一 DMA 接收重启路径。 +9. 未实现 Boot 端 UPK 解包和最终完整性校验前,升级文件限定为裸 APP 镜像。