zoukankan      html  css  js  c++  java
  • [转]XModem协议

    出处:XModem协议

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


    一、XModem校验和协议

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

    1. ---------------------------------------------------------------------------
    2. |     Byte1     |    Byte2    |    Byte3      |Byte4~Byte131| Byte132   |
    3. |-------------------------------------------------------------------------|
    4. |Start Of Header|Packet Number|~(Packet Number)| Packet Data |  Check Sum |
    5. ---------------------------------------------------------------------------

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


    3. 字段定义
    <SOH> 01H
    <EOT> 04H
    <ACK> 06H
    <NAK> 15H
    <CAN> 18H

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

    1. ------------------------------------------------------------------------------
    2. |               SENDER                | |          RECIEVER        |
    3. |                                     |  <---    |  NAK                      |
    4. |                                     |          |  Time out after 3 second  |
    5. |                                     |  <---    |  NAK                      |
    6. | SOH|0x01|0xFE|Data[0~127]|CheckSum| |  --->    |                           |
    7. |                                    |  <---    |  ACK                      |
    8. | SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
    9. |                                     |  <---    |  NAK                      |
    10. | SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
    11. |                                    |  <---    |  ACK                      |
    12. | SOH|0x03|0xFC|Data[0~127]|CheckSum| |  --->    |                           |
    13. |                                     |  <---    |  ACK                      |
    14. | .                                  |          |  .                        |
    15. | .                                   |          |  .                        |
    16. | .                                   |          |  .                        |
    17. |                                    |  <---    |  ACK                      |
    18. | EOT                                |  --->    |                           |
    19. |                                    |  <---    |  ACK                      |
    20. ------------------------------------------------------------------------------

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

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


    二、XModem-CRC16协议

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

    1. ------------------------------------------------------------------------------
    2. |    Byte1     |    Byte2   |     Byte3      |Byte4~Byte131|Byte132~Byte133|
    3. |----------------------------------------------------------------------------|
    4. |Start Of Header|Packet Number|~(Packet Number)| Packet Data |   16Bit CRC   |
    5. ------------------------------------------------------------------------------

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

    [cpp] view plain copy

    1. int calcrc(char *ptr, int count)  
    2. {  
    3.     int crc;  
    4.     char i;  
    5.   
    6.     crc = 0;  
    7.     while (--count >= 0)  
    8.     {  
    9.         crc = crc ^ (int) *ptr++ << 8;  
    10.         i = 8;  
    11.         do  
    12.         {  
    13.             if (crc & 0x8000)  
    14.                 crc = crc << 1 ^ 0x1021;  
    15.             else  
    16.                 crc = crc << 1;  
    17.         } while (--i);  
    18.     }  
    19.   
    20.     return (crc);  
    21. }  

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

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

    1. ---------------------------------------------------------------------------
    2. |               SENDER             | |           RECIEVER        |
    3. |                                  |  <---    |  'C'                      |
    4. |                                  |          |  Time out after 3 second  |
    5. |                                  |  <---    |  'C'                      |
    6. | SOH|0x01|0xFE|Data[0~127]|CRC16| |  --->    |                           |
    7. |                                  |  <---    |  ACK                      |
    8. | SOH|0x02|0xFD|Data[0~127]|CRC16| |  --->    |                           |
    9. |                                  |  <---    |  NAK                      |
    10. | SOH|0x02|0xFD|Data[0~127]|CRC16| |  --->    |                           |
    11. |                                  |  <---    |  ACK                      |
    12. | SOH|0x03|0xFC|Data[0~127]|CRC16| |  --->    |                           |
    13. |                                  |  <---    |  ACK                      |
    14. | .                                |          |  .                        |
    15. | .                                |          |  .                        |
    16. | .                                |          |  .                        |
    17. |                                  |  <---    |  ACK                      |
    18. | EOT                              |  --->    |                           |
    19. |                                  |  <---    |  ACK                      |
    20. ---------------------------------------------------------------------------

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

    1. ------------------------------------------------------------------------------
    2. |               SENDER                | |           RECIEVER        |
    3. |                                     |  <---    |  'C'                      |
    4. |                                     |          |  Time out after 3 second  |
    5. |                                     |  <---    |  NAK                      |
    6. |                                     |          |  Time out after 3 second  |
    7. |                                     |  <---    |  'C'                      |
    8. |                                     |          |  Time out after 3 second  |
    9. |                                     |  <---    |  NAK                      |
    10. | SOH|0x01|0xFE|Data[0~127]|CheckSum| |  --->    |                           |
    11. |                                     |  <---    |  ACK                      |
    12. | SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
    13. |                                     |  <---    |  NAK                      |
    14. | SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
    15. |                                     |  <---    |  ACK                      |
    16. | SOH|0x03|0xFC|Data[0~127]|CheckSum| |  --->    |                           |
    17. |                                     |  <---    |  ACK                      |
    18. | .                                   |          |  .                        |
    19. | .                                   |          |  .                        |
    20. | .                                   |          |  .                        |
    21. |                                     |  <---    |  ACK                      |
    22. | EOT                                 |  --->    |                           |
    23. |                                     |  <---    |  ACK                      |
    24. ------------------------------------------------------------------------------

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


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

    使用Java实现Xmodem协议

    Xmodem协议

     

    1.介绍

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

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

    Byte1Byte2Byte3Byte4 ~ byte131Byte132
    控制字符 包序号 包序号的反码 数据 校验和

    如果你对串口通信还不太了解,可以看下我写的这篇博客使用Java实现串口通信

    2.实现

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

    1.  
    2. public class Xmodem {
    3.  
    4. // 开始
    5. private final byte SOH = 0x01;
    6. // 结束
    7. private final byte EOT = 0x04;
    8. // 应答
    9. private final byte ACK = 0x06;
    10. // 重传
    11. private final byte NAK = 0x15;
    12. // 无条件结束
    13. private final byte CAN = 0x18;
    14.  
    15. // 以128字节块的形式传输数据
    16. private final int SECTOR_SIZE = 128;
    17. // 最大错误(无应答)包数
    18. private final int MAX_ERRORS = 10;
    19.  
    20. // 输入流,用于读取串口数据
    21. private InputStream inputStream;
    22. // 输出流,用于发送串口数据
    23. private OutputStream outputStream;
    24.  
    25. public Xmodem(InputStream inputStream, OutputStream outputStream) {
    26. this.inputStream = inputStream;
    27. this.outputStream = outputStream;
    28. }
    29.  
    30. /**
    31. * 发送数据
    32. *
    33. * @param filePath
    34. * 文件路径
    35. */
    36. public void send(final String filePath) {
    37. new Thread() {
    38. public void run() {
    39. try {
    40. // 错误包数
    41. int errorCount;
    42. // 包序号
    43. byte blockNumber = 0x01;
    44. // 校验和
    45. int checkSum;
    46. // 读取到缓冲区的字节数量
    47. int nbytes;
    48. // 初始化数据缓冲区
    49. byte[] sector = new byte[SECTOR_SIZE];
    50. // 读取文件初始化
    51. DataInputStream inputStream = new DataInputStream(
    52. new FileInputStream(filePath));
    53.  
    54. while ((nbytes = inputStream.read(sector)) > 0) {
    55. // 如果最后一包数据小于128个字节,以0xff补齐
    56. if (nbytes < SECTOR_SIZE) {
    57. for (int i = nbytes; i < SECTOR_SIZE; i++) {
    58. sector[i] = (byte) 0xff;
    59. }
    60. }
    61.  
    62. // 同一包数据最多发送10次
    63. errorCount = 0;
    64. while (errorCount < MAX_ERRORS) {
    65. // 组包
    66. // 控制字符 + 包序号 + 包序号的反码 + 数据 + 校验和
    67. putData(SOH);
    68. putData(blockNumber);
    69. putData(~blockNumber);
    70. checkSum = CRC16.calc(sector) & 0x00ffff;
    71. putChar(sector, (short) checkSum);
    72. outputStream.flush();
    73.  
    74. // 获取应答数据
    75. byte data = getData();
    76. // 如果收到应答数据则跳出循环,发送下一包数据
    77. // 未收到应答,错误包数+1,继续重发
    78. if (data == ACK) {
    79. break;
    80. } else {
    81. ++errorCount;
    82. }
    83. }
    84. // 包序号自增
    85. blockNumber = (byte) ((++blockNumber) % 256);
    86. }
    87.  
    88. // 所有数据发送完成后,发送结束标识
    89. boolean isAck = false;
    90. while (!isAck) {
    91. putData(EOT);
    92. isAck = getData() == ACK;
    93. }
    94. } catch (Exception e) {
    95. e.printStackTrace();
    96. }
    97. };
    98. }.start();
    99. }
    100.  
    101. /**
    102. * 接收数据
    103. *
    104. * @param filePath
    105. * 文件路径
    106. * @return 是否接收完成
    107. * @throws IOException
    108. * 异常
    109. */
    110. public boolean receive(String filePath) throws Exception {
    111. // 错误包数
    112. int errorCount = 0;
    113. // 包序号
    114. byte blocknumber = 0x01;
    115. // 数据
    116. byte data;
    117. // 校验和
    118. int checkSum;
    119. // 初始化数据缓冲区
    120. byte[] sector = new byte[SECTOR_SIZE];
    121. // 写入文件初始化
    122. DataOutputStream outputStream = new DataOutputStream(
    123. new FileOutputStream(filePath));
    124.  
    125. // 发送字符C,CRC方式校验
    126. putData((byte) 0x43);
    127.  
    128. while (true) {
    129. if (errorCount > MAX_ERRORS) {
    130. outputStream.close();
    131. return false;
    132. }
    133.  
    134. // 获取应答数据
    135. data = getData();
    136. if (data != EOT) {
    137. try {
    138. // 判断接收到的是否是开始标识
    139. if (data != SOH) {
    140. errorCount++;
    141. continue;
    142. }
    143.  
    144. // 获取包序号
    145. data = getData();
    146. // 判断包序号是否正确
    147. if (data != blocknumber) {
    148. errorCount++;
    149. continue;
    150. }
    151.  
    152. // 获取包序号的反码
    153. byte _blocknumber = (byte) ~getData();
    154. // 判断包序号的反码是否正确
    155. if (data != _blocknumber) {
    156. errorCount++;
    157. continue;
    158. }
    159.  
    160. // 获取数据
    161. for (int i = 0; i < SECTOR_SIZE; i++) {
    162. sector[i] = getData();
    163. }
    164.  
    165. // 获取校验和
    166. checkSum = (getData() & 0xff) << 8;
    167. checkSum |= (getData() & 0xff);
    168. // 判断校验和是否正确
    169. int crc = CRC16.calc(sector);
    170. if (crc != checkSum) {
    171. errorCount++;
    172. continue;
    173. }
    174.  
    175. // 发送应答
    176. putData(ACK);
    177. // 包序号自增
    178. blocknumber++;
    179. // 将数据写入本地
    180. outputStream.write(sector);
    181. // 错误包数归零
    182. errorCount = 0;
    183.  
    184. } catch (Exception e) {
    185. e.printStackTrace();
    186.  
    187. } finally {
    188. // 如果出错发送重传标识
    189. if (errorCount != 0) {
    190. putData(NAK);
    191. }
    192. }
    193. } else {
    194. break;
    195. }
    196. }
    197.  
    198. // 关闭输出流
    199. outputStream.close();
    200. // 发送应答
    201. putData(ACK);
    202.  
    203. return true;
    204. }
    205.  
    206. /**
    207. * 获取数据
    208. *
    209. * @return 数据
    210. * @throws IOException
    211. * 异常
    212. */
    213. private byte getData() throws IOException {
    214. return (byte) inputStream.read();
    215. }
    216.  
    217. /**
    218. * 发送数据
    219. *
    220. * @param data
    221. * 数据
    222. * @throws IOException
    223. * 异常
    224. */
    225. private void putData(int data) throws IOException {
    226. outputStream.write((byte) data);
    227. }
    228.  
    229. /**
    230. * 发送数据
    231. *
    232. * @param data
    233. * 数据
    234. * @param checkSum
    235. * 校验和
    236. * @throws IOException
    237. * 异常
    238. */
    239. private void putChar(byte[] data, short checkSum) throws IOException {
    240. ByteBuffer bb = ByteBuffer.allocate(data.length + 2).order(
    241. ByteOrder.BIG_ENDIAN);
    242. bb.put(data);
    243. bb.putShort(checkSum);
    244. outputStream.write(bb.array());
    245. }
    246. }

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

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

    3.使用

    1.  
    2. // serialPort为串口对象
    3. Xmodem xmodem = new Xmodem(serialPort.getInputStream(),serialPort.getOutputStream());
    4. // filePath为文件路径
    5. // ./bin/xxx.bin
    6. xmodem.send(filePath);

    4.写在最后

    完整的代码下载



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

    2018.06.08 10.58

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

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

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

           xmodem的API接口:

    1. bool xmodem_init( xmodem_t *ptXmodem,user_api_t *ptApi);
    2. bool xmodem_start_rx( xmodem_t *ptXmodem,xmodem_select_check_mode_t tCheckMode,uint16_t hwFrameLong);
    3. bool xmodem_cfg_tx_mode(xmodem_t *ptXmodem,uint16_t hwFrameLong);
    4. bool xmodem_cancel_rx( xmodem_t *ptXmodem);
    5. fsm_rt_t xmodem_tx( xmodem_t *ptXmodem);
    6. fsm_rt_t xmodem_check( void *ptXmodem,bool *pbIsRequestDrop,queue_peek_byte_t* ptReadByteHandler);

        模式:

    1. typedef enum{
    2. XMODEM_CHECKOUT_SUM = 0,
    3. XMODEM_CHECKOUT_CRC16,
    4. XMODEM_CHECKOUT_AUTO,
    5. }xmodem_select_check_mode_t;

    注:

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

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

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

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

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

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

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

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

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

    1. typedef enum{
    2. XMODEM_CHECK_STATE_START = 0,
    3. XMODEM_CHECK_STATE_WAIT_START_RX_FLAG,
    4. XMODEM_CHECK_STATE_START_UP_TX_C,
    5. XMODEM_CHECK_STATE_START_UP_TX_NAK,
    6. XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_START,
    7. XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_NUM,
    8. XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_CHECK_NUM,
    9. XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_DATA,
    10. XMODEM_CHECK_STATE_START_UP_FRAME_CHECK,
    11.  
    12. XMODEM_CHECK_STATE_WAIT_FRAME_START,
    13. XMODEM_CHECK_STATE_WAIT_FRAME_DATA,
    14. XMODEM_CHECK_STATE_WAIT_FRAME_CHECK,
    15. XMODEM_CHECK_STATE_WAIT_RETURN_ACK,
    16. XMODEM_CHECK_STATE_WAIT_RETURN_NAK,
    17. XMODEM_CHECK_STATE_RX_EOT,
    18. XMODEM_CHECK_STATE_USER_FORCE_STOP_RX,
    19.  
    20. //TX
    21. XMODEM_CHECK_STATE_TX_CHECK_START_FLAG,
    22. XMODEM_CHECK_STATE_TX_WAIT_RETURN,
    23. }xmodem_check_state_t;

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

    1. typedef enum{
    2. XMODEM_TX_STATE_START = 0,
    3. XMODEM_TX_STATE_IS_INIT_API,
    4. XMODEM_TX_STATE_WAIT_START_FLAG,
    5. XMODEM_TX_STATE_READ_BUFFER,
    6. XMODEM_TX_STATE_TX,
    7. XMODEM_TX_STATE_WAIT_RETURN,
    8. XMODEM_TX_STATE_TX_EOT,
    9. }xmodem_tx_state_t;

           xmodem需要外部提供一些API借口,如下:

    1. typedef void start_timer(uint16_t hwDelayMs);
    2. typedef bool check_timer_flag(void);
    3. typedef bool user_buffer_write(uint8_t *pchBuffer,uint16_t hwNum);
    4. typedef uint16_t user_buffer_read( uint8_t *pchBuffer,uint16_t hwNum);
    5. typedef fsm_rt_t xmodem_serial_out(uint8_t *pchBuffer,uint16_t hwSize);
    6.  
    7. typedef struct _user_api_t user_api_t;
    8. struct _user_api_t{
    9. //延时相关
    10. start_timer *ptStartTimer;
    11. check_timer_flag *ptCheckTimerFlag;
    12.  
    13. //APP层对接
    14. user_buffer_write *ptWriteBuffer;
    15. user_buffer_read *ptReadBuffer;
    16.  
    17. //输出数据
    18. xmodem_serial_out *ptSerialOut;
    19. };

    说明:

    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

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

    模式定义:

    1. typedef enum{
    2. XMODEM_128_SUM = 0,
    3. XMODEM_128_CRC16,
    4. XMODEM_1K_SUM,
    5. XMODEM_1K_CRC16,
    6. }xmodem_select_mode_t;

    初始化:

    1. bool xmodem_init(xmodem_t *ptXmodem,xmodem_cfg_t *ptCfg,user_api_t *ptApi)
    2. {
    3. CLASS(xmodem_t) *ptThis = (CLASS(xmodem_t) *)ptXmodem;
    4. if(NULL == ptThis || NULL == ptCfg || NULL == ptApi){
    5. return false;
    6. }
    7.  
    8. if(NULL == ptApi->ptWriteBuffer ||
    9. NULL == ptApi->ptStartTimer ||
    10. NULL == ptApi->ptCheckTimerFlag ||
    11. NULL == ptApi->ptSerialRead ||
    12. NULL == ptApi->ptSerialWrite ||
    13. NULL == ptCfg->pchBuffer ){
    14. this.ptUserApi = NULL;
    15. this.tUserApiIsInitFlag = false;
    16. return false;
    17. }
    18.  
    19. this.tXmodemSelectMode = ptCfg->tXmodemSelectMode;
    20. this.pchBuffer = ptCfg->pchBuffer;
    21. this.chByte = 0x00;
    22.  
    23. this.ptUserApi = ptApi;
    24. this.tUserApiIsInitFlag = true;
    25.  
    26. return true;
    27. }

    下面的处理逻辑:

    1. switch(this.tXmodemSelectMode){
    2. case XMODEM_128_SUM:
    3. //break;
    4. case XMODEM_128_CRC16:
    5. if(SOH == this.pchBuffer[s_hwRevCnt]){
    6. s_tState = XMODEM_REV_FRAME_DATA;
    7. s_hwRevCnt++;
    8. }else if(EOT == this.pchBuffer[s_hwRevCnt]){
    9. RST_XMODEM_REV_FRAME_FSM();
    10. return XMODEM_REV_FRAME_RX_CPL;
    11. }else{
    12. //RST_XMODEM_REV_FRAME_FSM();
    13. return XMODEM_REV_FRAME_DROP;
    14. }
    15. break;
    16. case XMODEM_1K_SUM:
    17. //break;
    18. case XMODEM_1K_CRC16:
    19. if(STX == this.pchBuffer[s_hwRevCnt]){
    20. s_tState = XMODEM_REV_FRAME_DATA;
    21. s_hwRevCnt++;
    22. }else if(EOT == this.pchBuffer[s_hwRevCnt]){
    23. RST_XMODEM_REV_FRAME_FSM();
    24. return XMODEM_REV_FRAME_RX_CPL;
    25. }else{
    26. //RST_XMODEM_REV_FRAME_FSM();
    27. return XMODEM_REV_FRAME_DROP;
    28. }
    29. break;
    30. default:
    31. while(1);
    32. }
    1. if(XMODEM_REV_FRAME_CPL == tTemp){
    2. s_hwRevCnt++;
    3. switch(this.tXmodemSelectMode){
    4. case XMODEM_128_SUM:
    5. if(132 > s_hwRevCnt){
    6. break;
    7. }
    8. chTemp = calsum(this.pchBuffer,s_hwRevCnt-1);
    9. if(chTemp == this.pchBuffer[s_hwRevCnt-1]){
    10. RST_XMODEM_REV_FRAME_FSM();
    11. return XMODEM_REV_FRAME_CPL;
    12. }else{
    13. RST_XMODEM_REV_FRAME_FSM();
    14. return XMODEM_REV_FRAME_CHECK_ERROR;
    15. }
    16. break;
    17. case XMODEM_128_CRC16:
    18. if(133 > s_hwRevCnt){
    19. break;
    20. }
    21. hwTemp = calcrc(this.pchBuffer,s_hwRevCnt-2);
    22. tCrc16.chCrcL = this.pchBuffer[s_hwRevCnt-1];
    23. tCrc16.chCrcH = this.pchBuffer[s_hwRevCnt-2];
    24. if(tCrc16.hwCrc16 == hwTemp){
    25. RST_XMODEM_REV_FRAME_FSM();
    26. return XMODEM_REV_FRAME_CPL;
    27. }else{
    28. RST_XMODEM_REV_FRAME_FSM();
    29. return XMODEM_REV_FRAME_CHECK_ERROR;
    30. }
    31. break;
    32. case XMODEM_1K_SUM:
    33. if(1028 > s_hwRevCnt){
    34. break;
    35. }
    36. chTemp = calsum(this.pchBuffer,s_hwRevCnt-1);
    37. if(chTemp == this.pchBuffer[s_hwRevCnt-1]){
    38. RST_XMODEM_REV_FRAME_FSM();
    39. return XMODEM_REV_FRAME_CPL;
    40. }else{
    41. RST_XMODEM_REV_FRAME_FSM();
    42. return XMODEM_REV_FRAME_CHECK_ERROR;
    43. }
    44. break;
    45. case XMODEM_1K_CRC16:
    46. if(1029 > s_hwRevCnt){
    47. break;
    48. }
    49. hwTemp = calcrc(this.pchBuffer,s_hwRevCnt-2);
    50. tCrc16.chCrcL = this.pchBuffer[s_hwRevCnt-1];
    51. tCrc16.chCrcH = this.pchBuffer[s_hwRevCnt-2];
    52. if(tCrc16.hwCrc16 == hwTemp){
    53. RST_XMODEM_REV_FRAME_FSM();
    54. return XMODEM_REV_FRAME_CPL;
    55. }else{
    56. RST_XMODEM_REV_FRAME_FSM();
    57. return XMODEM_REV_FRAME_CHECK_ERROR;
    58. }
    59. break;
    60. default:
    61. while(1);
    62. }

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

    初始化:

    1. bool xmodem_init(xmodem_t *ptXmodem,xmodem_cfg_t *ptCfg,user_api_t *ptApi)
    2. {
    3. CLASS(xmodem_t) *ptThis = (CLASS(xmodem_t) *)ptXmodem;
    4. if(NULL == ptThis || NULL == ptCfg || NULL == ptApi){
    5. return false;
    6. }
    7.  
    8. if(NULL == ptApi->ptWriteBuffer ||
    9. NULL == ptApi->ptStartTimer ||
    10. NULL == ptApi->ptCheckTimerFlag ||
    11. NULL == ptApi->ptSerialRead ||
    12. NULL == ptApi->ptSerialWrite ||
    13. NULL == ptCfg->pchBuffer ){
    14. this.ptUserApi = NULL;
    15. this.tUserApiIsInitFlag = false;
    16. return false;
    17. }
    18.  
    19. switch(ptCfg->tXmodemSelectMode){
    20. case XMODEM_128_SUM:
    21. this.cRxFrameHead = SOH;
    22. this.hwRxFrameSize = 128+3+1;
    23. this.cTxStartUp = SUM_SAT;
    24. this.ptXmodemCheck = xmodem_sum_check;
    25. break;
    26. case XMODEM_128_CRC16:
    27. this.cRxFrameHead = SOH;
    28. this.hwRxFrameSize = 128+3+2;
    29. this.cTxStartUp = CRC16_SAT;
    30. this.ptXmodemCheck = xmodem_crc_check;
    31. break;
    32. case XMODEM_1K_SUM:
    33. this.cRxFrameHead = STX;
    34. this.hwRxFrameSize = 1024+3+1;
    35. this.cTxStartUp = SUM_SAT;
    36. this.ptXmodemCheck = xmodem_sum_check;
    37. break;
    38. case XMODEM_1K_CRC16:
    39. this.cRxFrameHead = STX;
    40. this.hwRxFrameSize = 1024+3+2;
    41. this.cTxStartUp = CRC16_SAT;
    42. this.ptXmodemCheck = xmodem_crc_check;
    43. break;
    44. default:
    45. return false;
    46. }
    47. this.pchBuffer = ptCfg->pchBuffer;
    48. this.chByte = 0x00;
    49.  
    50. this.ptUserApi = ptApi;
    51. this.tUserApiIsInitFlag = true;
    52.  
    53. return true;
    54. }

    然后下面的处理:

    1. if(this.cRxFrameHead == this.pchBuffer[s_hwRevCnt]){
    2. s_tState = XMODEM_REV_FRAME_DATA;
    3. s_hwRevCnt++;
    4. }else if(EOT == this.pchBuffer[s_hwRevCnt]){
    5. RST_XMODEM_REV_FRAME_FSM();
    6. return XMODEM_REV_FRAME_RX_CPL;
    7. }else{
    8. //RST_XMODEM_REV_FRAME_FSM();
    9. return XMODEM_REV_FRAME_DROP;
    10. }
    1. s_hwRevCnt++;
    2. if(this.hwRxFrameSize > s_hwRevCnt){
    3. break;
    4. }
    5. if(this.ptXmodemCheck(ptXmodem)){
    6. RST_XMODEM_REV_FRAME_FSM();
    7. return XMODEM_REV_FRAME_CPL;
    8. }else{
    9. RST_XMODEM_REV_FRAME_FSM();
    10. return XMODEM_REV_FRAME_CHECK_ERROR;
    11. }

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

  • 相关阅读:
    微博二级评论爬取
    爬取genome的网页和图片
    一个数据结构转换的问题
    SQLAlchemy ORM教程之二:Query
    SQLAlchemy中filter()和filter_by()有什么区别
    词云加显示条形图
    智联招聘的python岗位数据词云制作
    Python标准库——collections模块的Counter类
    MySQL5.6 windows msi安装介绍
    ICSharpCode.SharpZipLib.Zip
  • 原文地址:https://www.cnblogs.com/bluecagalli/p/10598665.html
Copyright © 2011-2022 走看看