升级介绍
蓝牙固件升级是使用手机给固件进行版本升级,以达到修复bug或者添加新功能的作用。升级的大概流程是:首先,当蓝牙固件需要升级时,由嵌入式开发人员提供新的固件,由服务器管理人员将固件放到服务器上,此时,用户打开手机APP的时候会检测到服务器有更新,请求最新的蓝牙固件,确认更新后,手机会从服务器下载固件。下载完毕后,APP会读取固件内容,并根据升级协议将内容传到蓝牙固件里,完成升级。
DFU = Device Firmware Update (设备固件更新)
OTA = Over The Air (空中升级)
升级流程
各个蓝牙设备不尽相同,以下是我测试设备的升级流程:升级固件为.bin后缀的文件,文件名会有一定的格式。
我司的硬件工程师一般发送给我们的升级软件是hex文件,然后升级的时候需要的是bin文件这就需要我们把hex文件转换为bin文件。转换bin文件有两种方式:
1.通过软件的方式,软件的方式我是通过软件J-Flash来转换的。具体操作流程如下:
(1)打开J-Flash选择Create a new project。
(2)把hex文件拖入J-Flash 。
(3)找到hex文件对应的结束的最后一位的位置。
(4)选择Save data file as 保存类型选bin类型,然后点击保存之后弹出Enter address range框 start address 保持不变,End address 输入你想要转换文件的结束地址,然后点击OK hex转bin文件转换成功。
2.通过到代码的方式。转换的代码如下:
//
// LBHexToBin.m
//
// Created by lingbing on 2020/9/18.
//
#import
NS_ASSUME_NONNULL_BEGIN
@interface LBHexToBin : NSObject
+ (NSData*)convert:(NSData*)hex;
@end
NS_ASSUME_NONNULL_END
//
// LBHexToBin.m
//
// Created by lingbing on 2020/9/18.
//
#import "LBHexToBin.h"
@implementation LBHexToBin
+ (constByte)ascii2char:(constByte*)ascii
{
if(*ascii >='A')
return*ascii -0x37;
if(*ascii >='0')
return*ascii -'0';
return-1;
}
+ (constByte)readByte:(constByte*)pointer
{
Bytefirst = [LBHexToBinascii2char:pointer];
Bytesecond = [LBHexToBinascii2char:pointer +1];
return(first <<4) | second;
}
+ (constUInt16)readAddress:(constByte*)pointer
{
Bytemsb = [LBHexToBinreadByte:pointer];
Bytelsb = [LBHexToBinreadByte:pointer +2];
return(msb <<8) | lsb;
}
+ (NSUInteger)calculateBinLength:(NSData*)hex
{
if(hex ==nil|| hex.length==0)
{
return0;
}
NSUIntegerbinLength =0;
constNSUIntegerhexLength = hex.length;
constByte* pointer = (constByte*)hex.bytes;
UInt32lastBaseAddress =0;
do
{
constBytesemicollon = *pointer++;
// Validate - each line of the file must have a semicollon as a firs char
if(semicollon !=':')
{
return0;
}
constUInt8reclen = [LBHexToBinreadByte:pointer]; pointer +=2;
constUInt16offset = [LBHexToBinreadAddress:pointer]; pointer +=4;
constUInt8rectype = [LBHexToBinreadByte:pointer]; pointer +=2;
switch(rectype) {
case0x04: {
// Only consistent hex files are supported. If there is a jump to non-following ULBA address skip the rest of the file
constUInt32newULBA = [LBHexToBinreadAddress:pointer];
if(binLength >0&& newULBA != (lastBaseAddress >>16) +1)
returnbinLength;
lastBaseAddress = newULBA <<16;
break;
}
case0x02: {
// The same with Extended Segment Address. The calculated ULBA must not be greater than the last one + 1
constUInt32newSBA = [LBHexToBinreadAddress:pointer] <<4;
if(binLength >0&& (newSBA >>16) != (lastBaseAddress >>16) +1)
returnbinLength;
lastBaseAddress = newSBA;
break;
}
case0x00:
// If record type is Data Record (rectype = 0), add it's length (only it the address is >= 0x1000, MBR is skipped)
if(lastBaseAddress + offset >=0x1000)
binLength += reclen;
default:
break;
}
pointer += (reclen <<1); // Skip the data when calculating length
pointer +=2; // Skip the checksum
// Skip new line
if(*pointer =='\r') pointer++;
if(*pointer =='\n') pointer++;
}while(pointer != hex.bytes+ hexLength);
returnbinLength;
}
+ (NSData*)convert:(NSData*)hex
{
constNSUIntegerbinLength = [LBHexToBincalculateBinLength:hex];
constNSUIntegerhexLength = hex.length;
constByte* pointer = (constByte*)hex.bytes;
NSUIntegerbytesCopied =0;
UInt32lastBaseAddress =0;
Byte* bytes =malloc(sizeof(Byte) * binLength);
Byte* output = bytes;
do
{
constBytesemicollon = *pointer++;
// Validate - each line of the file must have a semicollon as a firs char
if(semicollon !=':')
{
free(bytes);
returnnil;
}
constUInt8reclen = [LBHexToBinreadByte:pointer]; pointer +=2;
constUInt16offset = [LBHexToBinreadAddress:pointer]; pointer +=4;
constUInt8rectype = [LBHexToBinreadByte:pointer]; pointer +=2;
switch(rectype) {
case0x04: {
constUInt32newULBA = [LBHexToBinreadAddress:pointer]; pointer +=4;
if(bytesCopied >0&& newULBA != (lastBaseAddress >>16) +1)
return[NSDatadataWithBytesNoCopy:byteslength:bytesCopied];
lastBaseAddress = newULBA <<16;
break;
}
case0x02: {
constUInt32newSBA = [LBHexToBinreadAddress:pointer] <<4; pointer +=4;
if(bytesCopied >0&& (newSBA >>16) != (lastBaseAddress >>16) +1)
return[NSDatadataWithBytesNoCopy:byteslength:bytesCopied];
lastBaseAddress = newSBA;
break;
}
case0x00:
// If record type is Data Record (rectype = 0), copy data to output buffer
// Skip data below 0x1000 address (MBR)
if(lastBaseAddress + offset >=0x1000)
{
for(inti =0; i < reclen; i++)
{
*output++ = [LBHexToBinreadByte:pointer]; pointer +=2;
bytesCopied++;
}
}
else
{
pointer += (reclen <<1); // Skip the data
}
break;
default:
pointer += (reclen <<1); // Skip the irrelevant data
break;
}
pointer +=2; // Skip the checksum
// Skip new line
if(*pointer =='\r') pointer++;
if(*pointer =='\n') pointer++;
}while(pointer != hex.bytes+ hexLength);
return[NSDatadataWithBytesNoCopy:byteslength:bytesCopied];
}
@end
升级过程:
1.扫描并连接要升级的蓝牙设备
2.发送给外设升级前的验证指令,验证是否是合法升级
3.外设验证返回成功之后,发送授权升级指令,指令返回成功之后开始固件升级
3.判断随机数无误,准备发送打包好的数据
4.真正发送打包好的数据(每次发送10包,一包20个字节),这里会重复N多次,看你的原数据包有多大;每次接到我发的包后,外设都会给我会OK否,我收到OK后才会发一下个数据包
5.告诉外设我数据发送完毕,并发送一段指令(包括本次空中升级数据包的大小,还有加密参数什么的)
6.外设给我回OK无误后,才算真正升级完成