1.介绍
Xmodem是一种在串口通信中广泛使用的异步文件传输协议,分为Xmodem(使用128字节的数据块)和1k-Xmodem(使用1024字节即1k字节的数据块)协议两种。
本文实现的是128字节数据块的Xmodem协议,采用CRC16校验,在项目中应用时,发送端和接收端可根据具体情况修改双方的协议。
如果你对串口通信还不太了解,可以看下我写的这篇博客使用Java实现串口通信。
2.实现
在和嵌入式同学调试的过程中,发现发送端发送数据过快,导致接收端处理不过来,所以在send方法中开启了一个子线程来处理数据发送逻辑,方便加入延时处理。
接收方法中,发送C是表示以CRC方式校验。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
|
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校验算法,采用的是查表法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
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.使用
1
2
3
4
5
|
// serialPort为串口对象 Xmodem xmodem = new Xmodem(serialPort.getInputStream(),serialPort.getOutputStream()); // filePath为文件路径 // ./bin/xxx.bin xmodem.send(filePath); |
4.写在最后
完整的代码下载
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://blog.csdn.net/kong_gu_you_lan/article/details/53673236