feat: 更新南向升级协议,修改升级流程逻辑

改为南向主动发送C,不再向南向发送进入和退出

1 升级进入/退出命令不再向南向发送控制帧,本地会话建立后立即响应成功
2 功率板Boot主动每500ms发送'C',协议板收到后转发给上位机
3 更新协议板版本寄存器为0x0143,版本号升级至V1.1.51
4 调整升级状态机,删除等待南向ACK的相关逻辑
5 更新相关文档,同步升级协议和状态机的修改内容
This commit is contained in:
wrh
2026-09-29 16:54:28 +08:00
parent 98f887eb36
commit 7eb30c1ab2
8 changed files with 56 additions and 150 deletions
+4 -5
View File
@@ -27,7 +27,7 @@
#include "stm32f4xx_hal.h"
#define HOLD_REG_FIRMWARE_VERSION 0x0300U /* 协议板固件版本,只读 */
#define CHRG_FIRMWARE_VERSION 0x0141U /* 当前协议板版本:V1.1.49 */
#define CHRG_FIRMWARE_VERSION 0x0143U /* 当前协议板版本:V1.1.51 */
rt_uint8_t ucDiscInBuf[(TOTAL_DISC_REGS+BITS_UCHAR-1)/BITS_UCHAR];
@@ -1403,10 +1403,9 @@ eMBException funYmodemUpadata (rt_uint8_t *reqFrame, rt_uint16_t reqLen,
return MB_EX_ILLEGAL_DATA_VALUE;
}
/* 保留原0x20响应内容,外层统一补设备地址和Modbus CRC。 */
resFrame[0] = reqFrame[0];
resFrame[1] = ACK;
*resLen = 2;
/* 成功时回显地址、功能码、保留字节、通道和操作,外层统一补CRC。 */
rt_memcpy(resFrame, reqFrame, 6);
*resLen = 6;
return MB_EX_NONE;
}
@@ -48,7 +48,6 @@
#define SOUTH_FRAME_DEBUG 0x00 // 南向UART原始收发帧
#define SOUTH_COMMAND_DEBUG 0x01 // 南向寄存器写命令
#define SOUTH_YMODEM_DEBUG 0x01 // 南向YMODEM回码
#define SOUTH_YMODEM_EXIT_APP 0x01 // 升级退出时发送南向跳转App帧
#define COMM_FRAME_DEBUG 0x00 // COM UART原始收发帧
#define COMM_DEBUG 0x00 // COM寄存器读写过程
#define COMM_TRIM_DEBUG 0x00 // COM校准参数解析
+2 -2
View File
@@ -14,8 +14,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 49U
#define CHRG_VERSION_STRING "V1.1.49"
#define CHRG_VERSION_PATCH 51U
#define CHRG_VERSION_STRING "V1.1.51"
/*
* Channel polling mode:
@@ -8,7 +8,6 @@
#include "chrg_def.h"
#include "chrg_thread.h"
#include "chrg_north.h"
#include "chrg_south.h"
#include "chrg_roll_sou.h"
#include "chrg_eload.h"
@@ -19,9 +18,7 @@
#define YMODEM_RX_FRAME_TIMEOUT_MS 1000U // PC分段数据拼帧超时
#define YMODEM_SESSION_IDLE_MS 15000U // 整个升级会话无有效数据超时
#define YMODEM_ENTER_TIMEOUT_MS 15000U // 功率板进入Boot及擦除等待时间
#define YMODEM_RESPONSE_TIMEOUT_MS 10000U // 南向单帧应答等待时间
#define YMODEM_PREPARE_DELAY_MS 200U // 切换升级模式后等待功率板重启稳定
typedef struct {
chrg_ymodem_proxy_state_t state; // 当前升级阶段
@@ -30,7 +27,6 @@ typedef struct {
rt_uint8_t current_packet; // 当前已转发帧的包号,仅用于日志
rt_uint8_t meta_ack_seen; // 元数据ACK已收到,后续才接受'C'
rt_uint8_t eot2_ack_seen; // 第二个EOT的ACK已收到
rt_uint8_t deferred_c; // 进入Boot时与ACK同批到达的'C'延后转发
rt_uint16_t rx_len; // 北向当前已拼接字节数
rt_uint16_t expected_len; // 根据SOH/STX计算的完整帧长度
rt_tick_t rx_tick; // 当前半帧起始时间
@@ -46,13 +42,6 @@ static chrg_ymodem_proxy_t g_proxy = {
.ch = 0xFFU
};
/* 功率板进入Boot和跳转App的固定Modbus控制帧。 */
static const rt_uint8_t g_enter_frame[8] =
{ID_ELOAD, 0x06, 0x00, 0x0F, 0x00, 0xFF, 0xF8, 0x6B};
#if SOUTH_YMODEM_EXIT_APP
static const rt_uint8_t g_exit_frame[8] =
{ID_ELOAD, 0x06, 0x00, 0x0F, 0x00, 0x0F, 0xF8, 0x2F};
#endif
static const rt_uint8_t g_cancel_frame[2] = {YM_CAN, YM_CAN};
static const char *chrg_ymodem_proxy_state_name(chrg_ymodem_proxy_state_t state)
@@ -245,7 +234,10 @@ static int chrg_ymodem_proxy_prepare_channel(rt_uint8_t ch)
roll->leng = 0U;
roll->updata_result = 0;
rt_mutex_release(thread->mutex);
rt_thread_mdelay(YMODEM_PREPARE_DELAY_MS);
if (chrg_south_switch((eIDX_SOU_CH)ch) != 0) {
chrg_ymodem_proxy_session_stop();
return -3;
}
chrg_eload_clear_ymodem_resp();
if (chrgsouth.rb != RT_NULL) rt_ringbuffer_reset(chrgsouth.rb);
return 0;
@@ -253,88 +245,38 @@ static int chrg_ymodem_proxy_prepare_channel(rt_uint8_t ch)
/*****************************************************************
函数名称: chrg_ymodem_proxy_enter
函数描述: 进入指定功率板的Boot升级模式
函数描述: 锁定指定功率板通道并等待Boot周期发送'C'
输入参数: ch:南向通道编号
返回说明: 0:收到功率板ACK <0:进入失败
其它说明: 仅在真实ACK到达后允许COM回复PC成功
返回说明: 0:本地升级会话已建立 <0:进入失败
其它说明: 不向南向发送进入指令,建立会话后立即允许COM回复PC成功
*****************************************************************/
int chrg_ymodem_proxy_enter(rt_uint8_t ch)
{
rt_tick_t start;
rt_uint8_t response;
if (g_proxy.state != YMODEM_PROXY_IDLE) return -1;
if (chrg_ymodem_proxy_prepare_channel(ch) != 0) return -2;
chrg_ymodem_proxy_reset();
g_proxy.ch = ch;
g_proxy.state = YMODEM_PROXY_ENTERING;
g_proxy.state = YMODEM_PROXY_WAIT_C;
g_proxy.activity_tick = rt_tick_get();
if (chrg_send_sou_upadata_data(g_enter_frame, sizeof(g_enter_frame)) != 0) {
chrg_ymodem_proxy_session_stop();
return -3;
}
start = rt_tick_get();
while ((rt_tick_t)(rt_tick_get() - start) <
rt_tick_from_millisecond(YMODEM_ENTER_TIMEOUT_MS)) {
response = chrg_eload_get_ymodem_resp();
if (response == ACK) {
g_proxy.state = YMODEM_PROXY_WAIT_C;
g_proxy.activity_tick = rt_tick_get();
return 0;
}
if (response == ACK_C) g_proxy.deferred_c = 1U;
if ((response == NAK) || (response == YM_CAN)) break;
rt_thread_mdelay(1U);
}
chrg_ymodem_proxy_session_stop();
return -4;
return 0;
}
/*****************************************************************
函数名称: chrg_ymodem_proxy_exit
函数描述: 跳转功率板App并恢复普通南向轮询
函数描述: 立即结束本地升级会话并恢复普通南向轮询
输入参数: ch:当前升级通道
返回说明: 0:收到跳转ACK <0:状态或通信失败
其它说明: 升级会话任意阶段均可退出,退出后强制刷新版本
返回说明: 0:退出成功 <0:通道错误
其它说明: 不向南向发送退出指令,也不等待南向回码
*****************************************************************/
int chrg_ymodem_proxy_exit(rt_uint8_t ch)
{
#if SOUTH_YMODEM_EXIT_APP
rt_tick_t start;
rt_uint8_t response;
#endif
if (ch >= TOTAL_SOU_CHS) return -1;
if (g_proxy.state == YMODEM_PROXY_IDLE) {
if (chrg_ymodem_proxy_prepare_channel(ch) != 0) return -2;
g_proxy.ch = ch;
} else if (ch != g_proxy.ch) {
if ((g_proxy.state != YMODEM_PROXY_IDLE) && (ch != g_proxy.ch)) {
return -1;
}
#if SOUTH_YMODEM_EXIT_APP
g_proxy.state = YMODEM_PROXY_EXITING;
chrg_eload_clear_ymodem_resp();
if (chrg_send_sou_upadata_data(g_exit_frame, sizeof(g_exit_frame)) != 0)
return -3;
start = rt_tick_get();
while ((rt_tick_t)(rt_tick_get() - start) <
rt_tick_from_millisecond(YMODEM_RESPONSE_TIMEOUT_MS)) {
response = chrg_eload_get_ymodem_resp();
if (response == ACK) {
chrg_roll_sou_version_invalidate(ch);
chrg_ymodem_proxy_session_stop();
return 0;
}
if ((response == NAK) || (response == YM_CAN)) break;
rt_thread_mdelay(1U);
}
chrg_ymodem_proxy_session_stop();
return -4;
#else
chrg_roll_sou_version_invalidate(ch);
chrg_ymodem_proxy_session_stop();
return 0;
#endif
}
/* 根据当前阶段校验完整帧,校验成功才允许提交南向。 */
@@ -542,10 +484,6 @@ rt_uint16_t chrg_ymodem_proxy_poll(rt_uint8_t *output,
chrg_ymodem_proxy_session_stop();
return 0U;
}
if (g_proxy.deferred_c && (g_proxy.state == YMODEM_PROXY_WAIT_C)) {
g_proxy.deferred_c = 0U;
chrg_ymodem_proxy_handle_south(ACK_C);
}
response = chrg_eload_get_ymodem_resp();
if (response != 0U) chrg_ymodem_proxy_handle_south(response);
if ((g_proxy.expected_len != 0U) && (g_proxy.rx_tick != 0U) &&
@@ -9,7 +9,7 @@
typedef enum {
YMODEM_PROXY_IDLE = 0, // 未进入升级模式
YMODEM_PROXY_ENTERING, // 已发送进入Boot控制帧,等待ACK
YMODEM_PROXY_ENTERING, // 保留状态
YMODEM_PROXY_WAIT_C, // 进入Boot成功,等待功率板'C'
YMODEM_PROXY_WAIT_META, // 等待PC发送包号0元数据SOH
YMODEM_PROXY_WAIT_META_RESP, // 元数据已转发,等待南向ACK和'C'
@@ -21,13 +21,13 @@ typedef enum {
YMODEM_PROXY_WAIT_END_SOH, // 等待包号0且数据全零的结束SOH
YMODEM_PROXY_WAIT_END_ACK, // 结束SOH已转发,等待南向ACK
YMODEM_PROXY_TRANSFER_DONE, // YMODEM传输完成,等待0x20退出命令
YMODEM_PROXY_EXITING, // 已发送跳转App控制帧,等待ACK
YMODEM_PROXY_EXITING, // 保留状态
YMODEM_PROXY_ABORT // 异常取消,等待CAN发送完成
} chrg_ymodem_proxy_state_t;
/* 锁定目标通道并进入功率板Boot,收到真实ACK后才返回成功。 */
/* 锁定目标通道并等待功率板Boot周期发送'C'。 */
extern int chrg_ymodem_proxy_enter(rt_uint8_t ch);
/* 仅传输完成后允许跳转App,收到ACK后恢复普通轮询。 */
/* 立即结束本地升级会话并恢复普通轮询。 */
extern int chrg_ymodem_proxy_exit(rt_uint8_t ch);
/* 累计PC分段数据,形成完整YMODEM帧后校验并提交南向。 */
extern int chrg_ymodem_proxy_feed_north(const rt_uint8_t *data,
+17
View File
@@ -1,5 +1,22 @@
# 修改日志
## V1.1.51 - 2026-09-29
### 南向Boot主动握手
- `0x20`进入命令仅锁定目标通道并立即回码,不再向南向发送进入升级控制帧或等待ACK。
- 南向Boot每500ms发送的 `C` 由协议板转发给上位机,收到首个 `C` 后进入YMODEM元数据阶段。
- `0x20`退出命令立即结束本地升级会话并回码,不再发送南向跳转App帧或等待ACK。
- 协议板版本寄存器 `0x0300` 更新为 `0x0143`。
## V1.1.50 - 2026-09-29
### 南向升级命令回码格式
- `0x20` 进入和退出升级成功后,回显请求前6字节并重新附加Modbus CRC。
- 成功回码保留功能码、目标通道和进入/退出操作,不再使用4字节 `地址+ACK+CRC` 格式。
- 协议板版本寄存器 `0x0300` 更新为 `0x0142`。
## V1.1.49 - 2026-09-29
### 南向升级随时退出
+9 -23
View File
@@ -4,13 +4,13 @@
已在 `chrg_ymodem_proxy.c/.h` 实现独立的非阻塞转发状态机,并接入COM北向入口和现有南向发送线程。当前实现包括:
- 指定通道进入/退出升级,以功率板真实ACK决定成功响应。
- 指定通道进入/退出升级命令完成本地状态切换后立即成功响应。
- SOH/STX分段拼帧、包号反码和CRC-16/XMODEM校验。
- `.upk` 元数据、连续数据包号、包号1固定STX和结束空SOH校验。
- 两次EOT独立状态,ACK/NAK/C/CAN均来自南向功率板。
- NAK原样转发给PC,协议板不再自动重发数据帧或EOT。
- 半帧1秒超时、南向应答10秒超时和会话15秒空闲取消。
- 跳转App确认后恢复普通轮询,并强制目标通道重新读取版本。
- 退出命令恢复普通轮询,并强制目标通道重新读取版本。
## 1. 范围与原则
@@ -42,24 +42,23 @@ Byte 6 CRC_L
Byte 7 CRC_H
```
协议板必须完成以下操作后才能回复成功:
协议板必须完成以下操作后立即回复成功:
1. 校验地址、长度、通道和 Modbus CRC。
2. 锁定目标通道并停止普通南向轮询。
3. 南向发送进入升级控制帧。
4. 收到功率板 Boot 的 ACK;若同时收到 `C`,必须保留并在北向响应完成后转发。
3. 清理历史南向回码,等待功率板 Boot 每500ms主动发送 `C`。
成功响应:
```text
协议板地址 + 0x06 + CRC_L + CRC_H
协议板地址 + 0x20 + 0x00 + 0x00 + 功率板通道号 + 0xFF + CRC_L + CRC_H
```
广播地址 `0x00` 不回复。失败使用现有 Modbus 异常响应,不得进入原始 YMODEM 模式。
### 2.2 退出升级/跳转 App
### 2.2 退出升级
在升级模式中,PC 发送同格式的 8 字节命令,Byte 5 为 `0x00`。协议板南向发送跳转 App 控制帧;功率板 Boot 必须先发送 `ACK` 并确认 UART 发送完成,再执行跳转。协议板收到该 ACK 后退出升级模式并恢复普通轮询。
PC发送同格式的8字节命令,Byte 5为 `0x00`。协议板立即结束本地升级会话、恢复普通轮询并原样回显退出命令,不向南向发送控制帧,也不等待南向回码。
结束空 SOH 获得 ACK 只表示 YMODEM 传输结束;PC 仍须发送退出命令,并在恢复通信后读取功率板版本确认升级结果。
@@ -101,15 +100,8 @@ CRC 使用 CRC-16/XMODEM:`poly=0x1021`、`init=0x0000`、不反射、`xorout=0
```text
N_IDLE
收到合法 0x20/进入命令
-> N_ENTERING
N_ENTERING
南向确认进入 Boot
-> 回复 0x20 成功
-> N_WAIT_C
失败/超时
-> 回复异常
-> N_IDLE
N_WAIT_C
收到南向 C -> 原样转发 -> N_WAIT_META
@@ -159,10 +151,7 @@ N_WAIT_END_ACK
南向 ACK -> 转发 ACK -> N_TRANSFER_DONE
N_TRANSFER_DONE
收到合法 0x20/退出命令 -> N_EXITING
N_EXITING
南向跳转确认 -> 回复命令成功 -> 恢复轮询 -> N_VERIFY
收到合法 0x20/退出命令 -> 回复命令成功 -> 恢复轮询 -> N_VERIFY
N_VERIFY
PC通过恢复后的正常协议读取功率板版本
@@ -174,9 +163,8 @@ N_VERIFY
```text
PC -> 协议板 : 0x20进入命令
协议板 -> 功率板 : 进入升级控制帧
功率板 -> 协议板 : ACK, C
协议板 -> PC : 0x20成功响应
功率板 -> 协议板 : C(Boot每500ms主动发送)
协议板 -> PC : C
PC -> 协议板 : SOH包0元数据
@@ -206,8 +194,6 @@ PC -> 协议板 : SOH包0,Data全0
协议板 -> PC : ACK
PC -> 协议板 : 0x20退出命令
协议板 -> 功率板 : 跳转App控制帧
功率板 -> 协议板 : ACK(发送完成后才跳转App)
协议板 -> PC : 0x20成功响应
协议板 : 恢复轮询并刷新功率板版本
PC -> 协议板 : 通过正常协议查询功率板版本并确认升级结果
+7 -40
View File
@@ -8,37 +8,17 @@
功率板UPK采用与协议板Boot相同的“1024字节首块”结构:前96字节为UPK逻辑头,后928字节固定为 `0x00`,package body从文件偏移 `0x400` 开始。协议板只校验YMODEM帧长度、包号反码和CRC16;UPK头、大小、加密和CRC由功率板Boot解析。
## 2. 串口和控制帧
## 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,避免协议板因未收到应答而超时。
协议板不再向南向发送进入Boot或跳转App控制帧。功率板Boot启动后每500ms主动发送 `43`,协议板收到后转发北向并开始YMODEM流程。北向退出命令仅结束协议板本地升级会话。
## 3. 南向帧和应答映射
| 协议板发送 | 功率板 Boot 正常回复 | 协议板处理 |
| --- | --- | --- |
| 进入升级控制帧 | `ACK + C` | ACK确认进入,C进入应答队列,按顺序交北向 |
| Boot主动发送 | `C` | 转发北向并进入元数据等待阶段 |
| 元数据 SOH/包0 | `ACK + C` | 两字节均按顺序交北向 |
| UPK头区 STX/包1 | `ACK`或`NAK` | Boot解析96字节头并检查928字节填充,不写APP区 |
| UPK包体 STX/包2起 | `ACK`或`NAK` | 从APP首地址开始安装,仅接收`pkg_size`范围内的数据 |
@@ -48,21 +28,15 @@
| 第二次 EOT | `ACK + C` | 两字节均按顺序交北向 |
| 结束空 SOH/包0 | `ACK` | 交北向,标记传输完成 |
| CAN | `ACK`或无回复 | 停止会话并清理缓存 |
| 跳转 App 控制帧 | `ACK` | 退出升级模式并恢复轮询 |
## 4. 南向状态机
```text
S_IDLE
北向进入请求 -> 锁通道、停轮询 -> S_ENTER_TX
北向进入请求 -> 锁通道、停轮询 -> S_WAIT_C
S_ENTER_TX
UART发送完成 -> S_ENTER_WAIT
S_ENTER_WAIT
ACK -> 记录进入成功
C -> 记录Boot就绪 -> S_WAIT_META
NAK/超时 -> S_ABORT
S_WAIT_C
Boot主动发送C -> 转交北向 -> S_WAIT_META
S_WAIT_META
收到北向完整元数据帧 -> S_META_TX
@@ -121,14 +95,7 @@ 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_IDLE
S_ABORT
清理应答队列和半帧,解除通道锁,恢复轮询 -> S_IDLE