#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); }