XModem协议

XModem协议介绍:
XModem是一种在串口通信中广泛使用的异步文件传输协议,分为XModem和1k-XModem协议两种,前者使用128字节的数据块,后者使用1024字节即1k字节的数据块。

一、XModem校验和协议

  1. XModem信息包格式
    XModem协议最早由Ward Christensen在20世纪70年代提出并实现的,传输数据单位为信息包,信息包格式如下:

| Byte1 | Byte2 | Byte3 |Byte4~Byte131| Byte132 |
|-------------------------------------------------------------------------|
|Start Of Header|Packet Number|~(Packet Number)| Packet Data | Check Sum |

  1. 校验和的计算
    所有的数据字节都将参与和运算,由于校验和只占一个字节,如果累加的和超过255将从零开始继续累加。

  2. 字段定义
    01H
    04H
    06H
    15H
    18H

  3. 校验和方式的XModem传输流程
    传输流程如图所示:


| SENDER | | RECIEVER |
| | <— | NAK |
| | | Time out after 3 second |
| | <— | NAK |
| SOH|0x01|0xFE|Data[0~127]|CheckSum| | —> | |
| | <— | ACK |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| | —> | |
| | <— | NAK |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| | —> | |
| | <— | ACK |
| SOH|0x03|0xFC|Data[0~127]|CheckSum| | —> | |
| | <— | ACK |
| . | | . |
| . | | . |
| . | | . |
| | <— | ACK |
| EOT | —> | |
| | <— | ACK |

对于发送方仅仅支持校验和的传输方式,接收方应首先发送NAK信号来发起传输,如果发送方没有数据发送过来,需要超时等待3秒之后再发起NAK信号来进行数据传输。对于数据传输正确,接收方需要发送ACK信号来进行确认,如果数据传输有误,则发送NAK信号,发送方在接收到NAK信号之后需要重新发起该次数据传输,如果数据已近传输完成,发送方需要发送EOT信号,来结束数据传输。

  1. 如何取消数据传输
    当接收方发送CAN表示无条件结束本次传输过程,发送方收到CAN后,无需发送EOT来确认,直接停止数据的发送。

二、XModem-CRC16协议

  1. XModem-CRC16信息包格式
    XModem协议在90年代做过一次修改,将132字节处的校验和改成双字节的CRC16校验,CRC16校验的信息包格式如下:

| Byte1 | Byte2 | Byte3 |Byte4Byte131|Byte132Byte133|
|----------------------------------------------------------------------------|
|Start Of Header|Packet Number|~(Packet Number)| Packet Data | 16Bit CRC |

  1. CRC16的计算
    比较复杂,表示看不懂,以后有时间再研究吧,先给出一份源代码,来自:
    http://web.mit.edu/6.115/www/miscfiles/amulet/amulet-help/xmodem.htm

[cpp] view plain copy

int calcrc(char *ptr, int count)
{
int crc;
char i;

crc = 0;  
while (--count >= 0)  
{  
    crc = crc ^ (int) *ptr++ << 8;  
    i = 8;  
    do  
    {  
        if (crc & 0x8000)  
            crc = crc << 1 ^ 0x1021;  
        else  
            crc = crc << 1;  
    } while (--i);  
}  

return (crc);  

}
需要注意的是,在发送方,CRC是高字节在前,低字节在后。

  1. CRC16校验的XModem传输流程
    传输流程如图所示:

| SENDER | | RECIEVER |
| | <— | ‘C’ |
| | | Time out after 3 second |
| | <— | ‘C’ |
| SOH|0x01|0xFE|Data[0~127]|CRC16| | —> | |
| | <— | ACK |
| SOH|0x02|0xFD|Data[0~127]|CRC16| | —> | |
| | <— | NAK |
| SOH|0x02|0xFD|Data[0~127]|CRC16| | —> | |
| | <— | ACK |
| SOH|0x03|0xFC|Data[0~127]|CRC16| | —> | |
| | <— | ACK |
| . | | . |
| . | | . |
| . | | . |
| | <— | ACK |
| EOT | —> | |
| | <— | ACK |

和校验和方式不同的是,当接收方要求发送方以CRC16校验方式发送数据时以’C’来请求,发送方对此做出应答,流程就如上图所示。当发送方仅仅支持校验和方式时,则接收方要发送NAK来请求,要求以校验和方式来发送数据,如果仅仅支持CRC16校验方式,则只能发送’C’来请求。如果两者都支持的话,优先发送’C’来请求,流程如图所示:


| SENDER | | RECIEVER |
| | <— | ‘C’ |
| | | Time out after 3 second |
| | <— | NAK |
| | | Time out after 3 second |
| | <— | ‘C’ |
| | | Time out after 3 second |
| | <— | NAK |
| SOH|0x01|0xFE|Data[0~127]|CheckSum| | —> | |
| | <— | ACK |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| | —> | |
| | <— | NAK |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| | —> | |
| | <— | ACK |
| SOH|0x03|0xFC|Data[0~127]|CheckSum| | —> | |
| | <— | ACK |
| . | | . |
| . | | . |
| . | | . |
| | <— | ACK |
| EOT | —> | |
| | <— | ACK |

最后,如果信息包中的数据如果不足128字节,剩余的部分要以0x1A(Ctrl-Z)来填充。

三、1k-XModem协议
1k-XModem协议同XModem-CRC16协议差不多,只是数据块长度变成了1024字节即1k,同时每个信息报的第一个字节的SOH变成了STX,STX定义为 0x02,能有效的加快数据传输速率。

使用Java实现Xmodem协议

Xmodem协议

1.介绍

Xmodem是一种在串口通信中广泛使用的异步文件传输协议,分为Xmodem(使用128字节的数据块)和1k-Xmodem(使用1024字节即1k字节的数据块)协议两种。
本文实现的是128字节数据块的Xmodem协议,采用CRC16校验,在项目中应用时,发送端和接收端可根据具体情况修改双方的协议。

标准Xmodem协议(使用128字节的数据块)帧格式:

Byte1 Byte2 Byte3 Byte4 ~ byte131 Byte132
控制字符 包序号 包序号的反码 数据 校验和
如果你对串口通信还不太了解,可以看下我写的这篇博客使用Java实现串口通信。

2.实现

在和嵌入式同学调试的过程中,发现发送端发送数据过快,导致接收端处理不过来,所以在send方法中开启了一个子线程来处理数据发送逻辑,方便加入延时处理。
接收方法中,发送C是表示以CRC方式校验。

public class Xmodem {

// 开始
private final byte SOH = 0x01;
// 结束
private final byte EOT = 0x04;
// 应答
private final byte ACK = 0x06;
// 重传
private final byte NAK = 0x15;
// 无条件结束
private final byte CAN = 0x18;

// 以128字节块的形式传输数据
private final int SECTOR_SIZE = 128;
// 最大错误(无应答)包数
private final int MAX_ERRORS = 10;

// 输入流,用于读取串口数据
private InputStream inputStream;
// 输出流,用于发送串口数据
private OutputStream outputStream;

public Xmodem(InputStream inputStream, OutputStream outputStream) {
    this.inputStream = inputStream;
    this.outputStream = outputStream;
}

/**
 * 发送数据
 * 
 * @param filePath
 *            文件路径
 */
public void send(final String filePath) {
    new Thread() {
        public void run() {
            try {
                // 错误包数
                int errorCount;
                // 包序号
                byte blockNumber = 0x01;
                // 校验和
                int checkSum;
                // 读取到缓冲区的字节数量
                int nbytes;
                // 初始化数据缓冲区
                byte[] sector = new byte[SECTOR_SIZE];
                // 读取文件初始化
                DataInputStream inputStream = new DataInputStream(
                        new FileInputStream(filePath));

                while ((nbytes = inputStream.read(sector)) > 0) {
                    // 如果最后一包数据小于128个字节,以0xff补齐
                    if (nbytes < SECTOR_SIZE) {
                        for (int i = nbytes; i < SECTOR_SIZE; i++) {
                            sector[i] = (byte) 0xff;
                        }
                    }

                    // 同一包数据最多发送10次
                    errorCount = 0;
                    while (errorCount < MAX_ERRORS) {
                        // 组包
                        // 控制字符 + 包序号 + 包序号的反码 + 数据 + 校验和
                        putData(SOH);
                        putData(blockNumber);
                        putData(~blockNumber);
                        checkSum = CRC16.calc(sector) & 0x00ffff;
                        putChar(sector, (short) checkSum);
                        outputStream.flush();

                        // 获取应答数据
                        byte data = getData();
                        // 如果收到应答数据则跳出循环,发送下一包数据
                        // 未收到应答,错误包数+1,继续重发
                        if (data == ACK) {
                            break;
                        } else {
                            ++errorCount;
                        }
                    }
                    // 包序号自增
                    blockNumber = (byte) ((++blockNumber) % 256);
                }

                // 所有数据发送完成后,发送结束标识
                boolean isAck = false;
                while (!isAck) {
                    putData(EOT);
                    isAck = getData() == ACK;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
    }.start();
}

/**
 * 接收数据
 * 
 * @param filePath
 *            文件路径
 * @return 是否接收完成
 * @throws IOException
 *             异常
 */
public boolean receive(String filePath) throws Exception {
    // 错误包数
    int errorCount = 0;
    // 包序号
    byte blocknumber = 0x01;
    // 数据
    byte data;
    // 校验和
    int checkSum;
    // 初始化数据缓冲区
    byte[] sector = new byte[SECTOR_SIZE];
    // 写入文件初始化
    DataOutputStream outputStream = new DataOutputStream(
            new FileOutputStream(filePath));

    // 发送字符C,CRC方式校验
    putData((byte) 0x43);

    while (true) {
        if (errorCount > MAX_ERRORS) {
            outputStream.close();
            return false;
        }

        // 获取应答数据
        data = getData();
        if (data != EOT) {
            try {
                // 判断接收到的是否是开始标识
                if (data != SOH) {
                    errorCount++;
                    continue;
                }

                // 获取包序号
                data = getData();
                // 判断包序号是否正确
                if (data != blocknumber) {
                    errorCount++;
                    continue;
                }

                // 获取包序号的反码
                byte _blocknumber = (byte) ~getData();
                // 判断包序号的反码是否正确
                if (data != _blocknumber) {
                    errorCount++;
                    continue;
                }

                // 获取数据
                for (int i = 0; i < SECTOR_SIZE; i++) {
                    sector[i] = getData();
                }

                // 获取校验和
                checkSum = (getData() & 0xff) << 8;
                checkSum |= (getData() & 0xff);
                // 判断校验和是否正确
                int crc = CRC16.calc(sector);
                if (crc != checkSum) {
                    errorCount++;
                    continue;
                }

                // 发送应答
                putData(ACK);
                // 包序号自增
                blocknumber++;
                // 将数据写入本地
                outputStream.write(sector);
                // 错误包数归零
                errorCount = 0;

            } catch (Exception e) {
                e.printStackTrace();

            } finally {
                // 如果出错发送重传标识
                if (errorCount != 0) {
                    putData(NAK);
                }
            }
        } else {
            break;
        }
    }

    // 关闭输出流
    outputStream.close();
    // 发送应答
    putData(ACK);

    return true;
}

/**
 * 获取数据
 * 
 * @return 数据
 * @throws IOException
 *             异常
 */
private byte getData() throws IOException {
    return (byte) inputStream.read();
}

/**
 * 发送数据
 * 
 * @param data
 *            数据
 * @throws IOException
 *             异常
 */
private void putData(int data) throws IOException {
    outputStream.write((byte) data);
}

/**
 * 发送数据
 * 
 * @param data
 *            数据
 * @param checkSum
 *            校验和
 * @throws IOException
 *             异常
 */
private void putChar(byte[] data, short checkSum) throws IOException {
    ByteBuffer bb = ByteBuffer.allocate(data.length + 2).order(
            ByteOrder.BIG_ENDIAN);
    bb.put(data);
    bb.putShort(checkSum);
    outputStream.write(bb.array());
}

}
CRC16校验算法,采用的是查表法。

public class CRC16 {

private static final char crctable[] = { 0x0000, 0x1021, 0x2042, 0x3063,
        0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b,
        0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252,
        0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a,
        0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
        0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509,
        0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630,
        0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
        0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7,
        0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af,
        0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
        0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e,
        0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5,
        0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
        0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4,
        0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc,
        0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb,
        0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3,
        0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da,
        0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2,
        0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589,
        0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481,
        0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
        0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0,
        0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f,
        0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827,
        0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e,
        0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16,
        0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d,
        0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45,
        0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c,
        0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
        0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 };

public static char calc(byte[] bytes) {
    char crc = 0x0000;
    for (byte b : bytes) {
        crc = (char) ((crc << 8) ^ crctable[((crc >> 8) ^ b) & 0x00ff]);
    }
    return (char) (crc);
}

}
3.使用

// serialPort为串口对象
Xmodem xmodem = new Xmodem(serialPort.getInputStream(),serialPort.getOutputStream());
// filePath为文件路径
// ./bin/xxx.bin
xmodem.send(filePath);
4.写在最后

完整的代码下载

作者:容华谢后
链接:https://www.jianshu.com/p/6dabbfe61495
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2018.06.08 10.58

  xmodem协议模块今天测试完毕了,说下我的思路和感受。写之前一定要有一个大题的思路,

这个东西做成什么样子(API接口设计,状态机设计),然后就是画各个状态图->合并状态图->源码实现。

其实我比较懒,懒得动笔画,我习惯写之前把状态枚举出来,然后写的过程中会根据思路增加或者减少状态。

   xmodem的API接口:

bool xmodem_init( xmodem_t *ptXmodem,user_api_t *ptApi);
bool xmodem_start_rx( xmodem_t *ptXmodem,xmodem_select_check_mode_t tCheckMode,uint16_t hwFrameLong);
bool xmodem_cfg_tx_mode(xmodem_t *ptXmodem,uint16_t hwFrameLong);
bool xmodem_cancel_rx( xmodem_t *ptXmodem);
fsm_rt_t xmodem_tx( xmodem_t *ptXmodem);
fsm_rt_t xmodem_check( void *ptXmodem,bool pbIsRequestDrop,queue_peek_byte_t ptReadByteHandler);
模式:

typedef enum{
XMODEM_CHECKOUT_SUM = 0,
XMODEM_CHECKOUT_CRC16,
XMODEM_CHECKOUT_AUTO,
}xmodem_select_check_mode_t;
注:

1、发送模式自适应,无需配置模式;

2、接收模式三种模式都可以配置,如果配置为自适应,则’C’和NAK每隔3s交替发送;

3、帧长只做了1024和非1024(最大帧长为1024),非1024按着128格式走;

4、不支持收和发文件同时进行;

5、接收线程由协议解析引擎驱动(每次进入接收都需要用户启动);

6、发送函数需要由用户发送线程驱动(开始发送前需要配置帧长,默认128);

  接收状态机,相对于发送状态机要复杂一些,其中有几个状态是独立:强制停止接收态,发送模式

接收协议解析,这几个状态是通过API强制改变的。发送模式简单分为三大部分:发送启动字符(每隔3s发一次),

第一帧协议接收(这里面可能跳转到发送启动字符状态),后续帧接收(这里面有发送ACK和NAK)。

typedef enum{
XMODEM_CHECK_STATE_START = 0,
XMODEM_CHECK_STATE_WAIT_START_RX_FLAG,
XMODEM_CHECK_STATE_START_UP_TX_C,
XMODEM_CHECK_STATE_START_UP_TX_NAK,
XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_START,
XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_NUM,
XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_CHECK_NUM,
XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_DATA,
XMODEM_CHECK_STATE_START_UP_FRAME_CHECK,

XMODEM_CHECK_STATE_WAIT_FRAME_START,
XMODEM_CHECK_STATE_WAIT_FRAME_DATA,
XMODEM_CHECK_STATE_WAIT_FRAME_CHECK,
XMODEM_CHECK_STATE_WAIT_RETURN_ACK,
XMODEM_CHECK_STATE_WAIT_RETURN_NAK,
XMODEM_CHECK_STATE_RX_EOT,
XMODEM_CHECK_STATE_USER_FORCE_STOP_RX,

//TX
XMODEM_CHECK_STATE_TX_CHECK_START_FLAG,
XMODEM_CHECK_STATE_TX_WAIT_RETURN,

}xmodem_check_state_t;
xmodem的接收模式就相对简单了,读取字符串流,打包发送,等待返回字符。

typedef enum{
XMODEM_TX_STATE_START = 0,
XMODEM_TX_STATE_IS_INIT_API,
XMODEM_TX_STATE_WAIT_START_FLAG,
XMODEM_TX_STATE_READ_BUFFER,
XMODEM_TX_STATE_TX,
XMODEM_TX_STATE_WAIT_RETURN,
XMODEM_TX_STATE_TX_EOT,
}xmodem_tx_state_t;
xmodem需要外部提供一些API借口,如下:

typedef void start_timer(uint16_t hwDelayMs);
typedef bool check_timer_flag(void);
typedef bool user_buffer_write(uint8_t *pchBuffer,uint16_t hwNum);
typedef uint16_t user_buffer_read( uint8_t *pchBuffer,uint16_t hwNum);
typedef fsm_rt_t xmodem_serial_out(uint8_t *pchBuffer,uint16_t hwSize);

typedef struct _user_api_t user_api_t;
struct _user_api_t{
//延时相关
start_timer *ptStartTimer;
check_timer_flag *ptCheckTimerFlag;

//APP层对接
user_buffer_write   *ptWriteBuffer;
user_buffer_read    *ptReadBuffer;

//输出数据
xmodem_serial_out   *ptSerialOut;

};
说明:

1、start_timer和check_timer_flag延时相关函数;

2、user_buffer_write向用户输出数据流,完整一帧协议;

3、user_buffer_read读取用户要发送数据,每次读取一帧长度,返回实际读取数据长度,如果为0,则表示文件发送完成;

4、xmodem_serial_out向UART的发送Buffer写入数据,状态机方式,主要是考虑UART的发送buffer小于帧长;

源码我就不发布了,没啥意义,需要配合我的协议解析引擎和queue队列才能使用。

2018.07.30

前几天看了遍代码,总是感觉哪里不对,但是又说不上来,今天看了个相似历程,算是明白自己的代码哪里有问题了:

1、状态机太"复杂"了,这里的复杂是指一个函数里面跑了几个状态机,都在一个平面;不是说一个函数不能跑几个状态机,

而是状态机应该是嵌套关系,不应该是平铺的关系;

2、数据流程没有去抽象,用了一个很大的函数去处理,也就是没有所谓的层次感;

总结就是,写代码应该是像搭积木,用一块块积木去拼凑自己的模型;而不应该是像摊大饼一样,平铺在一起。

修改代码,回头再发上来。

2018.08.02

今天代码改造完了,有了纵向的层次感,想积木一样,一层一层搭建起来。但是里面仍然有一些根据初始化配置不同而处理

逻辑不同,比如:长度是128还是1K,校验方式是校验和还是CRC16。这些怎么处理?

我们可以这样处理,逻辑层抽象出通用的API接口,把上述四种情况封装成四个处理函数,然后在初始化时候根据不同的

配置初始化指针,这样对于逻辑层来说处理逻辑完全是通用的。

2018.08.16

今天抽个时间把代码改了下,以前的处理逻辑是这样的:

模式定义:

typedef enum{
XMODEM_128_SUM = 0,
XMODEM_128_CRC16,
XMODEM_1K_SUM,
XMODEM_1K_CRC16,
}xmodem_select_mode_t;
初始化:

bool xmodem_init(xmodem_t *ptXmodem,xmodem_cfg_t *ptCfg,user_api_t *ptApi)
{
CLASS(xmodem_t) *ptThis = (CLASS(xmodem_t) *)ptXmodem;
if(NULL == ptThis || NULL == ptCfg || NULL == ptApi){
return false;
}

if(NULL == ptApi->ptWriteBuffer     ||
   NULL == ptApi->ptStartTimer      ||
   NULL == ptApi->ptCheckTimerFlag  ||
   NULL == ptApi->ptSerialRead      ||
   NULL == ptApi->ptSerialWrite     ||
   NULL == ptCfg->pchBuffer           ){
    this.ptUserApi          = NULL;
    this.tUserApiIsInitFlag = false;
    return false;
}

this.tXmodemSelectMode             = ptCfg->tXmodemSelectMode;
this.pchBuffer                     = ptCfg->pchBuffer;
this.chByte                        = 0x00;

this.ptUserApi                     = ptApi;
this.tUserApiIsInitFlag            = true;

return true;

}
下面的处理逻辑:

            switch(this.tXmodemSelectMode){
                case XMODEM_128_SUM:
                    //break;
                case XMODEM_128_CRC16:
                    if(SOH == this.pchBuffer[s_hwRevCnt]){
                        s_tState = XMODEM_REV_FRAME_DATA;
                        s_hwRevCnt++;
                    }else if(EOT == this.pchBuffer[s_hwRevCnt]){
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_RX_CPL;
                    }else{
                        //RST_XMODEM_REV_FRAME_FSM();
                        return  XMODEM_REV_FRAME_DROP;
                    }
                    break;
                case XMODEM_1K_SUM:
                    //break;
                case XMODEM_1K_CRC16:
                    if(STX == this.pchBuffer[s_hwRevCnt]){
                        s_tState = XMODEM_REV_FRAME_DATA;
                        s_hwRevCnt++;
                    }else if(EOT == this.pchBuffer[s_hwRevCnt]){
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_RX_CPL;
                    }else{
                        //RST_XMODEM_REV_FRAME_FSM();
                        return  XMODEM_REV_FRAME_DROP;
                    }
                    break;
                default:
                    while(1);
            }
     if(XMODEM_REV_FRAME_CPL == tTemp){
            s_hwRevCnt++;
            switch(this.tXmodemSelectMode){
                case XMODEM_128_SUM:
                    if(132 > s_hwRevCnt){
                        break;
                    }
                    chTemp = calsum(this.pchBuffer,s_hwRevCnt-1);
                    if(chTemp == this.pchBuffer[s_hwRevCnt-1]){
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_CPL;
                    }else{
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_CHECK_ERROR;
                    }
                    break;
                case XMODEM_128_CRC16:
                    if(133 > s_hwRevCnt){
                        break;
                    }
                    hwTemp = calcrc(this.pchBuffer,s_hwRevCnt-2);
                    tCrc16.chCrcL = this.pchBuffer[s_hwRevCnt-1];
                    tCrc16.chCrcH = this.pchBuffer[s_hwRevCnt-2];
                    if(tCrc16.hwCrc16 == hwTemp){
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_CPL;
                    }else{
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_CHECK_ERROR;
                    }
                    break;
                case XMODEM_1K_SUM:
                    if(1028 > s_hwRevCnt){
                        break;
                    }
                    chTemp = calsum(this.pchBuffer,s_hwRevCnt-1);
                    if(chTemp == this.pchBuffer[s_hwRevCnt-1]){
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_CPL;
                    }else{
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_CHECK_ERROR;
                    }
                    break;
                case XMODEM_1K_CRC16:
                    if(1029 > s_hwRevCnt){
                        break;
                    }
                    hwTemp = calcrc(this.pchBuffer,s_hwRevCnt-2);
                    tCrc16.chCrcL = this.pchBuffer[s_hwRevCnt-1];
                    tCrc16.chCrcH = this.pchBuffer[s_hwRevCnt-2];
                    if(tCrc16.hwCrc16 == hwTemp){
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_CPL;
                    }else{
                        RST_XMODEM_REV_FRAME_FSM();
                        return XMODEM_REV_FRAME_CHECK_ERROR;
                    }
                    break;
                default:
                    while(1);
            }

有几处代码都是类似的形式,看着是不是辣眼睛?后来改为了这样:

初始化:

bool xmodem_init(xmodem_t *ptXmodem,xmodem_cfg_t *ptCfg,user_api_t *ptApi)
{
CLASS(xmodem_t) *ptThis = (CLASS(xmodem_t) *)ptXmodem;
if(NULL == ptThis || NULL == ptCfg || NULL == ptApi){
return false;
}

if(NULL == ptApi->ptWriteBuffer     ||
   NULL == ptApi->ptStartTimer      ||
   NULL == ptApi->ptCheckTimerFlag  ||
   NULL == ptApi->ptSerialRead      ||
   NULL == ptApi->ptSerialWrite     ||
   NULL == ptCfg->pchBuffer           ){
    this.ptUserApi          = NULL;
    this.tUserApiIsInitFlag = false;
    return false;
}

switch(ptCfg->tXmodemSelectMode){
    case XMODEM_128_SUM:
        this.cRxFrameHead   =   SOH;
        this.hwRxFrameSize  =   128+3+1;
        this.cTxStartUp     =   SUM_SAT;
        this.ptXmodemCheck  =   xmodem_sum_check;
        break;
    case XMODEM_128_CRC16:
        this.cRxFrameHead   =   SOH;
        this.hwRxFrameSize  =   128+3+2;
        this.cTxStartUp     =   CRC16_SAT;
        this.ptXmodemCheck  =   xmodem_crc_check;
        break;
    case XMODEM_1K_SUM:
        this.cRxFrameHead   =   STX;
        this.hwRxFrameSize  =   1024+3+1;
        this.cTxStartUp     =   SUM_SAT;
        this.ptXmodemCheck  =   xmodem_sum_check;
        break;
    case XMODEM_1K_CRC16:
        this.cRxFrameHead   =   STX;
        this.hwRxFrameSize  =   1024+3+2;
        this.cTxStartUp     =   CRC16_SAT;
        this.ptXmodemCheck  =   xmodem_crc_check;
        break;
    default:
        return false;
}
this.pchBuffer                     = ptCfg->pchBuffer;
this.chByte                        = 0x00;

this.ptUserApi                     = ptApi;
this.tUserApiIsInitFlag            = true;

return true;

}
然后下面的处理:

            if(this.cRxFrameHead == this.pchBuffer[s_hwRevCnt]){
                s_tState = XMODEM_REV_FRAME_DATA;
                s_hwRevCnt++;
            }else if(EOT == this.pchBuffer[s_hwRevCnt]){
                RST_XMODEM_REV_FRAME_FSM();
                return XMODEM_REV_FRAME_RX_CPL;
            }else{
                //RST_XMODEM_REV_FRAME_FSM();
                return  XMODEM_REV_FRAME_DROP;
            }
            s_hwRevCnt++;
            if(this.hwRxFrameSize > s_hwRevCnt){
                break;
            }
            if(this.ptXmodemCheck(ptXmodem)){
                RST_XMODEM_REV_FRAME_FSM();
                return XMODEM_REV_FRAME_CPL;                    
            }else{
                RST_XMODEM_REV_FRAME_FSM();
                return XMODEM_REV_FRAME_CHECK_ERROR;
            }

这样看是不是舒服很多。。。。。。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: xmodem协议是一种在计算机通信中广泛使用的协议,它用于在串行通信中可靠地传输文件。这种协议最初是由Ward Christensen和Bill Osterhagen在上世纪70年代末开发的。 xmodem协议使用了比特循环冗余校验(CRC)算法,以确保数据的完整性。它将文件分成128字节的数据块,并附加一个校验和来验证数据块的准确性。发送方首先发送数据块和校验和,接收方收到后会对数据块进行校验。如果校验和不匹配,接收方会通知发送方重新发送该数据块,直到校验和匹配为止。 在C语言中,我们可以使用串口编程库或者自己编写相关的通信函数来实现xmodem协议。首先,我们需要打开串口,并设置通信参数,如波特率、数据位、校验位和停止位。然后,我们可以编写发送和接收数据块的函数。 发送数据块的函数首先读取文件,将文件数据分成128字节的块,并计算出校验和。然后,它通过串口将数据块和校验和发送给接收方,并等待接收方的响应。如果接收方收到的响应是重新传输请求,发送方会重新发送数据块,直到接收到确认响应才移动到下一个数据块。 接收数据块的函数负责接收数据块和校验和,并进行校验。如果数据块和校验和匹配,它会将数据块写入文件。然后,它发送一个确认响应给发送方,表示已成功接收,可以传输下一个数据块。如果数据块和校验和不匹配,它会发送一个重新传输请求给发送方。 总之,通过在C语言中编写适当的通信函数,我们可以实现xmodem协议,从而在串行通信中可靠地传输文件。 ### 回答2: xmodem协议是一种常用的通信协议,主要用于通过串口进行文件传输。它是由Ward Christensen在上世纪70年代提出并设计的。xmodem协议基于字符传输,可用于在不可靠的通信链路上进行可靠的文件传输。 xmodem协议的核心思想是将文件分割成固定大小的数据块进行传输,并通过校验和来保证数据的正确性。具体传输过程如下: 发送方将文件分割成128字节的数据块,并为每个数据块生成一个校验和。发送方首先发送一个文件名进行通知,并等待接收方的确认。 接收方收到文件名后,发送一个确认信号。然后,发送方将数据块和校验和发送给接收方。 接收方接收到数据后进行校验,如果校验和正确,则发送一个确认信号,表示接收成功。如果校验和错误,则发送一个请求重传的信号。 发送方在接收到重传请求后重新发送当前数据块,直到接收方成功接收为止。全部数据块发送完毕后,发送方发送一个结束信号。 接收方在接收到结束信号后发送一个确认信号,表示传输完成。整个文件传输过程结束。 在C语言中,可以使用串口通信库来实现xmodem协议的文件传输。通过设置串口参数,打开串口,并编写发送和接收函数来实现xmodem协议。 具体步骤包括创建文件分割函数、校验和生成函数、串口初始化函数、发送函数和接收函数等。在发送函数中,将文件分割成数据块,并计算校验和,在接收函数中,接收数据,并进行校验。 通过使用C语言和xmodem协议,可以实现可靠的文件传输,适用于各种嵌入式系统和通信设备。 ### 回答3: xmodem协议是一种用于在计算机之间进行可靠数据传输的通信协议。它采用C语言来实现。 xmodem协议的实现是基于串行通信线路的,通过串行通信线路将数据从发送方传输到接收方。它具有以下基本特点: 1. 数据分块传输:xmodem协议将数据分成一块一块的小数据块进行传输,每个小数据块大小为128字节。当数据量超过128字节时,会分为多个小数据块进行传输。 2. 帧结构:每个小数据块被包装在一个帧中传输。帧结构包括帧头、数据块、CRC校验和和帧尾,用于保障数据传输的可靠性。 3. 数据确认和反馈:接收方在接收到每个数据帧后会发送一个确认帧给发送方,表示接收成功。如果发送方在规定时间内没有收到确认帧,则会重传数据帧。 4. 错误校验:每个数据帧通过计算CRC校验和来验证接收到的数据的准确性,以保证数据的完整性。 C语言可以通过使用串口库函数或串行通信库来实现xmodem协议。在发送方,需要将待传输的数据按照规定的帧结构进行封装,并通过串口将帧发送给接收方。在接收方,需要通过串口监听数据并解析每个帧的数据内容,并校验CRC校验和。如果校验成功,则发送确认帧给发送方,否则请求发送方重传。 总结来说,xmodem协议利用C语言的串口通信库函数实现了可靠的数据传输。通过分块传输、帧结构、数据确认和错误校验等机制,保证了数据的完整性和可靠性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值