1,簡介
2,傳輸過程
3,代碼實現 (我們是使用 android 設備USB 和主板通訊,當然串口也可以.基礎知識自行百度)
Xmodem 協議簡介:
??在串口通信中廣泛使用的異步文件傳輸協議有Xmodem,Ymodem,Zmodem.本文只介紹 Xmodem,其他兩種,未用過,沒研究.Xmodem協議分為兩種,一種是標準的 Xmode和Xmodem-1k 兩個版本.
??Xmodem內容固定長度為 128 個字節,格式如下:
Byte0 | Byte1 | Byte2 | Byte3~Byte130 | (Byte131~Byte132)/(Byte131) |
---|---|---|---|---|
SOH | 數據包序號 | 數據包序號補碼 | 數據包內容(128字節) | 數據校驗 |
??Xmodem-1k內容固定長度為 1024個字節,格式如下:
Byte0 | Byte1 | Byte2 | Byte3~Byte1026 | (Byte1027~Byte1028)/(Byte 1027) |
---|---|---|---|---|
STX | 數據包序號 | 數據包序號補碼 | 數據包內容(1024字節) | 數據校驗 |
控制符定義:
控制符 | 值 | 含義 |
---|---|---|
SOH | 0X01 | Xmodme 數據頭 |
STX | 0X02 | Xmodme -1k 數據頭 |
EOT | 0X04 | 發送結束/數據發送完畢 |
ACK | 0X06 | 應答標志/數據正確 |
NAK | 0X15 | 非應答標志,重傳(使用 CheckSum 數據校驗) |
CAN | 0X18 | 取消發送/終止發送 |
CRC16 | 0X43 | 使用 CRC16 數據校驗 |
數據包序號:
??據包序號下標是從0x01開始向上遞增的,累加到0XFF后將循環反復. (0~255)
數據包序號補碼:
??數據包序號按位取反,即 ~數據包序號
校驗和的方式有CRC校驗和 CheckSum校驗:
??使用不同的校驗方式,占的字節數不一樣哦~
??CRC校驗:
????占 2 個字節,,CRC多項式公式為X16+X12+X5+1然后取低16位數據.(網上說的,我也不理解,沒研究)
@JvmStatic
fun calc16(bytes: ByteArray): ByteArray? {
var crc = 0x0000.toChar()
for (b in bytes) {
crc = (crc.toInt() shl 8 xor crctable[crc.toInt() shr 8 xor b.toInt() and 0x00ff].toInt()).toChar()
}
return intToByteArray(crc.toInt() and 0x00ffff)
}
@JvmStatic
fun intToByteArray(i: Int): ByteArray? {
val result = ByteArray(2)
result[0] = (i shr 8 and 0xFF).toByte()
result[1] = (i and 0xFF).toByte()
return result
}
??CheckSum校驗:
????占1 個字節,對傳輸的數據進行累加和,然后轉成16 進制,用兩個字節的來表示,取第 1 位.
@JvmStatic
fun getCheckSum(bytes: ByteArray): Byte {
var checkSum = 0
for (aByte in bytes) {
checkSum += aByte.toInt()
}
val bytes1 = intToByteArray(checkSum.toLong())
return bytes1!![1]
}
//將 int 轉成 byte 數組,文件大小,用兩個字節表示
@JvmStatic
fun intToByteArray(i: Int): ByteArray? {
val result = ByteArray(2)
result[0] = (i shr 8 and 0xFF).toByte()
result[1] = (i and 0xFF).toByte()
return result
}
Xmodem 傳輸過程:
android 程序 | Flow | 主板 |
---|---|---|
<— CRC16 (0X43)/NAK (0X15) | ||
SOH+0x01+0xFE+Data[0-127]+數據校驗 | —> | Packet ok |
<— ACK(0X06) | ||
SOH+0x02+ 0xFD+Data[0-127]+數據校驗 | —> | Packet ok |
<— ACK(0X06) | ||
SOH+0x03+0xFC+Data[0-127]+數據校驗 | —> | Packet ok |
<— ACK(0X06) | ||
.... | .... | .... |
EOT | —> | Packet ok |
Finished | <— ACK |
1、當主板給 Android 設備發送的C或者NAK后,android設備則知道主板已開啟 Xmodem 協議傳輸,然后開始發送數據:SOH/STX+0x01+0xFE+Data[0-127]/Data[0-1024]+數據校驗
2、主板接收到數據后,對數據進行校驗,數據正確則回復ACK確認字節,android 設備接收到ACK確認后,就認為數據包被接收方正確接收了,接著發送下一包數據.
3、如果android設備NAK字節,則表示需要重新傳輸剛才的數據包;如果android 設備收到的CAN字節,則表示無條件停止傳輸。
4、傳輸完畢后,等待主板喚醒 android 設備,判斷固件是否更新成功.(這部分跟主板開發進行協商吧~~)
Xmodem 代碼實現:
隨意寫寫,可自行進行封裝~~~
public class Xmodem2 {
public static int checkType = 1; //校驗類型 1是csc/2是check-sum,由主板回復值,判斷賦值
public static int transType = 2; //傳輸類型 Xmodem/1K-Xmodem ,android 設備主動觸發
public static final byte SOH = 0x01; // 開始 Xmodem數據頭
public static final byte STX = 0x02; // 開始 1K-Xmodem數據頭
public static final byte EOT = 0x04; // 結束
public static final byte ACK = 0x06; // 應答
public static final byte NAK = 0x15; // 重傳
public static final byte CAN = 0x18; // 無條件結束
public static final byte C = 0x43; // csc校驗
public static int SECTOR_SIZE = 0;// 以128字節塊的形式傳輸數據
public static final int MAX_ERRORS = 10; // 最大錯誤(無應答)包數
public static UsbSerialPort port; //寫入數據
public static String filePath; //文件路徑
public static byte responseACK; //由主板是否回復 ACK,由主板回復值,判斷賦值
public static boolean finishAck = false; //數據傳輸結束ACK
private final ExecutorService executor = Executors.newFixedThreadPool(5);
public Xmodem2(String path, UsbSerialPort usbSerialPort) {
filePath = path;
port = usbSerialPort;
if (transType == 1)
SECTOR_SIZE = 128;
else
SECTOR_SIZE = 1024;
}
public void send() {
executor.submit(() -> {
try {
int errorCount;// 錯誤包數
byte blockNumber = 0x01;// 包序號
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) 0X1A;
}
}
// 同一包數據最多發送10次
errorCount = 0;
while (errorCount < MAX_ERRORS) {
// 組包
// 控制字符1 + 包序號1 + 包序號的反碼1 + 數據128 + 校驗 1->crc/2-> check-sum
byte[] sendData = new byte[SECTOR_SIZE + (checkType == 1 ? 5 : 4)];
sendData[0] = SECTOR_SIZE == 128 ? SOH : STX;
sendData[1] = blockNumber;
sendData[2] = (byte) ~blockNumber;
System.arraycopy(sector, 0, sendData, 3, sector.length);
if (checkType == 1) { //CRC
byte[] crc16 = CRC16.calc16(sector);
if (transType == 1) {
System.arraycopy(crc16, 0, sendData, 131, crc16.length); //C+128+ CRC
} else {
System.arraycopy(crc16, 0, sendData, 1027, crc16.length); //C+1014+ CRC
}
} else { // checkSum
if (transType == 1) {
sendData[131] = CRC16.getCheckSum(sector); //15+128+ Check-Sum
} else {
sendData[1027] = CRC16.getCheckSum(sector); //15+1014+ Check-Sum
}
}
//==============================================
String s = Utils.INSTANCE.toHexString(sendData);
Log.e("11111", "傳輸的數據: " + s);
//===============================================
port.write(sendData, 5000);
// 獲取應答數據
// 如果收到應答數據則跳出循環,發送下一包數據
// 未收到應答,錯誤包數+1,繼續重發
Thread.sleep(50);
if (responseACK == ACK) {
responseACK = 0;
break;
} else {
errorCount++;
}
}
// 包序號自增
blockNumber = (byte) ((++blockNumber) % 256);
}
// 所有數據發送完成后,發送結束標識
errorCount = 0;
while (errorCount < MAX_ERRORS) {
Log.e("11111", "發送結束標志啦");
port.write(new byte[]{EOT}, 5000);
Thread.sleep(50);
if (responseACK == ACK) {
finishAck = true;
break;
} else {
errorCount++;
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
使用:
Xmodem2(path, UsbHelper.INSTANCE.port).send()