1.介紹
Xmodem是一種在串口通信中廣泛使用的異步文件傳輸協(xié)議,分為Xmodem(使用128字節(jié)的數(shù)據(jù)塊)和1k-Xmodem(使用1024字節(jié)即1k字節(jié)的數(shù)據(jù)塊)協(xié)議兩種。
本文實現(xiàn)的是128字節(jié)數(shù)據(jù)塊的Xmodem協(xié)議,采用CRC16校驗,在項目中應(yīng)用時,發(fā)送端和接收端可根據(jù)具體情況修改雙方的協(xié)議。
如果你對串口通信還不太了解,可以看下我寫的這篇博客使用Java實現(xiàn)串口通信。
2.實現(xiàn)
在和嵌入式同學(xué)調(diào)試的過程中,發(fā)現(xiàn)發(fā)送端發(fā)送數(shù)據(jù)過快,導(dǎo)致接收端處理不過來,所以在send方法中開啟了一個子線程來處理數(shù)據(jù)發(fā)送邏輯,方便加入延時處理。
接收方法中,發(fā)送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 ; // 結(jié)束 private final byte EOT = 0x04 ; // 應(yīng)答 private final byte ACK = 0x06 ; // 重傳 private final byte NAK = 0x15 ; // 無條件結(jié)束 private final byte CAN = 0x18 ; // 以128字節(jié)塊的形式傳輸數(shù)據(jù) private final int SECTOR_SIZE = 128 ; // 最大錯誤(無應(yīng)答)包數(shù) private final int MAX_ERRORS = 10 ; // 輸入流,用于讀取串口數(shù)據(jù) private InputStream inputStream; // 輸出流,用于發(fā)送串口數(shù)據(jù) private OutputStream outputStream; public Xmodem(InputStream inputStream, OutputStream outputStream) { this .inputStream = inputStream; this .outputStream = outputStream; } /** * 發(fā)送數(shù)據(jù) * * @param filePath * 文件路徑 */ public void send( final String filePath) { new Thread() { public void run() { try { // 錯誤包數(shù) int errorCount; // 包序號 byte blockNumber = 0x01 ; // 校驗和 int checkSum; // 讀取到緩沖區(qū)的字節(jié)數(shù)量 int nbytes; // 初始化數(shù)據(jù)緩沖區(qū) byte [] sector = new byte [SECTOR_SIZE]; // 讀取文件初始化 DataInputStream inputStream = new DataInputStream( new FileInputStream(filePath)); while ((nbytes = inputStream.read(sector)) > 0 ) { // 如果最后一包數(shù)據(jù)小于128個字節(jié),以0xff補齊 if (nbytes < SECTOR_SIZE) { for ( int i = nbytes; i < SECTOR_SIZE; i++) { sector[i] = ( byte ) 0xff ; } } // 同一包數(shù)據(jù)最多發(fā)送10次 errorCount = 0 ; while (errorCount < MAX_ERRORS) { // 組包 // 控制字符 + 包序號 + 包序號的反碼 + 數(shù)據(jù) + 校驗和 putData(SOH); putData(blockNumber); putData(~blockNumber); checkSum = CRC16.calc(sector) & 0x00ffff ; putChar(sector, ( short ) checkSum); outputStream.flush(); // 獲取應(yīng)答數(shù)據(jù) byte data = getData(); // 如果收到應(yīng)答數(shù)據(jù)則跳出循環(huán),發(fā)送下一包數(shù)據(jù) // 未收到應(yīng)答,錯誤包數(shù)+1,繼續(xù)重發(fā) if (data == ACK) { break ; } else { ++errorCount; } } // 包序號自增 blockNumber = ( byte ) ((++blockNumber) % 256 ); } // 所有數(shù)據(jù)發(fā)送完成后,發(fā)送結(jié)束標(biāo)識 boolean isAck = false ; while (!isAck) { putData(EOT); isAck = getData() == ACK; } } catch (Exception e) { e.printStackTrace(); } }; }.start(); } /** * 接收數(shù)據(jù) * * @param filePath * 文件路徑 * @return 是否接收完成 * @throws IOException * 異常 */ public boolean receive(String filePath) throws Exception { // 錯誤包數(shù) int errorCount = 0 ; // 包序號 byte blocknumber = 0x01 ; // 數(shù)據(jù) byte data; // 校驗和 int checkSum; // 初始化數(shù)據(jù)緩沖區(qū) byte [] sector = new byte [SECTOR_SIZE]; // 寫入文件初始化 DataOutputStream outputStream = new DataOutputStream( new FileOutputStream(filePath)); // 發(fā)送字符C,CRC方式校驗 putData(( byte ) 0x43 ); while ( true ) { if (errorCount > MAX_ERRORS) { outputStream.close(); return false ; } // 獲取應(yīng)答數(shù)據(jù) data = getData(); if (data != EOT) { try { // 判斷接收到的是否是開始標(biāo)識 if (data != SOH) { errorCount++; continue ; } // 獲取包序號 data = getData(); // 判斷包序號是否正確 if (data != blocknumber) { errorCount++; continue ; } // 獲取包序號的反碼 byte _blocknumber = ( byte ) ~getData(); // 判斷包序號的反碼是否正確 if (data != _blocknumber) { errorCount++; continue ; } // 獲取數(shù)據(jù) 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 ; } // 發(fā)送應(yīng)答 putData(ACK); // 包序號自增 blocknumber++; // 將數(shù)據(jù)寫入本地 outputStream.write(sector); // 錯誤包數(shù)歸零 errorCount = 0 ; } catch (Exception e) { e.printStackTrace(); } finally { // 如果出錯發(fā)送重傳標(biāo)識 if (errorCount != 0 ) { putData(NAK); } } } else { break ; } } // 關(guān)閉輸出流 outputStream.close(); // 發(fā)送應(yīng)答 putData(ACK); return true ; } /** * 獲取數(shù)據(jù) * * @return 數(shù)據(jù) * @throws IOException * 異常 */ private byte getData() throws IOException { return ( byte ) inputStream.read(); } /** * 發(fā)送數(shù)據(jù) * * @param data * 數(shù)據(jù) * @throws IOException * 異常 */ private void putData( int data) throws IOException { outputStream.write(( byte ) data); } /** * 發(fā)送數(shù)據(jù) * * @param data * 數(shù)據(jù) * @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.寫在最后
完整的代碼下載
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://blog.csdn.net/kong_gu_you_lan/article/details/53673236