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 |
2 | SOH 00 FF “foo.c” "1064’’ NUL[118] CRC CRC | >>>>> | - |
3 | - | <<<< | ACK |
4 | - | <<<< | C |
5 | STX 01 FE data[1024] CRC CRC | >>>>> | - |
6 | - | <<<< | ACK |
7 | STX 02 FD data[1024] CRC CRC | >>>> | - |
8 | - | <<<< | ACK |
9 | STX 03 FC data[1024] CRC CRC | >>>> | - |
10 | - | <<<<< | ACK |
11 | STX 04 FB data[1024] CRC CRC | >>>> | - |
12 | - | <<<< | ACK |
13 | SOH 05 FA data[100] 1A[28] CRC CRC | >>>> | - |
14 | - | <<<< | ACK |
15 | EOT | >>>>> | - |
16 | - | <<<<< | NAK |
17 | EOT | >>>>> | - |
18 | - | <<<<< | ACK |
19 | - | <<<<< | C |
20 | SOH 00 FF NUL[128] CRC CRC | >>>>> | - |
21 | - | <<<<< | ACK |
5、CRC的计算
YModem的采用的是CRC16-CCITT欧洲版本的CRC校验,它的生成多项式为:x16+x12+x5+1,具体的CRC的计算可以查看源代码中内容。
关键点说明
YModem的传输过程就是上面所示。但是上面传输过程中存在许多通信信号,它们的数值与意义如下表所示:
符号 | 数值 | 含义 | 说明 |
---|---|---|---|
SOH | 0x01 | 128字节数据包 | |
STX | 0x02 | 1024字节数据包 | |
EOT | 0x04 | 结束传输 | EOT信号由发送端发送 |
ACK | 0x06 | 回应 | |
NAK | 0x15 | 不回应 | |
CA | 0x18 | 传输中止 | CA中止传输信号由发送端发送 |
C | 0x43 | 请求数据包 |
总结
GitHub提供的代码只是方便使用而已,其实在线升级的功能根据需求的不同有很多细节上的区别,关键是要理解了YModem协议后,根据具体的要求来修改自己的代码。