Files
chrg/apps/boot/application/func/ymodem.c
T
2025-09-17 09:11:22 +08:00

337 lines
8.9 KiB
C

#include "ymodem.h"
struct ymodem_info_t {
uint8_t isBusy; /* 正在处理主机数据的标志位 */
uint8_t enRecv; /* 使能接收主机的指令包 */
uint16_t rxLen; /* 接收到的数据长度 */
uint8_t *rxData; /* 协议解析的数据来源 */
uint8_t numPkt; /* 记录 YModem 协议的 packet number */
eYmFLOW flow; /* 记录协议的执行流程 */
union message_raw_t *msg; /* 接收主机数据包的缓存池,称为主机消息 */
struct tx_raw_t txPkt; /* 用于存放设备上发数据组包的部分参数 */
ymSend send; /* 数据发送接口 */
ymPrepareCallback prepare; /* 收到主机指令时的预备处理接口 */
ymReplyCallback reply; /* 正在执行指令时响应主机查询执行过程和结果的接口 */
};
static struct ymodem_info_t yM;
static struct drv_timer_t timerSendC; /* 用于定时向主机发送数据的定时器 */
static void timerSendCCallback (void *user_data)
{
static uint8_t c[1] = {YMODEM_C};
yM.send(c, 1, HAL_MAX_DELAY);
}
/**
* @brief 复位一些全局变量和标志信息
* @note
* @retval None
*/
static void ymodemReset (void)
{
yM.isBusy = 0;
yM.enRecv = 1;
yM.numPkt = 0;
yM.flow = YMODEM_FLOW_NONE;
}
/**
* @brief 对主机下发的心跳包的处理
* @note 对主机下发的数据帧进行回复, ymodem 没有心跳包,此处只是模拟
* @retval None
*/
static void ymodemHeartBeatProcess (void)
{
static eRESULT result;
yM.reply((eYmCMD)yM.msg->pkg.header, &result, NULL, NULL);
switch (result) {
case YM_RESULT_OK:
//yM.txPkt.response = YMODEM_ACK;
break;
case YM_RESULT_FAILED: {
/* 因前面已经加 1 ,此处是由于对数据处理有问题,非协议本身问题,因此需要减回 */
yM.numPkt--;
yM.txPkt.response = YMODEM_NAK;
break;
}
case YM_RESULT_CANCEL:
yM.txPkt.response = YMODEM_CAN;
break;
default:
/* 业务层还在处理数据,暂时不回复主机 */
return;
}
yM.isBusy = 0;
yM.send(&yM.txPkt.response, 1, HAL_MAX_DELAY);
}
/**
* @brief 对主机下发指令的处理
* @note
* @retval None
*/
static void ymodemCommandProcess (void)
{
uint16_t data_len = 0;
yM.isBusy = 1;
if ((yM.msg->pkg.header == YMODEM_SOH)||(yM.msg->pkg.header == YMODEM_STX)) {
data_len = yM.rxLen - YMODEM_FRAME_FIXED_LEN;
}
yM.prepare((eYmCMD)yM.msg->pkg.header, yM.msg->pkg.data, data_len);
}
/**
* @brief 设置协议执行流程
* @note
* @param[in] cmd: 主机的指令
* @retval eErrCode
*/
static eErrCode ymodemExeFlow (eYmCMD cmd)
{
switch (cmd) {
case YM_CMD_SOH: {
if (yM.flow == YMODEM_FLOW_NONE) { /* 第一个 SOH 数据帧 */
yM.flow = YMODEM_FLOW_START;
yM.txPkt.response = YMODEM_ACK;
} else if (yM.flow == YMODEM_FLOW_SECOND_EOT) { /* 最后一个空的 SOH 数据帧 */
drv_timer_pause(&timerSendC);
yM.flow = YMODEM_FLOW_SUCCESS;
yM.numPkt = 0;
yM.txPkt.response = YMODEM_ACK;
} else { /* 正在传输数据的 SOH 数据帧 */
/* 暂停发送字符“ C ”的定时器 */
drv_timer_pause(&timerSendC);
yM.txPkt.response = YMODEM_ACK;
}
break;
}
case YM_CMD_STX: {
/* 因为第一个有数据的数据帧可能是 STX ,因此此处有必要暂停发送字符“ C ”的定时器 */
drv_timer_pause(&timerSendC);
yM.txPkt.response = YMODEM_ACK;
break;
}
case YM_CMD_EOT: {
if (yM.flow == YMODEM_FLOW_START) { /* 第一个 EOT */
yM.flow = YMODEM_FLOW_FIRST_EOT;
yM.txPkt.response = YMODEM_NAK;
} else if (yM.flow == YMODEM_FLOW_FIRST_EOT) { /* 第二个 EOT */
yM.flow = YMODEM_FLOW_SECOND_EOT;
yM.numPkt = 0;
yM.txPkt.response = YMODEM_ACK;
/* 发完 ACK 需要继续发“ C ” */
drv_timer_restart(&timerSendC);
} else {
return ERR_EXE_FLOW;
}
break;
}
case YM_CMD_CAN: {
yM.flow = YMODEM_FLOW_CANCEL;
yM.txPkt.response = YMODEM_ACK;
break;
}
default:
break;
}
return ERR_OK;
}
/**
* @brief 协议解析处理函数
* @note 需要循环调用
* @retval eErrCode
*/
eErrCode ymodemHandler (uint8_t *data, uint16_t len)
{
eErrCode err_code;
/* 有数据 */
if (data && len && yM.enRecv) {
/* 暂存和格式化 */
yM.rxData = data;
yM.rxLen = len;
yM.msg = (union message_raw_t *)yM.rxData;
//hex_printf(data, len);
/* 只有数据帧才做以下错误检查 */
if (yM.msg->pkg.header == YMODEM_SOH || yM.msg->pkg.header == YMODEM_STX) {
/* 判断序列号是否符合顺序 */
if (yM.msg->pkg.pkt_num != yM.numPkt) {
if (yM.msg->pkg.pkt_num == (yM.numPkt - 1)) {
printf("error: duplicate frame %d %d\r\n", yM.msg->pkg.pkt_num, yM.numPkt);
err_code = ERR_DUPLICATE_FRAME;
} else {
printf("error: omission frame %d %d\r\n", yM.msg->pkg.pkt_num, yM.numPkt);
err_code = ERR_OMISSION_FRAME;
}
goto __error_exit;
}
/* 判断正反序列号是否正确 */
uint8_t pkt_num = ~(yM.msg->pkg.pkt_num);
if (yM.msg->pkg.not_pkt_num != pkt_num) {
printf("error: packet number: %.2X %.2X\r\n", yM.msg->pkg.pkt_num, yM.msg->pkg.not_pkt_num);
err_code = ERR_PKT_NUM_ERR;
goto __error_exit;
}
/* 获取协议帧中的数据字段长度 */
uint16_t data_len;
if (yM.msg->pkg.header == YMODEM_SOH) {
data_len = YMODEM_SOH_DATA_LEN;
} else if (yM.msg->pkg.header == YMODEM_STX) {
data_len = YMODEM_STX_DATA_LEN;
}
/* 帧长度判断 */
/* 奇怪的是, Xshell 在发送最后一个空 SOH 数据帧时会附加两个字节的 0x4F ,原因未知。
* 为了处理这个问题,避免误判为数据帧长度有误,此处嵌套了“ ymodem_pkt_num != 0 ”的判断 */
if (yM.numPkt != 0) {
if (yM.rxLen != (data_len + YMODEM_FRAME_FIXED_LEN)) {
//printf("error: _dev_rx_len: %d\r\n", yM.rxLen);
err_code = ERR_FRAME_LENGTH;
goto __error_exit;
}
}
/* 校验数据是否正确 */
uint16_t crc16 = crc16_xmodem(yM.msg->pkg.data, data_len);
printf("%04X", crc16);
uint16_t raw_crc16 = (yM.msg->pkg.data[data_len] << 8) | yM.msg->pkg.data[data_len + 1];
if (crc16 != raw_crc16) {
printf("error: crc16: %.4X\r\n", crc16);
printf("error: raw crc16: %.4X\r\n", raw_crc16);
err_code = ERR_FRAME_VERIFY_ERR;
goto __error_exit;
}
//printf("crc16 ok.\r\n");
/* 执行到此处记录序列号加1 */
yM.numPkt++;
}
//printf("Ymodem recv len: %d (%02X %02X %02X)\r\n",
// yM.rxLen, yM.msg->pkg.header, yM.msg->pkg.pkt_num, yM.msg->pkg.not_pkt_num);
/* 设置流程 */
if (ymodemExeFlow((eYmCMD)yM.msg->pkg.header)) {
//printf("error: flow illegal\r\n");
err_code = ERR_EXE_FLOW;
goto __error_exit;
}
/* 除了最后一个空的SOH数据帧,其他都会执行 ,包括 EOT CAN */
/* 需要注意是, 执行完后仍未回复主机,回复部分由 ymodemHeartBeatProcess 处理 */
ymodemCommandProcess();
return ERR_OK;
__error_exit:
yM.txPkt.response = YMODEM_NAK;
yM.send(&yM.txPkt.response, 1, HAL_MAX_DELAY);
return err_code;
}
if (yM.isBusy) {
/* 模拟主机的心跳处理,查询是否对协议包处理完毕,以向主机回复 */
ymodemHeartBeatProcess();
}
return ERR_OK;
}
/**
* @brief 配置协议析构层的参数
* @note
* @param[in] para: 需要进行配置的选型或参数
* @param[in] value: 对应的数据或值
* @retval None
*/
void ymodemConfig (eYmMODE mode, void *value)
{
switch (mode) {
case YM_MODE_RESET:
ymodemReset();
drv_timer_restart(&timerSendC);
break;
case YM_MODE_RECV: {
uint8_t *enable = (uint8_t *)value;
yM.enRecv = *enable;
if (yM.enRecv == 0) {
drv_timer_pause(&timerSendC);
}
break;
}
default:
break;
}
}
/**
* @brief 协议析构层的初始化
* @note
* @param[in] Send: 底层数据发送接口
* @param[in] HeartbeatCallback: 心跳包的响应接口
* @param[in] PrepareCallback: 指令包的处理接口
* @param[in] Set_ReplyInfo: 查询指令执行结果的处理接口
* @retval None
*/
void ymodem_init (ymSend Send,
ymPrepareCallback PrepareCallback,
ymReplyCallback Set_ReplyInfo)
{
yM.send = Send;
yM.prepare = PrepareCallback;
yM.reply = Set_ReplyInfo;
ymodemReset();
drv_timer_init(&timerSendC,
timerSendCCallback,
1000,
TIMER_RUN_FOREVER);
drv_timer_start(&timerSendC);
}