RT-Thread STM32F4 自制 BootLoader 的制作和使用(在线升级上位机篇Android)

前言说明

  前两篇文章分别介绍了通用BootLoader的编写和自制BootLoader的编写,本篇文章用来介绍串口YModem升级的上位机实现方法。

大致介绍

  在上位机实现在下位机的在线升级,最关键的就是实现串口YModem的通讯协议,由于很多嵌入式设计的多样性的需求,不可能有完美的第三方库满足需求,因此我们需要在第三方库的基础上进行修改。
采用的第三方库的地址为: https://github.com/ArdWang/YModemlib_Android.

思路分析

1.为什么采用YModem协议进行串口发送和接收呢?是不是有别的更好的方案?

  这是我一开始就思考的问题。在慢慢的调试和使用过程中,有了答案,串口通讯的协议方式有很多,甚至自己造一个都是可以的,但是采用YModem协议进行通讯,使用的人比较多,并且通讯准确可靠,带CRC校验,像xshell或者别的串口调试工具自带YModem协议,即使上位机的代码没有写,也可以采用串口调试工具来直接调试下位机的程序,比较方便。当然,如果也可以用来调试上位的代码。

2.,如果将600多kb的文件内容直接下发是否合适?

  在线升级的文件大小基本上都在几百KB以上,因此如果将文件一次性下发,是可以的,Linux系统对于输入和输出流的大小是有限制的,因此不仅仅Linux系统对输出流有缓存,Android系统下app 程序内部也要对发出的文件内容进行缓存,串口的输出速度是固定的,因此即使app程序将数据发送完成,数据真正到达到下位机的时间并不受APP控制。直接将几百KB大小的文件下发给RAM很小的单片机是不合适的,单片机无法快速的处理完毕这么多数据。

调试建议

  调试YModem协议可以采用Xshell自带的YModem通讯功能来进行调试,使用串口网络调试都比较方便。

综上:

  因此采用一个带CRC校验和分批发送数据的协议进行在线的升级是十分必要的。并且考察了RT-thread 通用BootLoader的升级方式就可以明白,RT-thread采用的是YModem-1K通讯协议。因此如果自制BootLoader,最好也采用YModem-1k协议,这样与通用BootLoader可以保持一定的兼容,也方便以后调试和移植。并且这种通讯协议可以很方便的更换通讯硬件,如网络,蓝牙等。

启动通讯

下面的代码是一个简单的数据发送启动示例。

 private void startTransmission(){
        //String md5 = MD5Util.MD5(mCurrentFilePath);
        yModem = new YModem.Builder()
                .with(this)
                .filePath(mCurrentFilePath)
                .fileName(mCurrentFileName)
                .checkMd5("")
                .callback(new YModemListener() {
                    @Override
                    public void onDataReady(byte[] data) { //数据发送
                        if(sendData) {
                            if (bluetoothService.writeCharacteristic(mSendOTACharacteristic, data)) {
                                Log.i("==Send Data is:", bytesToHexFun(data));
                            }
                        }
                    }
 
                    @Override
                    public void onProgress(int currentSent, int total) { //发送进度
                        float currentPt = (float)currentSent/total;
                        int a = (int)(currentPt*100);
                        mUpgradeBar.setProgress(currentSent);   // Main Progress
                        mUpgradeBar.setMax(total); // Maximum Progress
                        if(a<=100){
                            mUpgradeBar.setProgressText(""+a+"%");
                        }else{
                            mUpgradeBar.setProgressText("100%");
                        }
                    }
                    @Override
                    public void onSuccess() { //发送完毕
                        Toast.makeText(OTAActivity.this,"固件升级完成",Toast.LENGTH_LONG).show();
                        finish();
                    }
                    @Override
                    public void onFailed(String reason) {//发送失败
                        Toast.makeText(OTAActivity.this,reason,Toast.LENGTH_LONG).show();
                    }
                }).build();
        yModem.start(); //启动
    }

关键点说明

控制一次读取数据大小

  下面的代码是读取发送文件的方法,在FileStreamThread.java文件中,其中

byte[] block = new byte[1024];

是控制一次读取和发送数据的大小的,默认是1024字节。

    private void prepareData() throws IOException {
        initStream();
        byte[] block = new byte[1024]; //用于修改 一次输出的数据大小,128字节或者1024字节
        int dataLength;
        byte blockSequence = 1;//The data package of a file is actually started from 1
        isDataAcknowledged.set(true);
        isKeepRunning = true;
        while (isKeepRunning) {

            if (!isDataAcknowledged.get()) {
                try {
                    //We need to sleep for a while as the sending 1024 bytes data from ble would take several seconds
                    //In my circumstances, this can be up to 3 seconds.
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                continue;
            }

            if ((dataLength = inputStream.read(block)) == -1) {
                L.f("The file data has all been read...(文件所有内容读取完毕!)");
                if (listener != null) {
                    onStop();
                    listener.onFinish();
                }
                break;
            }

            byte[] packige = YModemUtil.getDataPackage(block, dataLength, blockSequence);

            if (listener != null) {
                listener.onDataReady(packige);
            }

            blockSequence++;
            isDataAcknowledged.set(false);
        }

    }

流程控制

  下面的代码是主要用于控制整个通讯过程中的流程的,这与标准的YModem-1K的协议流程是一致的,但是具体到应用中还需要进行调试测试,我在使用的过程中就发现在与XShell工具进行YModem通讯时是没问题的,但是到了与单片机进行YModem通讯时有时候会出现不响应的情况,请大家在使用时根据标准的YModem协议流程进行单步调试,测试。适当时要具体进行修改,提高代码的兼容性。

    /**
     * Method for the outer caller when received data from the terminal
     */
    public void onReceiveData(byte[] respData) {
        if (respData != null && respData.length > 0) {
            switch (CURR_STEP) {
                case STEP_HELLO:
                    handleHello(respData);
                    break;
                case STEP_FILE_NAME:
                    handleFileName(respData);
                    break;
                case STEP_FILE_BODY:
                    timerHelper.stopTimer();
                    handleFileBody(respData);
                    break;
                case STEP_EOT:
                    timerHelper.stopTimer();
                    handleEOT(respData);
                    break;
                case STEP_END:
                    timerHelper.stopTimer();
                    handleEnd(respData);
                    break;
                default:
                    break;
            }
        } else {
            L.f("The terminal do responsed something, but received nothing??");
        }
    }

数据发送的YModem协议

  这个YModem的协议与标准的YModem的协议有一点差异,主要是加入了文件的Md5校验和Hello握手数据的发送。这是为了方便把发送数据的主动权控制在发送端手中。如果有必要可以根据实际的需求来修改握手和MD5校验这部分代码。

/**
 * THE YMODEM:

 * HELLO BOOTLOADER ---------------------------------------------->* //主机发送HELLO数据并等待从机的回应,失败达到一定次数,则判断失败,从机收到后会回应ok(ox0C)
 * <---------------------------------------------------------------* C //从机回复OK
 * SOH 00 FF filename0fileSizeInByte0MD5[90] ZERO[38] CRC CRC----->* //主机发送 文件名和文件大小信息并附带发送的这段数据的CRC检验码
 * <---------------------------------------------------------------* ACK C //从机回复应答,并带ok
 * STX 01 FE data[1024] CRC CRC ---------------------------------->* //主机开始发送数据给从机 每次数据1024字节,并带着数据的CRC校验值,注意程序中计算数据发送进度的时候把CRC校验的数据也计算了进去,导致计算出的进度比实际进度快,目前没有修复这个问题(如果确实有必要可以考虑修改下代码进行修复)
 * <---------------------------------------------------------------* ACK //从机回复应该
 * STX 02 FF data[1024] CRC CRC ---------------------------------->*
 * <---------------------------------------------------------------* ACK
 * ...
 * ...
 * <p>
 * STX 08 F7 data[1000] CPMEOF[24] CRC CRC ----------------------->*
 * <---------------------------------------------------------------* ACK
 * EOT ----------------------------------------------------------->* //主机发送EOT指令给从机,表示要结束传输
 * <---------------------------------------------------------------* ACK //从机回复应答
 * SOH 00 FF ZERO[128] ------------------------------------------->* //发送结束数据帧
 * <---------------------------------------------------------------* ACK //从机回复应答
 * <---------------------------------------------------------------* MD5_OK //从机会MDK5检验ok

 */

标准YModem协议说明

基本流程

  YModem分成YModem-1K与YModem-g。
  YModem-1K用1024字节信息块传输取代标准的128字节传输,数据的发送回使用CRC校验,保证数据传输的正确性。它每传输一个信息块数据时,就会等待接收端回应ACK信号,接收到回应后,才会继续传输下一个信息块,保证数据已经全部接收。
  YModem-g传输形式与YModem-1K差不多,但是它去掉了数据的CRC校验码,同时在发送完一个数据块信息后,它不会等待接收端的ACK信号,而直接传输下一个数据块。正是它没有涉及错误校验,才使得它的传输速度比YModem-1K来得块。
  一般都会选择YModem-1K传输,平时所说的YModem也是指的是YModem-1K。下面就讲讲它的传输协议。

1、起始帧的数据格式

  YModem的起始帧并不直接传输文件的数据,而是将文件名与文件的大小放在数据帧中传输,它的帧长=3字节数据首部+128字节数据+2字节CRC16校验码=33字节。它的数据结构如下:

SOH  00 FF  filename[ ] filezise[ ]  NUL[ ] CRCH CRCL

  其中SOH=0x01,表示这个数据帧中包含着128字节的数据部分;在SOH后面的00 FF,00表示数据帧序号,因为是起始帧,所以它的帧序为00,至于FF,它是帧序的取反,YModem特地这么做是 为了给数据是否正确提供一种判断依据 ,通过判断这两个字节是否为取反关系,就可以知道数据是否传输出错;
  filename[ ]就是文件名,如文件名foo.c,它在数据帧中存放格式为:66 6F 6F 2E 63 00,一定要在文件名最后跟上一个00,表示文件名结束;
  filesize[ ]就是文件大小,如上面的foo.c的大小为1KByte,即1024Byte,需要先将它转化成16进制,即0x400,所以它在数据帧的存放格式为:34 30 30 00,即“400”,同样的文件大小最后需要跟上00,表示结束;
  NUL[ ]表示剩下的字节都用00填充,数据部分大小为128字节,除去文件名与文件大小占用的空间外,剩余的字节全部用00填充;CRCH CRCL分别表示16位CRC校验码的高8位与低8位。

2、数据帧的数据格式

  YModem的数据帧中会预留1024字节空间用来传输文件数据,它跟起始帧接收差不多,如下:

STX 01 FE data[1024] CRCH CRCL

  其中STX=0x02,表示这帧数据帧后面包含着1024字节的数据部分;STX后面的01 FE,01表示第一帧数据帧,FE则是它的取反,当然如果是第二帧数据的话就是:01 FD;data[1024]表示存放着1024字节的文件数据;CRCH与CRCL是CRC16检验码的高8位与低8位。
  如果文件数据的最后剩余的数据在128~1024之前,则还是使用STX的1024字节传输,但是剩余空间全部用0x1A填充,如下结构:

STX [num] [~num] data[ ] 1A ...1A CRCH CRCL

  有一种特殊的情况:如果文件大小小于等于128字节或者文件数据最后剩余的数据小于128字节,则YModem会选择SOH数据帧用128字节来传输数据,如果数据不满128字节,剩余的数据用0x1A填充这是数据正的结构就变成了:
文件大小小于128字节:

SOH 01 FE data[ ] 1A ...1A CRCH CRCL  

文件最后剩余数据小于128字节:

 SOH [num] [~~num] data[ ] 1A...1A CRCH CRCL
3、结束帧数据结构

  YModem的结束帧数据也采用SOH的128字节数据帧,它的结构如下:

SOH 00 FF NUL[128] CRCH CRCL

结束帧同样以SOH开头,表示后面跟着128字节大小的数据;结束帧的帧序也认为是00 FF;结束帧的128字节的数据部分不存放任何信息,即NUL[128]全部用00填充。

4、文件传输过程

  文件的传输过程,以具体的例子说明。把foo.c,大小为4196Byte(16进制为0x1064)的文件作为传输的对象,则它的传输过程如下:
  下面是对标准的YModem的协议进行说明,这个协议的流程是要熟悉的,这是为了方便后续代码调试。

步骤序号发送端数据数据方向接收端数据
1-<<<<C
2SOH 00 FF “foo.c” "1064’’ NUL[118] CRC CRC>>>>>-
3-<<<<ACK
4-<<<<C
5STX 01 FE data[1024] CRC CRC>>>>>-
6-<<<<ACK
7STX 02 FD data[1024] CRC CRC>>>>-
8-<<<<ACK
9STX 03 FC data[1024] CRC CRC>>>>-
10-<<<<<ACK
11STX 04 FB data[1024] CRC CRC>>>>-
12-<<<<ACK
13SOH 05 FA data[100] 1A[28] CRC CRC>>>>-
14-<<<<ACK
15EOT>>>>>-
16-<<<<<NAK
17EOT>>>>>-
18-<<<<<ACK
19-<<<<<C
20SOH 00 FF NUL[128] CRC CRC>>>>>-
21-<<<<<ACK
5、CRC的计算

  YModem的采用的是CRC16-CCITT欧洲版本的CRC校验,它的生成多项式为:x16+x12+x5+1,具体的CRC的计算可以查看源代码中内容。

关键点说明

  YModem的传输过程就是上面所示。但是上面传输过程中存在许多通信信号,它们的数值与意义如下表所示:

符号数值含义说明
SOH0x01128字节数据包
STX0x021024字节数据包
EOT0x04结束传输EOT信号由发送端发送
ACK0x06回应
NAK0x15不回应
CA0x18传输中止CA中止传输信号由发送端发送
C0x43请求数据包

总结

  GitHub提供的代码只是方便使用而已,其实在线升级的功能根据需求的不同有很多细节上的区别,关键是要理解了YModem协议后,根据具体的要求来修改自己的代码。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值