Android 固件升级之 Xmodem

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()

---------------------------END-------------------------

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容