refactor: 清理旧YMODEM升级代码,更新版本至V1.1.43

- 删除chrg_comm.h和chrg_def.h中的旧YMODEM状态枚举及注释代码
- 删除chrg_comm.c中已废弃的旧YMODEM重发和会话管理代码
- 更新协议板版本寄存器编码为十进制143
- 新增chrg_ymodem_proxy模块的详细中文注释,明确升级代理职责
- 更新changelog记录V1.1.43版本变更
- 删除无用的Allegro日志文件,优化PCB设计相关文档

本次提交将YMODEM升级功能重构为独立代理模块,不再依赖旧版自动重发逻辑,提升升级流程的可维护性和可靠性。
This commit is contained in:
wrh
2026-09-28 20:51:04 +08:00
parent c34c537bfd
commit 9254c82547
13 changed files with 296 additions and 910 deletions
+3 -738
View File
@@ -27,7 +27,7 @@
#include "stm32f4xx_hal.h"
#define HOLD_REG_FIRMWARE_VERSION 0x0300U /* 协议板固件版本,只读 */
#define CHRG_FIRMWARE_VERSION 0x013AU /* 当前协议板版本:V1.1.42 */
#define CHRG_FIRMWARE_VERSION 143U /* 当前协议板版本:V1.1.43,按十进制版本号编码 */
rt_uint8_t ucDiscInBuf[(TOTAL_DISC_REGS+BITS_UCHAR-1)/BITS_UCHAR];
@@ -38,36 +38,6 @@ rt_uint16_t usRegProBuf[TOTAL_HOLD_ALL_REGS]; //北向protcal???
//rt_uint16_t usRegTestBuf[TOTAL_TEST_REGS];
rt_uint8_t g_proto_pData[80] = {0};//上位机协议指令,控制四个通道;二维
rt_uint16_t g_proto_reqLen = 0;
#if 0 /* 旧YMODEM重发状态机已由chrg_ymodem_proxy替代。 */
rt_uint8_t mobude_ymodem_buf[MB_YMODEM_DATA_SIZE] = {0};//南向功率板升级缓冲区
rt_uint8_t mobude_ymodem_buf_2[MB_YMODEM_DATA_SIZE] = {0};//南向功率板升级缓冲区
#define YMODEM_C_PERIOD_MS 1000
#define YMODEM_META_ACK_TIMEOUT_MS 3000
#define YMODEM_DATA_ACK_TIMEOUT_MS 1000
#define YMODEM_RX_FRAME_TIMEOUT_MS 300
#define YMODEM_MAX_RETRIES 3
/* COM侧升级会话状态:接收缓存负责拼帧,备份缓存负责超时/NAK重发。 */
static rt_uint16_t g_ymodem_rx_len = 0;
static rt_uint16_t g_ymodem_expected_len = 0;
static rt_uint16_t g_ymodem_last_len = 0;
static rt_uint8_t g_ymodem_waiting_ack = 0;
static rt_uint8_t g_ymodem_retry_count = 0;
static rt_uint8_t g_ymodem_eot_done = 0;
static rt_tick_t g_ymodem_send_tick = 0;
static rt_tick_t g_ymodem_c_tick = 0;
static rt_tick_t g_ymodem_rx_tick = 0;
static rt_uint8_t g_ymodem_deferred_resp = 0;
static rt_uint32_t g_ymodem_frame_count = 0;
static rt_uint32_t g_ymodem_send_count = 0;
static const rt_uint8_t g_ymodem_update_frame[8] =
{ID_ELOAD, 0x06, 0x00, 0x0F, 0x00, 0xFF, 0xF8, 0x6B};
static const rt_uint8_t g_ymodem_jump_frame[8] =
{ID_ELOAD, 0x06, 0x00, 0x0F, 0x00, 0x0F, 0xF8, 0x2F};
static void chrg_ymodem_session_stop(void);
#endif
/* Modbus 影子寄存器: 0x06/0x10 写操作暂存到此, PARAM_EFFECT 生效后复制到实际 pNOR->sw */
static struct chrg_switch_t g_shadow_sw[TOTAL_NOR_CHS];
static rt_uint8_t g_shadow_dirty[TOTAL_NOR_CHS] = {0}; /* 0=已生效 1=待生效,防止重复覆盖 */
@@ -422,19 +392,6 @@ eMBErrorCode mb_reg_test_cb (rt_uint8_t *req, rt_uint8_t *pucRegBuffer, rt_uint1
return eStatus;
}
/*****************************************************************
函数名称: mb_ymodem_updata_cb
*****************************************************************/
eMBErrorCode mb_ymodem_updata_cb (rt_uint8_t *state, rt_uint8_t *pucRegBuffer)
{
if ((state == RT_NULL) || (pucRegBuffer == RT_NULL)) {
return MB_EINVAL;
}
/* Ymodem_No的枚举值为0xFF,不能用只覆盖0和1的switch生成响应。 */
pucRegBuffer[0] = (*state == Ymodem_No) ? ACK_C : ACK;
return MB_ENOERR;
}
eMBErrorCode mb_reg_coils_cb (rt_uint8_t *req, rt_uint8_t *pucRegBuffer, rt_uint16_t usAddress,
rt_uint16_t usNCoils, eMBRegisterMode eMode)
{
@@ -1417,227 +1374,6 @@ eMBException funcTrimELoad (rt_uint8_t *reqFrame, rt_uint16_t reqLen,
其它说明: -
*****************************************************************/
#if 0 /* 旧YMODEM时序只作参考,禁止编译和运行。 */
Ymodem_state ystate = Ymodem_No;
rt_uint8_t s_first_soh_rcvd = 0;
/*****************************************************************
函数名称: chrg_ymodem_protocol_reset
函数描述: 清空COM侧Ymodem拼帧、重发和握手状态
输入参数: -
输出参数: -
返回说明: -
其它说明: 不改变南向轮询模式和目标通道
*****************************************************************/
static void chrg_ymodem_protocol_reset(void)
{
rt_memset(mobude_ymodem_buf, 0, sizeof(mobude_ymodem_buf));
rt_memset(mobude_ymodem_buf_2, 0, sizeof(mobude_ymodem_buf_2));
g_ymodem_rx_len = 0;
g_ymodem_expected_len = 0;
g_ymodem_last_len = 0;
g_ymodem_waiting_ack = 0;
g_ymodem_retry_count = 0;
g_ymodem_eot_done = 0;
g_ymodem_send_tick = 0;
g_ymodem_c_tick = rt_tick_get();
g_ymodem_rx_tick = 0;
g_ymodem_deferred_resp = 0;
g_ymodem_frame_count = 0;
g_ymodem_send_count = 0;
s_first_soh_rcvd = 0;
}
static const char *chrg_ymodem_state_name(Ymodem_state state)
{
switch (state) {
case Ymodem_Wait:
return "WAIT";
case Ymodem_Rec_Start:
return "START";
case Ymodem_Rec_1024:
return "DATA";
case Ymodem_Rec_End:
return "EOT";
case Ymodem_Rec_Finish:
return "FINISH";
default:
return "IDLE";
}
}
static const char *chrg_ymodem_resp_name(rt_uint8_t response)
{
switch (response) {
case ACK:
return "ACK";
case NAK:
return "NAK";
case ACK_C:
return "C";
case YM_CAN:
return "CAN";
default:
return "NONE";
}
}
static rt_uint8_t chrg_ymodem_take_south_resp(void)
{
rt_uint8_t resp;
if (g_ymodem_deferred_resp != 0) {
resp = g_ymodem_deferred_resp;
g_ymodem_deferred_resp = 0;
return resp;
}
return chrg_eload_get_ymodem_resp();
}
static int chrg_ymodem_wait_south_ack(rt_uint32_t timeout_ms)
{
rt_tick_t start = rt_tick_get();
rt_uint8_t resp;
while ((rt_tick_t)(rt_tick_get() - start) <
rt_tick_from_millisecond(timeout_ms)) {
resp = chrg_eload_get_ymodem_resp();
if (resp == ACK) {
rt_kprintf("[UPG] south=%s state=%s\r\n",
chrg_ymodem_resp_name(resp), chrg_ymodem_state_name(ystate));
return 0;
}
if (resp == NAK || resp == YM_CAN) {
rt_kprintf("[UPG] south=%s state=%s\r\n",
chrg_ymodem_resp_name(resp), chrg_ymodem_state_name(ystate));
return -2;
}
if (resp != 0 && g_ymodem_deferred_resp == 0) {
g_ymodem_deferred_resp = resp;
}
rt_thread_mdelay(1);
}
return -1;
}
static int chrg_ymodem_send_control_frame(const rt_uint8_t *frame)
{
rt_uint8_t retry;
int wait_result;
const char *control_name = (frame[5] == 0xFF) ? "ENTER" : "JUMP";
rt_kprintf("[UPG] CTRL %s begin\r\n", control_name);
for (retry = 0; retry < YMODEM_MAX_RETRIES; retry++) {
int send_result = chrg_send_sou_upadata_data(frame, 8);
rt_kprintf("[UPG] CTRL %s try=%u tx=%d\r\n",
control_name, (unsigned int)(retry + 1), send_result);
if (send_result == 0) {
wait_result = chrg_ymodem_wait_south_ack(YMODEM_META_ACK_TIMEOUT_MS);
if (wait_result == 0) {
rt_kprintf("[UPG] CTRL %s done\r\n", control_name);
return 0;
}
rt_kprintf("[UPG] CTRL %s wait=%d\r\n", control_name, wait_result);
}
}
rt_kprintf("[UPG] CTRL %s failed\r\n", control_name);
return -1;
}
static void chrg_ymodem_session_stop(void);
/*****************************************************************
函数名称: chrg_ymodem_session_start
函数描述: 停止普通南向请求并进入指定通道的升级透传模式
输入参数: ch:需要升级的南向功率板通道
输出参数: -
返回说明: 0:成功 <0:线程互斥锁未就绪或加锁失败
其它说明: 不修改chrgnorth.enable和chrgsouth.enable,升级结束后恢复自动轮询
*****************************************************************/
static int chrg_ymodem_session_start(eIDX_SOU_CH ch)
{
struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU];
struct chrg_rollsou_t *pROLL = &chrgrollsou;
struct chrg_south_t *pSOU = &chrgsouth;
if ((ch >= TOTAL_SOU_CHS) || (pTHR->mutex == RT_NULL)) {
return -1;
}
if (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(100)) != RT_EOK) {
return -2;
}
/* 先阻止南向线程继续发普通Modbus请求,等待已发请求退出邮箱等待。 */
pROLL->run = RUN_UPDATA;
pROLL->ch = (rt_uint8_t)ch;
pROLL->send_flag = 0;
pROLL->leng = 0;
pROLL->updata_result = 0;
rt_mutex_release(pTHR->mutex);
rt_thread_mdelay(25);
chrg_eload_clear_ymodem_resp();
if (pSOU->rb != RT_NULL) {
rt_ringbuffer_reset(pSOU->rb);
}
chrg_ymodem_protocol_reset();
rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER);
pROLL->mode = 1;
pROLL->run = RUN_UPDATA;
pROLL->ch = (rt_uint8_t)ch;
rt_mutex_release(pTHR->mutex);
ystate = Ymodem_Wait;
rt_kprintf("[UPG] START ch=%u mode=%u run=%u state=%s\r\n",
(unsigned int)ch, (unsigned int)pROLL->mode,
(unsigned int)pROLL->run, chrg_ymodem_state_name(ystate));
/* 功率板完成擦除后才返回ACK,并立即开始发送C。 */
if (chrg_ymodem_send_control_frame(g_ymodem_update_frame) != 0) {
chrg_ymodem_session_stop();
return -3;
}
return 0;
}
/*****************************************************************
函数名称: chrg_ymodem_session_stop
函数描述: 退出南向升级透传模式并恢复普通自动轮询
输入参数: -
输出参数: -
返回说明: -
其它说明: 清除残留升级应答,避免其被普通Modbus解析器误识别
*****************************************************************/
static void chrg_ymodem_session_stop(void)
{
struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU];
struct chrg_rollsou_t *pROLL = &chrgrollsou;
struct chrg_south_t *pSOU = &chrgsouth;
rt_kprintf("[UPG] STOP state=%s rx_frames=%lu tx_frames=%lu retries=%u eot=%u\r\n",
chrg_ymodem_state_name(ystate),
(unsigned long)g_ymodem_frame_count,
(unsigned long)g_ymodem_send_count,
(unsigned int)g_ymodem_retry_count,
(unsigned int)g_ymodem_eot_done);
if (pTHR->mutex != RT_NULL) {
rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER);
pROLL->mode = 0;
pROLL->send_flag = 0;
pROLL->leng = 0;
pROLL->updata_result = 0;
pROLL->run = RUN_AUTO;
rt_mutex_release(pTHR->mutex);
}
chrg_eload_clear_ymodem_resp();
if (pSOU->rb != RT_NULL) {
rt_ringbuffer_reset(pSOU->rb);
}
chrg_ymodem_protocol_reset();
ystate = Ymodem_No;
}
#endif
eMBException funYmodemUpadata (rt_uint8_t *reqFrame, rt_uint16_t reqLen,
rt_uint8_t *resFrame, rt_uint16_t *resLen)
@@ -1759,450 +1495,6 @@ static int chrg_comm_parse_msg (struct chrg_comm_t *pCOMM)
return ret;
}
#if 0 /* 旧版协议板自动重发实现,禁止编译。 */
/*
* 旧版Ymodem处理代码保留在下方注释中作为历史参考。当前实现使用完整拼帧、
* CRC校验和停止等待重发机制,不再依赖一次串口接收刚好得到一整帧。
*/
/*****************************************************************
函数名称: chrg_ymodem_frame_size
函数描述: 根据Ymodem控制字符取得一帧应接收的总长度
输入参数: header:Ymodem帧头或控制字符
输出参数: -
返回说明: 133:SOH帧 1029:STX帧 1:EOT/CAN 0:无效字符
其它说明: -
*****************************************************************/
static rt_uint16_t chrg_ymodem_frame_size(rt_uint8_t header)
{
if (header == SOH) {
return YMODEM_SOH_SIZE;
}
if (header == STX) {
return YMODEM_STX_SIZE;
}
if ((header == EOT) || (header == YM_CAN)) {
return 1;
}
return 0;
}
/*****************************************************************
函数名称: chrg_ymodem_crc16
函数描述: 计算Ymodem数据区使用的CRC16-XMODEM校验值
输入参数: data:数据区地址 length:数据区长度
输出参数: -
返回说明: CRC16-XMODEM校验值
其它说明: 初值0,多项式0x1021,结果按高字节在前写入Ymodem帧
*****************************************************************/
static rt_uint16_t chrg_ymodem_crc16(const rt_uint8_t *data, rt_uint16_t length)
{
rt_uint16_t crc = 0;
rt_uint16_t i;
rt_uint8_t bit;
for (i = 0; i < length; i++) {
crc ^= (rt_uint16_t)data[i] << 8;
for (bit = 0; bit < 8; bit++) {
crc = (crc & 0x8000) ? (rt_uint16_t)((crc << 1) ^ 0x1021) :
(rt_uint16_t)(crc << 1);
}
}
return crc;
}
/*****************************************************************
函数名称: chrg_ymodem_frame_valid
函数描述: 校验Ymodem数据帧长度、包号反码和数据区CRC
输入参数: frame:完整Ymodem帧 length:帧总长度
输出参数: -
返回说明: RT_TRUE:有效 RT_FALSE:无效
其它说明: EOT和CAN为单字节控制帧,不包含包号与CRC
*****************************************************************/
static rt_bool_t chrg_ymodem_frame_valid(const rt_uint8_t *frame, rt_uint16_t length)
{
rt_uint16_t data_len;
rt_uint16_t frame_crc;
if ((frame == RT_NULL) || (length != chrg_ymodem_frame_size(frame[0]))) {
return RT_FALSE;
}
if ((frame[0] == EOT) || (frame[0] == YM_CAN)) {
return RT_TRUE;
}
if ((rt_uint8_t)(frame[1] + frame[2]) != 0xFF) {
return RT_FALSE;
}
data_len = (frame[0] == SOH) ? 128 : 1024;
frame_crc = ((rt_uint16_t)frame[length - 2] << 8) | frame[length - 1];
return chrg_ymodem_crc16(&frame[YMODEM_DATA_OFFSET], data_len) == frame_crc;
}
/*****************************************************************
函数名称: chrg_ymodem_resend_last
函数描述: 将最近一次完整Ymodem帧重新提交给南向发送线程
输入参数: -
输出参数: -
返回说明: 0:提交成功 -1:无可重发帧 其它:南向提交错误
其它说明: 仅在NAK或等待应答超时时调用,始终使用原帧实际长度
*****************************************************************/
static int chrg_ymodem_resend_last(void)
{
int ret;
if (g_ymodem_last_len == 0) {
return -1;
}
ret = chrg_send_sou_upadata_data(mobude_ymodem_buf_2, g_ymodem_last_len);
if (ret == 0) {
g_ymodem_send_count++;
g_ymodem_retry_count++;
g_ymodem_send_tick = rt_tick_get();
g_ymodem_waiting_ack = 1;
rt_kprintf("[UPG] RETX south frame=%02X pkt=%u len=%u count=%lu retry=%u state=%s\r\n",
mobude_ymodem_buf_2[0], (unsigned int)mobude_ymodem_buf_2[1],
(unsigned int)g_ymodem_last_len,
(unsigned long)g_ymodem_send_count,
(unsigned int)g_ymodem_retry_count,
chrg_ymodem_state_name(ystate));
}
return ret;
}
/*****************************************************************
函数名称: chrg_ymodem_process
函数描述: 累计COM串口分段数据,形成完整Ymodem帧后提交到南向线程
输入参数: data:本次串口数据 length:本次数据长度
输出参数: -
返回说明: 0:处理完成 -2:仍在等待上一帧应答 -3:帧校验失败 -4:提交失败
其它说明: 一帧可由多次串口接收组成,只有完整且校验通过后才会发送到功率板
*****************************************************************/
static int chrg_ymodem_process(const rt_uint8_t *data, rt_uint16_t length)
{
rt_uint16_t offset = 0;
rt_uint16_t copy_len;
int ret;
if ((data == RT_NULL) || (length == 0)) {
return 0;
}
while (offset < length) {
if (g_ymodem_waiting_ack != 0) {
return -2;
}
if (g_ymodem_expected_len == 0) {
g_ymodem_expected_len = chrg_ymodem_frame_size(data[offset]);
if (g_ymodem_expected_len == 0) {
offset++;
continue;
}
g_ymodem_rx_len = 0;
g_ymodem_rx_tick = rt_tick_get();
}
copy_len = g_ymodem_expected_len - g_ymodem_rx_len;
if (copy_len > (length - offset)) {
copy_len = length - offset;
}
rt_memcpy(&mobude_ymodem_buf[g_ymodem_rx_len], &data[offset], copy_len);
g_ymodem_rx_len += copy_len;
offset += copy_len;
if (g_ymodem_rx_len < g_ymodem_expected_len) {
continue;
}
if (!chrg_ymodem_frame_valid(mobude_ymodem_buf, g_ymodem_rx_len)) {
rt_kprintf("[UPG] RX invalid header=0x%02X pkt=%u len=%u state=%s\r\n",
mobude_ymodem_buf[0], (unsigned int)mobude_ymodem_buf[1],
(unsigned int)g_ymodem_rx_len,
chrg_ymodem_state_name(ystate));
g_ymodem_rx_len = 0;
g_ymodem_expected_len = 0;
g_ymodem_rx_tick = 0;
return -3;
}
g_ymodem_last_len = g_ymodem_rx_len;
rt_memcpy(mobude_ymodem_buf_2, mobude_ymodem_buf, g_ymodem_last_len);
if (mobude_ymodem_buf[0] == SOH) {
if (g_ymodem_eot_done != 0) {
/* 第二次EOT之后的0号空SOH是Ymodem收尾帧,只回复ACK。 */
ystate = Ymodem_Rec_Finish;
} else if ((mobude_ymodem_buf[1] == 0) && (s_first_soh_rcvd == 0)) {
/* 只有传输开始时的0号SOH是文件名/大小帧,需要回复ACK+C。 */
ystate = Ymodem_Rec_Start;
s_first_soh_rcvd = 1;
} else {
/* 包号非0的SOH是普通128字节固件数据帧,只回复ACK。 */
ystate = Ymodem_Rec_1024;
}
} else if (mobude_ymodem_buf[0] == STX) {
ystate = Ymodem_Rec_1024;
} else if (mobude_ymodem_buf[0] == EOT) {
ystate = Ymodem_Rec_End;
} else {
ystate = Ymodem_Rec_Finish;
}
g_ymodem_frame_count++;
rt_kprintf("[UPG] RX frame=%02X pkt=%u len=%u total=%lu state=%s\r\n",
mobude_ymodem_buf[0], (unsigned int)mobude_ymodem_buf[1],
(unsigned int)g_ymodem_last_len,
(unsigned long)g_ymodem_frame_count,
chrg_ymodem_state_name(ystate));
ret = chrg_send_sou_upadata_data(mobude_ymodem_buf_2, g_ymodem_last_len);
g_ymodem_rx_len = 0;
g_ymodem_expected_len = 0;
g_ymodem_rx_tick = 0;
if (ret != 0) {
rt_kprintf("[UPG] TX south failed frame=%02X pkt=%u ret=%d\r\n",
mobude_ymodem_buf_2[0], (unsigned int)mobude_ymodem_buf_2[1], ret);
return -4;
}
g_ymodem_send_count++;
rt_kprintf("[UPG] TX south frame=%02X pkt=%u len=%u count=%lu state=%s\r\n",
mobude_ymodem_buf_2[0], (unsigned int)mobude_ymodem_buf_2[1],
(unsigned int)g_ymodem_last_len,
(unsigned long)g_ymodem_send_count,
chrg_ymodem_state_name(ystate));
g_ymodem_retry_count = 0;
g_ymodem_waiting_ack = 1;
g_ymodem_send_tick = rt_tick_get();
}
return 0;
}
/*****************************************************************
函数名称: ymodem_send
函数描述: 处理南向Ymodem应答、生成上位机应答并执行超时重发
输入参数: pCOMM:COM通信对象
输出参数: pCOMM->tx_buf和pCOMM->tx_len
返回说明: 1:需要向上位机发送数据 0:本轮无数据发送
其它说明: 首包前每秒发送一次C;SOH首包和第二次EOT后按Ymodem规则发送ACK+C
*****************************************************************/
static rt_uint8_t ymodem_send(struct chrg_comm_t *pCOMM)
{
rt_uint8_t south_resp;
rt_uint32_t timeout_ms;
rt_tick_t now;
pCOMM->tx_len = 0;
now = rt_tick_get();
if ((g_ymodem_expected_len != 0) && (g_ymodem_rx_tick != 0) &&
((now - g_ymodem_rx_tick) >=
rt_tick_from_millisecond(YMODEM_RX_FRAME_TIMEOUT_MS))) {
g_ymodem_rx_len = 0;
g_ymodem_expected_len = 0;
g_ymodem_rx_tick = 0;
pCOMM->tx_buf[0] = NAK;
pCOMM->tx_len = 1;
rt_kprintf("[UPG] RX timeout -> NAK state=%s\r\n",
chrg_ymodem_state_name(ystate));
return 1;
}
south_resp = chrg_ymodem_take_south_resp();
if (south_resp == ACK) {
g_ymodem_waiting_ack = 0;
g_ymodem_retry_count = 0;
if (ystate == Ymodem_Rec_Start) {
pCOMM->tx_buf[0] = ACK;
pCOMM->tx_len = 1;
} else if (ystate == Ymodem_Rec_End) {
pCOMM->tx_buf[0] = ACK;
pCOMM->tx_len = 1;
g_ymodem_eot_done = 1;
ystate = Ymodem_Rec_Finish;
} else {
pCOMM->tx_buf[0] = ACK;
pCOMM->tx_len = 1;
}
rt_kprintf("[UPG] SOUTH ACK state=%s retries=%u\r\n",
chrg_ymodem_state_name(ystate), (unsigned int)g_ymodem_retry_count);
return 1;
}
if (south_resp == NAK) {
rt_kprintf("[UPG] SOUTH NAK state=%s retry=%u\r\n",
chrg_ymodem_state_name(ystate), (unsigned int)g_ymodem_retry_count);
if (g_ymodem_retry_count >= YMODEM_MAX_RETRIES) {
pCOMM->tx_buf[0] = YM_CAN;
pCOMM->tx_buf[1] = YM_CAN;
pCOMM->tx_len = 2;
chrg_ymodem_session_stop();
return 1;
}
(void)chrg_ymodem_resend_last();
return 0;
}
if (south_resp == ACK_C) {
rt_kprintf("[UPG] SOUTH C state=%s waiting=%u\r\n",
chrg_ymodem_state_name(ystate), (unsigned int)g_ymodem_waiting_ack);
/* 等待当前帧ACK时忽略功率板周期C,避免打乱上位机停止等待握手。 */
if (g_ymodem_waiting_ack != 0) {
return 0;
}
pCOMM->tx_buf[0] = ACK_C;
pCOMM->tx_len = 1;
return 1;
}
if (south_resp == YM_CAN) {
rt_kprintf("[UPG] SOUTH CAN state=%s\r\n",
chrg_ymodem_state_name(ystate));
pCOMM->tx_buf[0] = YM_CAN;
pCOMM->tx_buf[1] = YM_CAN;
pCOMM->tx_len = 2;
chrg_ymodem_session_stop();
return 1;
}
if (g_ymodem_waiting_ack != 0) {
timeout_ms = (ystate == Ymodem_Rec_Start) ?
YMODEM_META_ACK_TIMEOUT_MS : YMODEM_DATA_ACK_TIMEOUT_MS;
if ((now - g_ymodem_send_tick) >= rt_tick_from_millisecond(timeout_ms)) {
rt_kprintf("[UPG] ACK timeout state=%s retry=%u\r\n",
chrg_ymodem_state_name(ystate),
(unsigned int)g_ymodem_retry_count);
if (g_ymodem_retry_count >= YMODEM_MAX_RETRIES) {
pCOMM->tx_buf[0] = YM_CAN;
pCOMM->tx_buf[1] = YM_CAN;
pCOMM->tx_len = 2;
chrg_ymodem_session_stop();
return 1;
}
(void)chrg_ymodem_resend_last();
}
}
return 0;
}
///*****************************************************************
//函数名称: ymodem_send
//函数描述: 每隔一秒发一次0x43告诉上位机准备数据
//输入参数: pCOMM: 消息结构
//输出参数: -
//返回说明: -
//其它说明: 分命令处理对应消息。地址为广播时不应答
//*****************************************************************/
////返回1:需要发送数据;返回0:无需发送
//uint8_t y_time = 0;
//uint8_t ymodem_send(struct chrg_comm_t *pCOMM,uint8_t state){
//
// struct chrg_rollsou_t *pROLL = &chrgrollsou;
// static rt_uint8_t number = 4;
// uint8_t need_tx = 0;
// if(pROLL->send_flag==0)y_time++;
// pCOMM->tx_len = 0; //每次进来先清空发送长度
// /* ======== 尚未收到上位机第一个数据包: 定时发送'C'握手 ======== */
// if (!s_first_soh_rcvd) {
// if (y_time > 20) {
// pCOMM->tx_buf[0] = ACK_C;
// pCOMM->tx_len = 1;
// need_tx = 1;
// y_time = 0;
// }
// return need_tx;
// }
// if(state == Ymodem_Rec_End){
// //if (y_time > 20) {
// pCOMM->tx_buf[0] = ACK;
// pCOMM->tx_buf[1] = ACK_C;
// pCOMM->tx_len = 2;
// need_tx = 1;
// y_time = 0;
// number--;
// if(number == 0)
// {
// state = Ymodem_Wait;
// number = 4;
// }
// state = Ymodem_Wait;
// //}
// }
// /* ======== 已收到上位机数据包: 南向有应答立刻转发给上位机 ======== */
// uint8_t south_resp = chrg_eload_get_ymodem_resp();
// if (south_resp != 0) {
// switch (south_resp) {
// case ACK:
// memset(mobude_ymodem_buf_2,0,sizeof(mobude_ymodem_buf));
// switch(state){
// case Ymodem_Rec_End:
// case Ymodem_Rec_Start:
// pCOMM->tx_buf[0] = ACK;
// pCOMM->tx_buf[1] = ACK_C;
// pCOMM->tx_len = 2;
// break;
// case Ymodem_Rec_1024:
// pCOMM->tx_buf[0] = ACK;
// pCOMM->tx_len = 1;
// }
// need_tx = 1;
// /* Rec_End收到ACK → 南向已完成2次EOT握手,切到收尾阶段 */
// break;
// case NAK:
// chrg_send_sou_upadata_data(mobude_ymodem_buf_2,1029);
// break;
// default:
// break;
// }
// y_time = 0;
// rt_kprintf("REC DATA = %02x\n",south_resp);
// }
//
// return need_tx;
//}
///*****************************************************************
//函数名称: Ymodem_Process
//函数描述: 处理ymodem协议数据
//输入参数: pCOMM: 消息结构
//输出参数: -
//返回说明: -
//其它说明: 分命令处理对应消息。地址为广播时不应答
//*****************************************************************/
//uint16_t len = 0;
//eMBException Ymodem_Process(rt_uint8_t *reqFrame, rt_uint16_t reqLen,
// rt_uint8_t *resFrame, rt_uint16_t *resLen)
//{
// eMBException eStatus = MB_EX_NONE;
// struct chrg_rollsou_t *pROLL = &chrgrollsou;
// rt_kprintf("TX_LEN = %d\n",reqLen);
//// for(int i=0;i<reqLen;i++){
//// rt_kprintf("%02x ",reqFrame[i]);
//// }
// len = reqLen;
// uint8_t data = reqFrame[0];
// switch(data){
// case SOH:
// memset(mobude_ymodem_buf,0,sizeof(mobude_ymodem_buf));
// ystate = Ymodem_Rec_Start;
// memcpy(mobude_ymodem_buf,reqFrame,reqLen);
// memcpy(mobude_ymodem_buf_2,reqFrame,reqLen);
// chrg_send_sou_upadata_data(mobude_ymodem_buf,reqLen);
// pROLL->send_flag = 1;
// s_first_soh_rcvd = 1;
// break;
// case STX:
// ystate = Ymodem_Rec_1024;
// memset(mobude_ymodem_buf,0,sizeof(mobude_ymodem_buf));
// memcpy(mobude_ymodem_buf,reqFrame,reqLen);
// memcpy(mobude_ymodem_buf_2,reqFrame,reqLen);
// chrg_send_sou_upadata_data(mobude_ymodem_buf,reqLen);
// pROLL->send_flag = 1;
// break;
// case EOT:
// ystate = Ymodem_Rec_End;
// memset(mobude_ymodem_buf,0,sizeof(mobude_ymodem_buf));
// memcpy(mobude_ymodem_buf,reqFrame,reqLen);
// memcpy(mobude_ymodem_buf_2,reqFrame,reqLen);
// chrg_send_sou_upadata_data(mobude_ymodem_buf,reqLen);
// pROLL->send_flag = 1;
// break;
// }
// return eStatus;
//}
#endif
/*****************************************************************
函数名称: chrg_comm_updata_north
函数描述: 解析南向程序数据包
@@ -2214,8 +1506,6 @@ static rt_uint8_t ymodem_send(struct chrg_comm_t *pCOMM)
static int chrg_comm_updata_north (struct chrg_comm_t *pCOMM)
{
int ret = 0;
int process_ret;
rt_uint8_t need_tx;
if (pCOMM == RT_NULL) {
return -1;
@@ -2230,40 +1520,15 @@ static int chrg_comm_updata_north (struct chrg_comm_t *pCOMM)
}
if (pCOMM->rx_len > 0) {
process_ret = chrg_ymodem_proxy_feed_north(pCOMM->rx_buf,
(void)chrg_ymodem_proxy_feed_north(pCOMM->rx_buf,
pCOMM->rx_len);
(void)process_ret;
}
pCOMM->tx_len = chrg_ymodem_proxy_poll(pCOMM->tx_buf,
sizeof(pCOMM->tx_buf));
need_tx = (pCOMM->tx_len > 0U) ? 1U : 0U;
if (need_tx != 0U) {
if (pCOMM->tx_len > 0U) {
ret = chrg_tty_send(pCOMM->tty, pCOMM->tx_buf, pCOMM->tx_len);
}
// uint8_t need_tx = 0;
// pCOMM->tx_len = 0; //进来先清空发送缓冲区
// eMBException eStatus = MB_EX_NONE;
// //收到有效数据才解析Ymodem
// if(pCOMM->rx_len == 8 && pCOMM->rx_buf[1]==0x20){
// eStatus = funYmodemUpadata(pCOMM->rx_buf, pCOMM->rx_len, pCOMM->tx_buf, &pCOMM->tx_len);
// }
// else if(pCOMM->rx_len == 8 && pCOMM->rx_buf[1]==0x06){
// eStatus = funcWriteHoldingRegister(pCOMM->rx_buf, pCOMM->rx_len, pCOMM->tx_buf, &pCOMM->tx_len);
// }
// else if(pCOMM->rx_len != 0){
// rt_kprintf("len = %d\n",pCOMM->rx_len);
// Ymodem_Process(pCOMM->rx_buf, pCOMM->rx_len, pCOMM->tx_buf, &pCOMM->tx_len);
// }
// //统一调用定时应答生成
// need_tx = ymodem_send(pCOMM,ystate);
// //只有标志为1且长度有效才发送
// if(need_tx && pCOMM->tx_len > 0){
// ret = chrg_tty_send(pCOMM->tty, pCOMM->tx_buf, pCOMM->tx_len);
// }
return ret;
}
/*****************************************************************
-10
View File
@@ -172,16 +172,6 @@ struct code_func_t {
#define MB_YMODEM_DATA_SIZE (1029)
////升级协议状态
//typedef enum {
// Ymodem_No = 0,
// Ymodem_Wait,
// Ymodem_Rec_Start,
// Ymodem_Rec_1024,
// Ymodem_Rec_End,
// Ymodem_Rec_Finish
//} Ymodem_state;
#define THR_NAME_COMM "thr.comm"
#define DEV_NAME_COMM "uart1"
+2 -12
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 42U
#define CHRG_VERSION_STRING "V1.1.42"
#define CHRG_VERSION_PATCH 43U
#define CHRG_VERSION_STRING "V1.1.43"
/*
* Channel polling mode:
@@ -56,16 +56,6 @@ enum {
};
/* ======================== Ymodem 协议常量 (CRC-16/XMODEM) ======================== */
/* Ymodem 协议状态枚举 */
typedef enum {
Ymodem_No = 0xFF, // 非升级模式
Ymodem_Wait = 0, // 空闲等待,定时发送'C'
Ymodem_Rec_Start, // 收到文件名帧(SOH),待CRC校验
Ymodem_Rec_1024, // 数据帧接收中(SOH=128B / STX=1024B)
Ymodem_Rec_End, // 收到EOT,传输结束握手
Ymodem_Rec_Finish // 升级完成,准备收尾
} Ymodem_state;
/* Ymodem 协议控制字符 */
#define SOH 0x01 // 128字节数据包帧头
#define STX 0x02 // 1024字节数据包帧头
@@ -1,3 +1,8 @@
/*
* 功率板YMODEM升级代理。
* 北向负责拼帧和本地校验,南向应答原样转发给PC;
* 本模块不自动重发数据帧或EOT,重发决策统一由PC执行。
*/
#include <string.h>
#include <rtthread.h>
@@ -11,27 +16,27 @@
#define LOG_TAG "chrg.upgrade"
#include <rtdbg.h>
#define YMODEM_RX_FRAME_TIMEOUT_MS 1000U
#define YMODEM_SESSION_IDLE_MS 15000U
#define YMODEM_ENTER_TIMEOUT_MS 15000U
#define YMODEM_RESPONSE_TIMEOUT_MS 10000U
#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 // 南向单帧应答等待时间
typedef struct {
chrg_ymodem_proxy_state_t state;
rt_uint8_t ch;
rt_uint8_t expected_packet;
rt_uint8_t current_packet;
rt_uint8_t meta_ack_seen;
rt_uint8_t eot2_ack_seen;
rt_uint8_t deferred_c;
rt_uint16_t rx_len;
rt_uint16_t expected_len;
rt_tick_t rx_tick;
rt_tick_t response_tick;
rt_tick_t activity_tick;
rt_uint8_t rx_buf[YMODEM_STX_SIZE];
rt_uint8_t north_pending[2];
rt_uint8_t north_pending_len;
chrg_ymodem_proxy_state_t state; // 当前升级阶段
rt_uint8_t ch; // 被锁定的南向功率板通道
rt_uint8_t expected_packet; // PC下一帧应使用的包号
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; // 当前半帧起始时间
rt_tick_t response_tick; // 最近一帧提交南向的时间
rt_tick_t activity_tick; // 最近一次有效会话活动时间
rt_uint8_t rx_buf[YMODEM_STX_SIZE]; // 北向最大STX帧拼帧缓冲区
rt_uint8_t north_pending[2]; // 待转发给PC的单字节或CAN CAN
rt_uint8_t north_pending_len; // 北向待发应答长度
} chrg_ymodem_proxy_t;
static chrg_ymodem_proxy_t g_proxy = {
@@ -39,6 +44,7 @@ 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};
static const rt_uint8_t g_exit_frame[8] =
@@ -62,7 +68,6 @@ static const char *chrg_ymodem_proxy_state_name(chrg_ymodem_proxy_state_t state)
case YMODEM_PROXY_WAIT_END_ACK: return "END_ACK";
case YMODEM_PROXY_TRANSFER_DONE: return "DONE";
case YMODEM_PROXY_EXITING: return "EXITING";
case YMODEM_PROXY_VERIFY: return "VERIFY";
default: return "ABORT";
}
}
@@ -171,6 +176,7 @@ static rt_bool_t chrg_ymodem_frame_valid(const rt_uint8_t *frame,
frame_crc) ? RT_TRUE : RT_FALSE;
}
/* 元数据必须是包号0的SOH,文件名以.upk结尾,文件长度为十进制数字。 */
static rt_bool_t chrg_ymodem_metadata_valid(const rt_uint8_t *frame)
{
const rt_uint8_t *data = &frame[YMODEM_DATA_OFFSET];
@@ -192,6 +198,7 @@ static rt_bool_t chrg_ymodem_metadata_valid(const rt_uint8_t *frame)
return digit_seen;
}
/* YMODEM收尾帧必须是包号0的SOH,且数据区128字节全为0。 */
static rt_bool_t chrg_ymodem_end_soh_valid(const rt_uint8_t *frame)
{
rt_uint16_t i;
@@ -202,6 +209,7 @@ static rt_bool_t chrg_ymodem_end_soh_valid(const rt_uint8_t *frame)
return RT_TRUE;
}
/* 向南向线程只提交一次帧,成功后进入真实应答等待状态。 */
static int chrg_ymodem_proxy_submit_frame(const rt_uint8_t *frame,
rt_uint16_t length, chrg_ymodem_proxy_state_t next_state)
{
@@ -217,6 +225,7 @@ static int chrg_ymodem_proxy_submit_frame(const rt_uint8_t *frame,
return 0;
}
/* 锁定南向物理通道,停止普通Modbus轮询并清理历史应答。 */
static int chrg_ymodem_proxy_prepare_channel(rt_uint8_t ch)
{
struct chrg_thread_t *thread = &chrgthr[IDX_THR_ROLL_SOU];
@@ -238,6 +247,13 @@ static int chrg_ymodem_proxy_prepare_channel(rt_uint8_t ch)
return 0;
}
/*****************************************************************
函数名称: chrg_ymodem_proxy_enter
函数描述: 进入指定功率板的Boot升级模式
输入参数: ch:南向通道编号
返回说明: 0:收到功率板ACK <0:进入失败
其它说明: 仅在真实ACK到达后允许COM回复PC成功
*****************************************************************/
int chrg_ymodem_proxy_enter(rt_uint8_t ch)
{
rt_tick_t start;
@@ -270,6 +286,13 @@ int chrg_ymodem_proxy_enter(rt_uint8_t ch)
return -4;
}
/*****************************************************************
函数名称: chrg_ymodem_proxy_exit
函数描述: 跳转功率板App并恢复普通南向轮询
输入参数: ch:当前升级通道
返回说明: 0:收到跳转ACK <0:状态或通信失败
其它说明: 仅TRANSFER_DONE状态可退出,退出后强制刷新版本
*****************************************************************/
int chrg_ymodem_proxy_exit(rt_uint8_t ch)
{
rt_tick_t start;
@@ -297,6 +320,7 @@ int chrg_ymodem_proxy_exit(rt_uint8_t ch)
return -3;
}
/* 根据当前阶段校验完整帧,校验成功才允许提交南向。 */
static int chrg_ymodem_proxy_handle_frame(const rt_uint8_t *frame,
rt_uint16_t length)
{
@@ -344,6 +368,13 @@ static int chrg_ymodem_proxy_handle_frame(const rt_uint8_t *frame,
return -2;
}
/*****************************************************************
函数名称: chrg_ymodem_proxy_feed_north
函数描述: 接收PC分段数据并拼成完整YMODEM帧
输入参数: data:本次北向数据 length:数据长度
返回说明: 0:已接收或提交 <0:状态、帧或提交错误
其它说明: 等待南向应答时拒绝下一帧;CAN在任意阶段优先处理
*****************************************************************/
int chrg_ymodem_proxy_feed_north(const rt_uint8_t *data,
rt_uint16_t length)
{
@@ -394,6 +425,7 @@ int chrg_ymodem_proxy_feed_north(const rt_uint8_t *data,
return 0;
}
/* 仅按当前状态接受功率板ACK/NAK/C/CAN,不伪造成功应答。 */
static void chrg_ymodem_proxy_handle_south(rt_uint8_t response)
{
g_proxy.activity_tick = rt_tick_get();
@@ -468,6 +500,13 @@ static void chrg_ymodem_proxy_handle_south(rt_uint8_t response)
}
}
/*****************************************************************
函数名称: chrg_ymodem_proxy_poll
函数描述: 推进南向应答、半帧超时、单帧超时和会话空闲超时
输入参数: output:北向待发缓冲区 capacity:缓冲区容量
返回说明: 0:无待发数据 >0:需要发给PC的字节数
其它说明: 超时只取消会话,不自动重发已可能写入Flash的帧
*****************************************************************/
rt_uint16_t chrg_ymodem_proxy_poll(rt_uint8_t *output,
rt_uint16_t capacity)
{
@@ -523,18 +562,3 @@ rt_uint16_t chrg_ymodem_proxy_poll(rt_uint8_t *output,
g_proxy.north_pending_len = 0U;
return length;
}
rt_bool_t chrg_ymodem_proxy_active(void)
{
return (g_proxy.state != YMODEM_PROXY_IDLE) ? RT_TRUE : RT_FALSE;
}
rt_uint8_t chrg_ymodem_proxy_channel(void)
{
return g_proxy.ch;
}
chrg_ymodem_proxy_state_t chrg_ymodem_proxy_state(void)
{
return g_proxy.state;
}
@@ -1,35 +1,39 @@
/*
* 功率板YMODEM升级代理接口。
* PC负责重发和包号推进,协议板只校验、转发并透传功率板应答。
*/
#ifndef __CHRG_YMODEM_PROXY_H__
#define __CHRG_YMODEM_PROXY_H__
#include <rtthread.h>
typedef enum {
YMODEM_PROXY_IDLE = 0,
YMODEM_PROXY_ENTERING,
YMODEM_PROXY_WAIT_C,
YMODEM_PROXY_WAIT_META,
YMODEM_PROXY_WAIT_META_RESP,
YMODEM_PROXY_WAIT_DATA,
YMODEM_PROXY_WAIT_DATA_RESP,
YMODEM_PROXY_WAIT_EOT1_RESP,
YMODEM_PROXY_WAIT_EOT2,
YMODEM_PROXY_WAIT_EOT2_RESP,
YMODEM_PROXY_WAIT_END_SOH,
YMODEM_PROXY_WAIT_END_ACK,
YMODEM_PROXY_TRANSFER_DONE,
YMODEM_PROXY_EXITING,
YMODEM_PROXY_VERIFY,
YMODEM_PROXY_ABORT
YMODEM_PROXY_IDLE = 0, // 未进入升级模式
YMODEM_PROXY_ENTERING, // 已发送进入Boot控制帧,等待ACK
YMODEM_PROXY_WAIT_C, // 进入Boot成功,等待功率板'C'
YMODEM_PROXY_WAIT_META, // 等待PC发送包号0元数据SOH
YMODEM_PROXY_WAIT_META_RESP, // 元数据已转发,等待南向ACK和'C'
YMODEM_PROXY_WAIT_DATA, // 等待PC发送下一个数据包或首个EOT
YMODEM_PROXY_WAIT_DATA_RESP, // 数据包已转发,等待南向ACK/NAK
YMODEM_PROXY_WAIT_EOT1_RESP, // 第一个EOT已转发,等待南向NAK
YMODEM_PROXY_WAIT_EOT2, // 等待PC发送第二个EOT
YMODEM_PROXY_WAIT_EOT2_RESP, // 第二个EOT已转发,等待ACK和'C'
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_ABORT // 异常取消,等待CAN发送完成
} chrg_ymodem_proxy_state_t;
/* 锁定目标通道并进入功率板Boot,收到真实ACK后才返回成功。 */
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,
rt_uint16_t length);
/* 推进南向应答和超时状态,返回待发给PC的ACK/NAK/C/CAN。 */
extern rt_uint16_t chrg_ymodem_proxy_poll(rt_uint8_t *output,
rt_uint16_t capacity);
extern rt_bool_t chrg_ymodem_proxy_active(void);
extern rt_uint8_t chrg_ymodem_proxy_channel(void);
extern chrg_ymodem_proxy_state_t chrg_ymodem_proxy_state(void);
#endif
Binary file not shown.
+15 -59
View File
@@ -1,59 +1,15 @@
\t (00:00:28) Allegro 17.2 S079 Windows SPB 64-bit Edition
\t (00:00:28) Journal start - Sun Sep 20 09:40:58 2026
\t (00:00:28) Host=DESKTOP-5RA9F3K User=Adminuser Pid=2796 CPUs=20
\t (00:00:28) CmdLine= D:\Cadence\SPB_17.2\tools\bin\Allegro.exe D:\HC-PMP-SW\Power_ControlPannel\MP_Control_sw_405\HC_DFCD\HC_DFCD_LCD_V_1.0\docs\HC_DFCD_LCD_V2_0(1).brd
\t (00:00:28)
\d (00:00:28) Design opened: D:/HC-PMP-SW/Power_ControlPannel/MP_Control_sw_405/MP_SW_Chrg/chrg/docs/HC_DFCD_Protocol_V3_0.brd
\i (00:00:28) generaledit
\i (00:00:28) zoom out 1
\i (00:00:28) setwindow pcb
\i (00:00:28) zoom out 18.0833 55.1949
\i (00:00:28) trapsize 5515
\i (00:00:29) zoom out 1
\i (00:00:29) setwindow pcb
\i (00:00:29) zoom out 18.3039 52.8786
\i (00:00:29) trapsize 11030
\i (00:00:29) zoom out 1
\i (00:00:29) setwindow pcb
\i (00:00:29) zoom out 58.3881 52.4375
\i (00:00:29) trapsize 22060
\i (00:00:29) zoom out 1
\i (00:00:29) setwindow pcb
\i (00:00:29) zoom out 217.2174 50.6728
\i (00:00:29) trapsize 44119
\i (00:00:29) zoom out 1
\i (00:00:29) setwindow pcb
\i (00:00:29) zoom out 535.3173 195.5990
\i (00:00:29) trapsize 88239
\i (00:00:29) zoom in 1
\i (00:00:29) setwindow pcb
\i (00:00:29) zoom in -61.1751 55.2998
\i (00:00:29) trapsize 44119
\i (00:00:30) zoom in 1
\i (00:00:30) setwindow pcb
\i (00:00:30) zoom in -42.6449 29.7106
\i (00:00:30) trapsize 22060
\i (00:00:30) zoom in 1
\i (00:00:30) setwindow pcb
\i (00:00:30) zoom in -42.6449 29.7107
\i (00:00:30) trapsize 11030
\i (00:00:30) zoom in 1
\i (00:00:30) setwindow pcb
\i (00:00:30) zoom in -41.3213 29.7107
\i (00:00:30) trapsize 5515
\i (00:00:32) drag_start grid -14.2983 71.4034
\i (00:00:33) drag_stop -24.7766 57.2852
\i (00:00:33) zoom out 1
\i (00:00:33) setwindow pcb
\i (00:00:33) zoom out 18.5706 69.5283
\i (00:00:33) trapsize 11030
\i (00:00:42) zoom in 1
\i (00:00:42) setwindow pcb
\i (00:00:42) zoom in 93.0217 64.4546
\i (00:00:42) trapsize 5515
\i (00:00:52) zoom out 1
\i (00:00:52) setwindow pcb
\i (00:00:52) zoom out 72.8372 58.1677
\i (00:00:52) trapsize 11030
\i (00:02:28) exit
\t (00:02:29) Journal end - Sun Sep 20 09:42:59 2026
\t (00:00:02) allegro 17.2 S079 Windows SPB 64-bit Edition
\t (00:00:02) Journal start - Mon Sep 28 10:44:47 2026
\t (00:00:02) Host=DESKTOP-5RA9F3K User=Adminuser Pid=40156 CPUs=20
\t (00:00:02) CmdLine= D:\Cadence\SPB_17.2\tools\bin\allegro.exe
\t (00:00:02)
(00:00:02) Loading axlcore.cxt
\t (00:00:08) Starting new design...
\i (00:00:09) trapsize 29310
\i (00:00:09) generaledit
\i (00:00:13) trapsize 1230
\i (00:00:13) QtSignal SPBFoldDockArea FoldAreaTabWidget currentChanged unnamed
\i (00:00:13) opencd D:/HC-PMP-SW/Power_ControlPannel/MP_Control_sw_405/south/HC_DFCD_Source_Eload_V1_1/PCB/HC_DFCD_Source_Eload_V1_1.brd
\t (00:00:13) Opening existing design...
\i (00:00:13) trapsize 283
\t (00:00:13) Journal end - Mon Sep 28 10:44:58 2026
Binary file not shown.
+76 -13
View File
@@ -1,15 +1,78 @@
\t (00:00:05) allegro 17.2 S079 Windows SPB 64-bit Edition
\t (00:00:05) Journal start - Wed Sep 16 22:17:09 2026
\t (00:00:05) Host=DESKTOP-5RA9F3K User=Adminuser Pid=19892 CPUs=20
\t (00:00:05) CmdLine= D:\Cadence\SPB_17.2\tools\bin\allegro.exe
\t (00:00:05) Allegro 17.2 S079 Windows SPB 64-bit Edition
\t (00:00:05) Journal start - Mon Sep 28 10:42:59 2026
\t (00:00:05) Host=DESKTOP-5RA9F3K User=Adminuser Pid=31016 CPUs=20
\t (00:00:05) CmdLine= D:\Cadence\SPB_17.2\tools\bin\Allegro.exe D:\HC-PMP-SW\Power_ControlPannel\MP_Control_sw_405\MP_SW_Chrg\chrg\docs\HC_DFCD_Protocol_V3_0.brd
\t (00:00:05)
(00:00:05) Loading axlcore.cxt
\t (00:00:06) Starting new design...
\i (00:00:06) trapsize 29310
\i (00:00:07) generaledit
\i (00:00:08) trapsize 1230
\i (00:00:08) QtSignal SPBFoldDockArea FoldAreaTabWidget currentChanged unnamed
\i (00:00:08) opencd D:/HC-PMP-SW/Power_ControlPannel/MP_Control_sw_405/south/HC_DFCD_Source_Eload_V1_1/PCB/HC_DFCD_Source_Eload_V1_1.brd
\t (00:00:08) Opening existing design...
\i (00:00:08) trapsize 283
\t (00:00:08) Journal end - Wed Sep 16 22:17:12 2026
\t (00:00:06) Opening existing design...
\i (00:00:06) QtSignal SPBFoldDockArea FoldAreaTabWidget currentChanged HC_DFCD_Protocol_V3_0
\d (00:00:06) Design opened: D:/HC-PMP-SW/Power_ControlPannel/MP_Control_sw_405/MP_SW_Chrg/chrg/docs/HC_DFCD_Protocol_V3_0.brd
\i (00:00:08) trapsize 3087
\i (00:00:08) trapsize 3181
\i (00:00:08) trapsize 3224
\i (00:00:09) trapsize 2757
\i (00:00:09) trapsize 2757
\i (00:00:10) generaledit
\i (00:00:13) xhilite
\i (00:00:13) xname_flush
\i (00:00:13) done
\i (00:00:13) generaledit
\i (00:01:43) xhilite
\i (00:01:43) xname_flush
\i (00:01:43) done
\i (00:01:43) generaledit
\i (00:01:46) exit
\e (00:01:46) Do you want to save the changes you made to HC_DFCD_Protocol_V3_0.brd?
\i (00:01:46) fillin no
\t (00:01:47) Journal end - Mon Sep 28 10:44:41 2026
i (00:01:38) trapsize 1379
\i (00:01:38) zoom in 1
\i (00:01:38) setwindow pcb
\i (00:01:38) zoom in -9.3258 62.5298
\i (00:01:38) trapsize 689
\i (00:01:38) zoom in 1
\i (00:01:38) setwindow pcb
\i (00:01:38) zoom in -9.3258 62.5298
\i (00:01:38) trapsize 345
\i (00:01:38) zoom in 1
\i (00:01:38) setwindow pcb
\i (00:01:38) zoom in -9.3258 62.5298
\i (00:01:38) trapsize 172
\i (00:01:38) zoom out 1
\i (00:01:38) setwindow pcb
\i (00:01:38) zoom out -8.5709 62.0783
\i (00:01:38) trapsize 345
\i (00:01:38) zoom out 1
\i (00:01:38) setwindow pcb
\i (00:01:38) zoom out -8.5916 62.0782
\i (00:01:38) trapsize 689
\i (00:01:38) zoom out 1
\i (00:01:38) setwindow pcb
\i (00:01:38) zoom out -8.6055 62.0645
\i (00:01:38) trapsize 1379
\i (00:01:38) zoom out 1
\i (00:01:38) setwindow pcb
\i (00:01:38) zoom out -8.6055 62.0645
\i (00:01:38) trapsize 2757
\i (00:01:39) zoom out 1
\i (00:01:39) setwindow pcb
\i (00:01:39) zoom out -8.2746 62.0646
\i (00:01:39) trapsize 5515
\i (00:01:39) zoom out 1
\i (00:01:39) setwindow pcb
\i (00:01:39) zoom out -8.2745 62.0645
\i (00:01:39) trapsize 11030
\i (00:01:39) zoom out 1
\i (00:01:39) setwindow pcb
\i (00:01:39) zoom out -2.9386 62.0645
\i (00:01:39) trapsize 22059
\i (00:01:39) zoom out 1
\i (00:01:39) setwindow pcb
\i (00:01:39) zoom out 94.1228 62.0646
\i (00:01:39) trapsize 44119
\i (00:01:41) pick grid 679.1382 312.9521
\t (00:01:41) last pick: 679.1400 312.9500
\i (00:01:42) exit
\e (00:01:42) Do you want to save the changes you made to HC_DFCD_Protocol_V3_0.brd?
\i (00:01:43) fillin no
\t (00:01:43) Journal end - Mon Sep 28 10:44:38 2026
+3 -2
View File
@@ -20,5 +20,6 @@
(Doc
(Type "COrCapturePMDoc")
(Frame
(Placement "44 2 3 -1 -1 -8 -31 0 200 0 150"))
(Tab 0))))
(Placement "44 0 1 -1 -1 -8 -31 0 200 0 150"))
(Tab 0)))
(MPSSessionName "Adminuser"))
+19
View File
@@ -1,5 +1,24 @@
# 修改日志
## V1.1.43 - 2026-09-28
### YMODEM升级代理代码清理
- 删除 `chrg_comm.c` 中已被独立代理模块替代的旧YMODEM自动重发代码和注释代码。
- 删除无调用的旧状态枚举和升级查询接口。
- 补充YMODEM代理状态、会话上下文、校验和超时流程的中文注释。
- 协议板版本寄存器 `0x0300` 更新为十进制编码 `143`,后续版本不再使用十六进制编码。
## V1.1.42 - 2026-09-28
### 功率板YMODEM北向升级代理
- 新增独立 `chrg_ymodem_proxy` 模块,管理指定通道的升级会话、拼帧、校验和超时。
- 严格区分元数据、数据包、两次EOT和结束空SOH状态。
- 功率板NAK原样转发给PC,取消协议板对数据帧和EOT的内部自动重发。
- 退出升级并收到真实ACK后恢复南向轮询,强制刷新目标功率板版本。
- 协议板版本寄存器 `0x0300` 更新为 `0x013A`。
## V1.1.41 - 2026-09-28
### Sink协议类型切换复位时序
+27 -3
View File
@@ -1,10 +1,22 @@
# 功率板升级:北向转发计划
## 实施状态
已在 `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. 范围与原则
北向链路为“PC 上位机 ↔ 协议板 App”。协议板通过功能码 `0x20` 进入指定功率板的升级代理模式,之后北向链路切换为原始 YMODEM 字节流。
当前功率板 Boot 仅将 YMODEM 数据区直接写入 `APP_FLASH_BASE_ADDR`,本阶段升级文件必须是可直接启动的裸 APP 镜像。若交付物带 `.upk` 头、分区或校验字段,必须先实现 Boot 解包和最终完整性校验,未完成前不得透传该 `.upk`。
目标方案要求功率板Boot解析完整 `.upk` 文件:跳过YMODEM元数据,解析UPK头区,再将package body安装到 `APP_FLASH_BASE_ADDR`。协议板只透传UPK的YMODEM帧,不解析或重打包UPK。
升级期间采用以下原则:
@@ -71,7 +83,18 @@ CRC 使用 CRC-16/XMODEM:`poly=0x1021`、`init=0x0000`、不反射、`xorout=0
文件名 + 0x00 + `.upk` 实际十进制长度 + 0x00 + 其余补 0x00
```
数据从包号 1 开始。首个 STX 携带 `.upk` 文件偏移 `0~1023` 的原始字节,后续帧按文件偏移连续发送;协议板不解释 UPK 内部字段。最后一帧不足固定数据区时用 `0x1A` 补齐,但补齐字节不计入元数据中的 `.upk` 文件长度。
数据从包号1开始,功率板UPK固定采用以下布局:
```text
包号0:YMODEM元数据,功率板Boot读取文件名/文件长度,不写Flash
包号1:UPK文件偏移0~1023
前96字节为UPK逻辑头,后928字节为0x00填充
功率板Boot解析并校验UPK头,不写APP区
包号2起:UPK package body
功率板Boot从APP首地址0x08003800开始安装
```
协议板只按文件偏移原样转发,不解释或修改UPK内容。最后一帧不足1024字节时用 `0x1A` 补齐,但补齐字节不计入元数据文件长度、`pkg_size`或CRC。
## 4. 北向状态机
@@ -197,6 +220,7 @@ PC -> 协议板 : 通过正常协议查询功率板版本并确认升级结果
- 等待南向应答时禁止接受下一数据帧,严格执行停止等待。
- 任意传输阶段收到 PC 的 CAN 时,应转发南向、终止会话并恢复普通轮询。
- 本地包号反码或 CRC 错误:不转发南向,直接向 PC 发送 NAK。
- 包号1必须是完整1024字节UPK头区;北向只做YMODEM校验,UPK头和包体校验由功率板Boot完成。
- 南向 NAK:原样转发 PC,由 PC 重发;协议板不得自行重发。
- 南向应答超时:尽可能向功率板和 PC 各发送 CAN CAN,结束会话;不得盲目重发已可能写入的帧。
- 北向 ACK 等待建议由 PC 设置为 10 秒;进入升级擦除等待建议 15 秒。
@@ -210,4 +234,4 @@ PC -> 协议板 : 通过正常协议查询功率板版本并确认升级结果
4. 只有收到南向真实应答后才回复 PC。
5. 结束后执行退出命令和版本读取,区分“传输完成”与“升级确认成功”。
6. 日志至少记录通道、状态、帧类型、包号、长度、南向应答和失败原因。
7. 联调前确认升级文件为裸 APP 镜像;若使用 UPK,先完成 Boot 端解包和完整性校验。
7. 联调前先完成功率板Boot的UPK解析、安装和完整性校验;北向只允许发送符合功率板UPK规则的完整 `.upk` 文件。
+69 -19
View File
@@ -2,11 +2,11 @@
## 1. 范围与原则
南向链路为“协议板 App ↔ 目标功率板 Boot”。当前功率板 Boot 仅将 YMODEM 数据区直接写入 `APP_FLASH_BASE_ADDR`,因此本阶段只允许传输裸 APP 镜像。协议板不修改镜像内容,只负责锁定物理通道、连续发送完整 YMODEM 帧、采集功率板的单字节应答,并将应答按原顺序交给北向状态机。UPK 解包、分区、版本、加密和最终 CRC 校验属于后续 Boot 能力,未实现前不得向本 Boot 透传 `.upk`。
南向链路为“协议板 App ↔ 目标功率板 Boot”。功率板Boot按UPK规则接收完整 `.upk` 文件;协议板不修改UPK内容,只负责锁定物理通道、连续发送完整YMODEM帧、采集功率板的单字节应答,并将应答按原顺序交给北向状态机。
南向升级期间停止普通 Modbus 轮询、LCD 插入命令、控制台命令和其他通道请求。升级数据不得经过普通 Modbus解析器。
协议板只校验 YMODEM 帧长度、包号反码和 CRC16;当前 Boot 的 APP 可写范围是 `0x08003800 + 0xB000`(44 KB),裸 APP 镜像长度必须受此范围限制。
功率板UPK采用与协议板Boot相同的“1024字节首块”结构:前96字节为UPK逻辑头,后928字节固定为 `0x00`,package body从文件偏移 `0x400` 开始。协议板只校验YMODEM帧长度、包号反码和CRC16;UPK头、大小、加密和CRC由功率板Boot解析。
## 2. 串口和控制帧
@@ -40,6 +40,8 @@
| --- | --- | --- |
| 进入升级控制帧 | `ACK + C` | ACK确认进入,C进入应答队列,按顺序交北向 |
| 元数据 SOH/包0 | `ACK + C` | 两字节均按顺序交北向 |
| UPK头区 STX/包1 | `ACK`或`NAK` | Boot解析96字节头并检查928字节填充,不写APP区 |
| UPK包体 STX/包2起 | `ACK`或`NAK` | 从APP首地址开始安装,仅接收`pkg_size`范围内的数据 |
| 普通 SOH/STX | `ACK` | 交北向,允许下一帧 |
| 校验错误的数据帧 | `NAK` | 原样交北向,不在南向自动重发 |
| 第一次 EOT | `NAK` | 这是正常握手,原样交北向 |
@@ -74,9 +76,17 @@ S_META_WAIT
NAK -> 转交北向 -> S_WAIT_META
S_WAIT_DATA
收到SOH/STX -> S_DATA_TX
收到包号1的STX -> S_UPK_HEAD_TX
收到包号2起的SOH/STX -> S_DATA_TX
收到EOT -> S_EOT1_TX
S_UPK_HEAD_TX
UART发送完成 -> S_UPK_HEAD_WAIT
S_UPK_HEAD_WAIT
功率板解析UPK头成功并回复ACK -> 转交北向 -> S_WAIT_DATA
功率板回复NAK/CAN -> 转交北向 -> S_ABORT
S_DATA_TX
UART发送完成 -> S_DATA_WAIT
@@ -175,26 +185,65 @@ YMODEM 数据帧的重试由北向 PC 负责。南向代理不得在收到 NAK
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 跳转。
4. 包号0只解析YMODEM文件名和文件长度,不写Flash。
5. 包号1必须为STX/1024:解析前96字节UPK头,校验标识、头CRC、目标分区、`raw_size`、`pkg_size`和加密配置;检查后928字节均为 `0x00`;整包不写APP区。
6. 包号2起才处理package body,并从 `0x08003800` 开始安装;不得把UPK头区写入APP。
7. 只处理UPK头声明的 `pkg_size` 字节,忽略最后YMODEM帧的 `0x1A`补齐;不得按整帧把补齐数据计入固件长度。
8. 完成接收后校验package CRC;若启用加密,按UPK配置解密后写APP并校验raw CRC;未加密时直接写APP并校验raw CRC。
9. APP首地址必须是裸固件向量表,写入完成后校验MSP和Reset_Handler范围;任何UPK校验失败都不得跳转APP。
10. 第一次EOT回复NAK,第二次EOT仅在UPK接收和校验成功后回复 `ACK + C`,结束空SOH回复ACK。
11. 明确APP起始地址、最大 `raw_size/pkg_size`和Flash擦除范围;当前APP区为 `0x08003800~0x0800E7FF`,大小 `0xB000`(44KB),必须与App链接脚本一致。
12. 跳转App前关闭中断、DMA、SysTick和相关外设,设置MSP与VTOR。
13. 处理进入控制帧后,先清除控制帧接收缓存和长度计数,再启动YMODEM接收;不得将控制帧的8字节与元数据SOH拼接。
14. UART接收只保留一条DMA-to-idle重启路径;禁止UART IRQ与接收回调同时中止或重复启动DMA。
15. 跳转控制帧匹配成功后,先发送ACK并等待发送完成,再执行App跳转。
## 9. 南向验证顺序
## 9. 功率板Boot的UPK安装流程
```text
收到YMODEM包号0
-> 解析文件名和.upk实际长度
-> ACK + C
收到YMODEM包号1(STX/1024)
-> 解析前96字节UPK逻辑头
-> 校验head_crc、大小、分区、加密配置和928字节零填充
-> 初始化package CRC/解密状态
-> 不写APP区
-> ACK
收到YMODEM包号2起
-> 按pkg_size截取有效package body
-> 更新pkg_crc
-> 未加密:从0x08003800顺序写入
-> 已加密:按UPK配置解密后从0x08003800顺序写入
-> 超出pkg_size的0x1A填充不写入
-> 每帧成功后ACK
收到两次EOT
-> 校验累计pkg_size和pkg_crc
-> 校验写入APP的raw_size和raw_crc
-> 校验APP向量表
-> 全部成功后完成标准EOT和结束空SOH握手
-> 任一失败回复CAN或明确失败并禁止跳转
```
## 10. 南向验证顺序
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 帧。
4. 确认包号0不写Flash,包号1只解析UPK头且不写APP。
5. 确认包号2的数据区首字节写入 `0x08003800`,该地址内容为APP向量表。
6. 验证最后一帧的 `0x1A`补齐不计入大小和CRC。
7. 验证头CRC、package CRC、raw CRC、加密和超长UPK异常路径。
8. 验证标准两次EOT和结束空SOH。
9. 验证CRC错误时NAK能原样传到PC,并由PC重发。
10. 验证ACK丢失、半帧、超时、CAN和断电。
11. 跳转后读取功率板新版本,确认升级真正成功。
12. 连续发送 `ACK+C`、EOT、结束空SOH和跳转控制帧,确认Boot未丢字节、重复启动DMA或将控制帧拼入YMODEM帧。
## 10. 南向实施清单
## 11. 南向实施清单
1. 删除南向收到 NAK或超时后的内部 YMODEM重发。
2. 增加独立的 EOT1、EOT2和结束空包状态。
@@ -203,5 +252,6 @@ YMODEM 数据帧的重试由北向 PC 负责。南向代理不得在收到 NAK
5. 进入和退出控制帧禁止盲目重试。
6. 升级期间锁定通道并完全停止普通轮询。
7. 完成后恢复轮询并提供版本确认结果。
8. 先修复 Boot:进入控制帧后清接收缓存、跳转前回 ACK、UART 单一 DMA 接收重启路径。
9. 未实现 Boot 端 UPK 解包和最终完整性校验前,升级文件限定为裸 APP 镜像。
8. 修复Boot:进入控制帧后清接收缓存、跳转前回ACK、UART使用单一DMA接收重启路径。
9. 修改功率板Boot:包号1解析UPK头但不写APP,包号2起安装package body。
10. 增加UPK长度、头CRC、package CRC、raw CRC、解密和向量表校验。