Qt实现XYModem协议(三)

1 概述

XMODEM协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。这种协议以128字节块的形式传输数据,并且每个块都使用一个校验和过程来进行错误检测。使用循环冗余校验的与XMODEM相应的一种协议称为XMODEM-CRC。还有一种是XMODEM-1K,它以1024字节一块来传输数据。YMODEM也是一种XMODEM的实现。它包括XMODEM-1K的所有特征,另外在一次单一会话期间为发送一组文件,增加了批处理文件传输模式。

本文利用C++实现XYModem-1K协议,并利用Qt串口类QSerialPort实现数据读写。

3 实现

3.2 XModem

该模块定义和实现了XModem协议。

3.2.1 XModem定义

cpp 复制代码
class XModem : public Modem
{
public:
    XModem()
    : id_(0x0)
    {}

    enum Code {
        NUL = 0x00,
        SOH = 0x01,
        STX = 0x02,
        EOT = 0x04,
        ACK = 0x06,
        NAK = 0x15,
        CAN = 0x18,
        C   = 0x43,
        MAX = 0xff
    };
    enum Size {
        CODE = 1,
        ID = 2,
        SIZE1 = 128,
        SIZE2 = 1024,
        CRC16 = 2,
        FRAME_HEAD  = CODE + ID, //1 + 2
        FRAME_SIZE1 = FRAME_HEAD + SIZE1 + CRC16,// 3 + 128 + 2 = 133
        FRAME_SIZE2 = FRAME_HEAD + SIZE2 + CRC16, // 3 + 1024 + 2 = 1029
        DATA_SIZE1 = FRAME_SIZE1 -1,
        DATA_SIZE2 = FRAME_SIZE2 -1
    };
protected:
    virtual uint8_t get_code(bool isWait = true) = 0;

    bool tx_send(uint8_t *data, uint32_t size, int max_count = 5);
    bool tx_eot();
    bool tx_end();
    
    void tx_code(Code code);
    uint8_t wait_start(int max_count = 5);
    void tx_cancel();

    uint8_t not_id(uint8_t id) { return 0xFF - id; }
    void next_id() { id_ = next_id(id_); } 
private:
    uint8_t next_id(uint8_t id) { return (id + 1) % 0x100; }
    void do_send(uint8_t const* data, uint16_t size);
    void do_eot() { tx_code(EOT); }
    void do_c() { tx_code(C); }
private:
    uint8_t id_;
    uint8_t frame_[FRAME_SIZE2];
};

纯虚接口:

  • get_code 获取操作码Code

函数列表:

  • tx_send 发送数据
  • tx_eot 判断发送是否结束。
  • tx_code 发送操作码
  • wait_start 发送C码等待接收码SOH或STX 码
  • tx_cancel 发送取消帧
  • not_id 返回id的反码
  • next_id 返回下一id

3.2.2 XModem实现

3.2.2.1 tx_send
cpp 复制代码
bool XModem::tx_send(uint8_t *data, uint32_t size, int max_count)
{
    if(size > SIZE2)
        return false;

    do_send(data, size);
    for(int i = 0; i < max_count; i++)
    {
        uint8_t code = get_code();
        if(code == MAX)
            break;
        else if(code == NAK)
            do_send(data, size);
        else if(code == ACK)
        {
            next_id();
            return true;
        }
    }
    return false;
}

函数流程:

  • 发送数据
  • 获取操作码
  • 获得无效操作码,函数返回
  • 获得重传操作码NAK,重传数据
  • 获得确认操作码ACK,说明发送成功,设置下一id
  • 如果一直返回NAK,重复max_count次后函数返回
3.2.2.2 tx_eot
cpp 复制代码
bool XModem::tx_eot()
{
    do_eot();
    uint8_t code = get_code();
    if(code == NAK)
    {
        std::cout << "need next file" << std::endl;
        do_eot();
        if(get_code() == ACK)
            return true;
    }
    else if(code == ACK)
    {
        return true;
    }
    return false;
}

函数流程:

  • 发送EOT码
  • 获取操作码
  • 如果是NAK码,重新发送EOT码
  • 如何是ACK码,成功返回,否则失败返回
3.2.2.3 tx_cancel
cpp 复制代码
void XModem::tx_cancel()
{
    uint8_t frame[] = { CAN, CAN, CAN, CAN, CAN };
    write(frame, sizeof(frame));
}

函数流程:

  • 构造包含5个CAN取消码帧
  • 发送取消码帧
3.2.2.4 tx_code
cpp 复制代码
void XModem::tx_code(Code code)
{
    uint8_t frame = code;
    write(&frame, CODE);
}

函数流程:

  • 构造包含1个操作码帧
  • 发送操作码帧
3.2.2.5 wait_start
cpp 复制代码
uint8_t XModem::wait_start(int max_count)
{
    for(int i = 0; i < max_count; i++)
    {
        do_c();
        uint8_t code = get_code(false);
        if(code != MAX)
            return code;
    }
    return MAX;
}

函数流程:

  • 发送C开始码
  • 获取操作码,如果获取到有效操作码,则返回
  • 获取无效操作码重复max_count次上述操作后返回
3.2.2.6 do_send
cpp 复制代码
void XModem::do_send(uint8_t const* data, uint16_t size)
{
    uint16_t data_size = SIZE2;

    memset(frame_, 0, sizeof(frame_));

    frame_[1] = id_;
    frame_[2] =  not_id(id_);

    if(size > SIZE1)
        frame_[0] = STX;
    else
    {
        frame_[0] = SOH;
        data_size = SIZE1;
    }

    memcpy(&frame_[FRAME_HEAD], data, size);
    memset(&frame_[FRAME_HEAD + size], 0x1A, data_size - size);

    uint16_t crc = crc16(&frame_[FRAME_HEAD], data_size);
    frame_[FRAME_HEAD + data_size] = (uint8_t)(crc >> 8);
    frame_[FRAME_HEAD + data_size + 1] = (uint8_t)(crc >> 0);

    write(frame_, (data_size == SIZE1 ? FRAME_SIZE1 : FRAME_SIZE2));
}

函数流程:

相关推荐
galaxy_strive2 分钟前
绘制饼图详细过程
开发语言·c++·qt
pp-周子晗(努力赶上课程进度版)18 分钟前
【MySQL】视图、用户管理、MySQL使用C\C++连接
数据库·mysql
斯特凡今天也很帅27 分钟前
clickhouse常用语句汇总——持续更新中
数据库·sql·clickhouse
超级小忍2 小时前
如何配置 MySQL 允许远程连接
数据库·mysql·adb
吹牛不交税2 小时前
sqlsugar WhereIF条件的大于等于和等于查出来的坑
数据库·mysql
hshpy2 小时前
setting up Activiti BPMN Workflow Engine with Spring Boot
数据库·spring boot·后端
文牧之3 小时前
Oracle 审计参数:AUDIT_TRAIL 和 AUDIT_SYS_OPERATIONS
运维·数据库·oracle
篱笆院的狗3 小时前
如何使用 Redis 快速实现布隆过滤器?
数据库·redis·缓存
洛神灬殇4 小时前
【LLM大模型技术专题】「入门到精通系列教程」基于ai-openai-spring-boot-starter集成开发实战指南
网络·数据库·微服务·云原生·架构
小鸡脚来咯5 小时前
redis分片集群架构
数据库·redis·架构