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