0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

针对app开发过程中对OAD功能做一个流程和代码解读

电子设计 来源:网友电子设计发布 作者:网友电子设计发布 2021-12-29 14:45 次阅读

CC2640 R2是一款面向 Bluetooth Smart 应用的低功耗无线 MCU。该芯片运行TI的BLE协议栈,并支持OAD(Over the Air Download)空中固件升级功能,此空中固件升级功能就是利用Android或者iOS产品对应app通过BLE对CC2640R2的产品进行固件升级。同时,TI其实提供了Anroid和iOS的源码对其支持:http://www.ti.com/tool/SENSORTAG-SW?keyMatch=sensortag&tisearch=Search-EN-Everything。这部分的源码是基于TI的SensorTag硬件进行开发的,包含了很多内容,对于客户来说,基本上不适合直接拿去使用,但是其中的OAD部分代码,却是可以通用的。但是对于客户的iOS或者Android app工程师来说,往往对BLE协议不熟,那就更不用说TI的OAD协议了,所以即使提供了源码,客户在这一部分开发起来还是很困难。本文就针对这一点,针对app开发过程中对OAD功能做一个流程和代码解读,用以帮助客户更方便完成此功能开发。

首先,第一步是从上面的链接下载最新的android和iOS源码,上述链接最终会指引到github的下载地址。

有了源码之后,我们就可以解读了。

从app的角度上来看TI的OAD协议,大致是这样的:

App连接上设备之后,就能发现OAD的service和characteristics(服务和特征值):

服务的UUID:0xFFC0,对应的128bit UUID:

F000FFC0-0451-4000-B000-000000000000

服务下面有两个主要特征值:(其他特征值可以暂且忽略)

OAD Image Identify,UUID:0xFFC1;OAD Image Block,UUID:0xFFC2,对应的128bit UUID:

– OAD Image Identify F000FFC1-0451-4000-B000-000000000000,用于交互确认固件版本信息

– OAD Image Block F000FFC2-0451-4000-B000-000000000000,用于传送新的固件。

固件更新的所有操作都是对上面这两个特征值进行操作。

用TI的SensorTag app可以看到:

用第三方app比如light blue也能看到,安卓手机的情况也是一样。

在TI提供的示例代码中,在Android中,客户唯一要用到的文件其实就是FwUpdateActivity_CC26xx.java,所有的OAD流程基本全部都在这里。另外有一个相关的文件BluetoothLeService.java,这个是TI的SensorTag App封装的BLE相关API接口集合,主要用于一些特征值的操作,比如write,read等,OAD用到的主要就是write,通过write来神奇地实现各种流程。在iOS源码中,流程相关的主要是BLETIOAD2Profile.m(注意,另外一个BLETIOADProfile.m,这是针对旧版的CC254x的,和CC26xx略有不同,这里不做讨论,有兴趣可以自己去看),基本就在这里,另外对应也有一个BLE相关接口集合BLEUtility.m。

第一步

从app来讲,开始OAD的第一步就是先使能上面两个特征值的notification功能。

简单来说就是调用android或者iOS提供的现有API,分分钟完成。(蓝牙协议上来说就是往这两个特征值的CCC句柄上写01:00,我们这里尽量不讨论具体蓝牙协议,从简考虑,有兴趣的人可以自己去研究一下)。

对应到代码里,

Android在FwUpdateActivity_CC26xx.java中,新的固件文件装载到手机内存后:public void onLoad(View v)中调用:

mLeService.setCharacteristicNotification(mCharIdentify, true);

mLeService.setCharacteristicNotification(mCharBlock, true);

实际上可以根据客户真实代码在适当位置加上面两个函数就行,这样第一步其实就完成了。

*更多说明:

上面两个函数,其实就是BluetoothLeService.java中的API,最终追踪下去的话是调用Android SDK 的BLE API来使能notification:

mBluetoothGatt.setCharacteristicNotification(request.characteristic, request.notifyenable)

其实我们也可以直接调用这个来实现上面的功能。

iOS在BLETIOAD2Profile.m里的-(void) configureProfile 函数里调用:

CBUUID *sUUID = [CBUUID UUIDWithString:TI_OAD_SERVICE];

CBUUID *cUUID = [CBUUID UUIDWithString:TI_OAD_IMAGE_NOTIFY];

[BLEUtility setNotificationForCharacteristic:self.d.p sCBUUID:sUUID cCBUUID:cUUID enable:YES];

cUUID = [CBUUID UUIDWithString:TI_OAD_IMAGE_BLOCK_REQUEST];

if (self.notifications)[BLEUtility setNotificationForCharacteristic:self.d.p sCBUUID:sUUID cCBUUID:cUUID enable:YES];

注意,iOS代码里面OAD Image Identify和OAD Image Block对应的是TI_OAD_IMAGE_NOTIFY和TI_OAD_IMAGE_BLOCK_REQUEST。同样,在iOS里适当位置调用这个函数就行。

*更多说明:

对于iOS TI给出的源码,是BLEUtility.m中封装了Apple的BLE API来使能notification:

- (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic;

一样,我们也可以直接调用这个来实现上面的功能。

空中sniffer抓包看的话,就能看到这两个使能notification的流程:

第二步

从app角度来讲,第二步就是要把新固件的版本信息从手机传送到外设上,让外设进行判断是否要升级。

这一步就要用到OAD Image Identify,这个特征值在整个OAD过程中只会用到一次,就是在这里。

App这边先从获得到的新固件里把固件的image header (16个字节) 读出来,image header就是固件的二进制文件的开始16个字节,很容易获取到。

体现在代码里,

Android:还是在FwUpdateActivity_CC26xx.java中,在打开新固件文件的时候:

private boolean loadFile(String filepath, boolean isAsset) {

顺便创建要发送的16个字节image header结构:

mFileImgHdr = new ImgHdr(mFileBuffer,readLen);

这个构造函数就会把image header部分给组织好放到16字节的一个buffer中去,用以接下来发送给设备。这个类的定义也在FwUpdateActivity_CC26xx.java中:

private class ImgHdr {

……

ImgHdr(byte[] buf, int fileLen) {

this.len = (fileLen / (16 / 4));

this.ver = 0;

this.uid[0] = this.uid[1] = this.uid[2] = this.uid[3] = ‘E’;

this.addr = 0;

this.imgType = 1; //EFL_OAD_IMG_TYPE_APP

this.crc0 = calcImageCRC((int)0,buf);

crc1 = (short)0xFFFF;

……

}

可以看到构造函数里面,虽然有读取到的实际固件文件的image header作为输入参数,但我们实际的代码里面为了演示,只是写死了一些内容。这里可以根据实际情况修改一下,根据前面提到的16字节header的顺序来就行,比如:

private class ImgHdr {

……

ImgHdr(byte[] buf, int fileLen) {

this.len = (fileLen / (16 / 4));

this.ver = buf[5];

this.ver = (this.ver 《《 8) | buf[4];

this.uid[0] = buf[8];this.uid[1] = buf[9];

this.uid[2] = buf[10]; this.uid[3] = buf[11];

this.addr = buf[13];

this.addr = (this.addr 《《 8) | buf[12];

this.imgType = buf[14];//EFL_OAD_IMG_TYPE_APP

this.crc0 = calcImageCRC((int)0,buf);

crc1 = (short)0xFFFF;

……

}

这样就基本能拿到实际的真实数据了。

iOS:就很简单了,直接把新固件文件开始的16字节header复制到代码里就可以了,还是在BLETIOAD2Profile.m里,在-(void) uploadImage 函数中:

img_hdr_t2 imgHeader;

uint32_t pages = ((uint32_t)self.imageFile.length / 4096);

//做个CRC校验,放到image header中去,同时把image header的16字节内容补完整。

[self calcImageInfo:0 pages:pages imageHeader:&imgHeader buf:imageFileData];

memcpy(requestData, &imgHeader, 16);

需要注意的是iOS给的源码里面,image header内容的补充是在crc校验函数里面一并完成的:-(void) calcImageInfo里:

imageHeader buf:(uint8_t *)buf {

imageHeader-》len = (pages * FLASH_PAGE_SIZE) / (OAD_BLOCK_SIZE / FLASH_WORD_SIZE);

imageHeader-》ver = 0;

imageHeader-》uid[0] = imageHeader-》uid[1] = imageHeader-》uid[2] = imageHeader-》uid[3] = ‘E’;

imageHeader-》addr = (firstpage * FLASH_PAGE_SIZE) / (OAD_BLOCK_SIZE / FLASH_WORD_SIZE);

imageHeader-》imgType = EFL_OAD_IMG_TYPE_APP;

imageHeader-》res[0] = 0xff;

imageHeader-》crc0 = [self calcImageCRC:firstpage imageHeader:imageHeader buf:buf];

imageHeader-》crc1 = 0xffff;

}

这样iOS这里也得到完整的image header信息了。

接下来app将通过OAD Image Identify这个特征值首先把前面得到的新的固件的版本信息发送给设备(CC2640R2),这个版本信息包含在前面得到的image header的16个字节buffer里,把这个发出去给设备就行了。

体现在代码里,

Android,还是在FwUpdateActivity_CC26xx.java中,开始流程函数:

private void startProgramming() {

获取image identify(其实就是image header):

mCharIdentify.setValue(mFileImgHdr.getRequest());

并通过BLE的write characteristic动作从app发送到设备端:

mLeService.writeCharacteristic(mCharIdentify);

*更多说明:

上面write characteristic函数其实就是BluetoothLeService.java中的API,最终追踪下去的话是调用Android SDK 的BLE API来进行BLE的write command操作:

mBluetoothGatt.writeCharacteristic(request.characteristic);

其实我们也可以直接调用这个来实现上面的功能。

iOS代码中,在BLETIOAD2Profile.m里,还是在-(void) uploadImage 函数里:

CBUUID *cUUID = [CBUUID UUIDWithString:TI_OAD_IMAGE_NOTIFY];

[BLEUtility writeNoResponseCharacteristic:self.d.p sCBUUID:sUUID cCBUUID:cUUID data:[NSData dataWithBytes:requestData length:16]];

通过 write characteristic来把image header发送到设备端。

*更多说明:

这个的write函数,其实也是在BLEUtility.m里面封装了Apple的BLE标准API:

- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;

我们也可以直接调用这个来实现上面的功能。

空中sniffer抓包能看到write command发送image header:

最后,设备收到image header之后,会进行和自己本身的固件版本号进行比较。如果发现image header中的版本号没有自己的版本号新,那么就直接以notification (这里notification是在前面第一步使能)的形式在OAD Image Identify上回复自己的版本号给手机,表示拒绝此次固件升级,此次升级就此结束。

空中sniffer抓包,其中标黄的部分就是设备回复自身版本号,表示拒绝此次固件升级:

如果发现image header中的版本号确实比自己本身的固件版本号要新,那么就同意这次固件更新,会在OAD Image Block这个特征值的notification (这里notification是在前面第一步使能)上回复0x0000,表示准备接受序列号第0个固件内容包。注意实在OAD Image Block这个特征值上,不是OAD Image Identify这个特征值上,OAD Image Identify这个特征值的使命在前面已经完成,后面不会再使用。

从sniffer看就会看到0x0000从外设发回手机:

总结下来第二步,就是app要在OAD Image Identify特征值上发送新固件的image header到设备端,然后设备端进行判断是否要接收新的固件进行升级,如果要升级,则在OAD Image Block特征值上向手机回复0x0000,不然回复自己目前的固件版本号表示拒绝更新。总结起来就是下面的两个流程图:

第三步

这一步从app来讲就是按照顺序发送固件内容了。

手机在OAD Image Block特征值上收到0x0000之后就代表设备端愿意接收新固件,并且意思是对方准备好接收第0个固件包。每个固件包的内容长度是16个字节,并且需要在头部放上两个字节的序列号,序列号从0x0000开始累加,一直到最后一个固件包。

*注意这个序列号是小端在前的,所以后面是0x0100,0x0200,0x0300,0x0400,…,0xFF00,0x0001,0x0101,0x0201,…这样累加上去。

从空中sniffer上看,很容易就能看到整个流程的交互:

首先是手机发送第0包的固件内容,通过BLE的Write方式在OAD Image Block特征值上发送,固件block内容是16个字节,注意下图标黄的头两个字节,是固件包序列号,第0包是0x0000,所以包的总长是16+2=18个字节。

那么设备收到APP发送过去的第0包固件后,回复请求下一个固件包,通过Notification的方式在OAD Image Block特征值上回复0x0100:

APP收到设备发回的0x0100,就知道对方已经等待接收下一包固件,于是就发送下一包0x0100的固件包,以此类推,一直到固件发送结束。

具体到代码里,

在Android中,就是在programBlock()函数里,这里我们只关注核心部分:

private void programBlock() {

省略前面逻辑相关代码,工程里面很容易看懂。

// Prepare block

首先自然是包序列号,buffer的头两个字节:

mOadBuffer[0] = Conversion.loUint16(mProgInfo.iBlocks);

mOadBuffer[1] = Conversion.hiUint16(mProgInfo.iBlocks);

然后是16个字节的固件block包内容:

System.arraycopy(mFileBuffer, mProgInfo.iBytes, mOadBuffer, 2, OAD_BLOCK_SIZE);

// Send block

接着把这18个字节通过write方式在OAD Image Block特征值上发送:

mCharBlock.setValue(mOadBuffer);

boolean success = mLeService.writeCharacteristicNonBlock(mCharBlock);

如果发送成功,那么就顺移相关的包序列号还有固件文件中的位移标志:

if (success) {

// Update stats

packetsSent++;

mProgInfo.iBlocks++;

mProgInfo.iBytes += OAD_BLOCK_SIZE;

mProgressBar.setProgress((mProgInfo.iBlocks * 100) / mProgInfo.nBlocks);

如果最后一个固件block成功完成,那么就恭喜,OAD顺利成功!

if (mProgInfo.iBlocks == mProgInfo.nBlocks) {

b.setTitle(“Programming finished”);

b.setPositiveButton(“OK”,null);

AlertDialog d = b.create();

d.show();

mProgramming = false;

mLog.append(“Programming finished at block ” + (mProgInfo.iBlocks + 1) + “\n”);

}

} else {

mProgramming = false;

msg = “GATT writeCharacteristic failed\n”;

}

}

*更多说明:

上面boolean success = mLeService.writeCharacteristicNonBlock(mCharBlock);这个函数,其实就是BluetoothLeService.java中的API,最终追踪下去的话是调用Android SDK 的BLE API来实现的:mBluetoothGatt.writeCharacteristic(request.characteristic);

iOS的代码里,体现在BLETIOAD2Profile.m里的-(void) sendOnePacket 函数,只看核心部分,和Android很像的,因为流程一样:

-(void) sendOnePacket {

//Prepare Block

首先自然是包序列号,buffer的头两个字节:

uint8_t requestData[2 + OAD_BLOCK_SIZE];

requestData[0] = LO_UINT16(self.iBlocks);

requestData[1] = HI_UINT16(self.iBlocks);

然后是16个字节的固件block包内容:

memcpy(&requestData[2] , &imageFileData[self.iBytes], OAD_BLOCK_SIZE);

CBUUID *sUUID = [CBUUID UUIDWithString:TI_OAD_SERVICE];

CBUUID *cUUID = [CBUUID UUIDWithString:TI_OAD_IMAGE_BLOCK_REQUEST];

接着把这18个字节通过write方式在OAD Image Block特征值上发送:

[BLEUtility writeNoResponseCharacteristic:self.d.p sCBUUID:sUUID cCBUUID:cUUID data:[NSData dataWithBytes:requestData length:2 + OAD_BLOCK_SIZE]];

如果发送成功,那么就顺移相关的包序列号还有固件文件中的位移标志:

dataWithBytes:requestData length:2 + OAD_BLOCK_SIZE]);

self.sndDataCount ++;

self.iBlocks++;

self.iBytes += OAD_BLOCK_SIZE;

self.sentPackets++;

如果最后一个固件block成功完成,那么就恭喜,OAD顺利成功!

if(self.iBlocks == self.nBlocks) {

self.inProgramming = NO;

[self.oadDelegate didFinishUploading];

return;

}

流程和Android完全一样,我连注释都直接复制过来了J。

这样就是完整的OAD流程了。总结就是分三步走:使能OAD Image Identify和OAD Image Block 的notification,在OAD Image Identify上发送新固件版本(image header)进行确认,最后在OAD Image Block上按顺序把固件发送完,结束。

审核编辑:何安

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式处理
    +关注

    关注

    0

    文章

    341

    浏览量

    9963
收藏 人收藏

    评论

    相关推荐

    康谋分享 | 在基于场景的AD/ADAS验证过程中,识别挑战性场景!

    基于场景的验证是AD/ADAS系统开发过程中的重要步骤,然而面对海量驾驶记录数据时,如何实现自动且高效地识别、分类和提取驾驶记录的挑战性场景?本文康谋为您介绍IVEX软件识别挑战性场景并进行数据分析的强大功能
    的头像 发表于 08-28 10:16 892次阅读
    康谋分享 | 在基于场景的AD/ADAS验证<b class='flag-5'>过程中</b>,识别挑战性场景!

    新的积木编程工具:Node-App

    基于Bootstarp样式库构建,设置方式简单致,初学者也能轻松掌握。由Blockly生成的JavaScript代码实现界面交互和功能逻辑,所有操作在同一个工作区内完成。 Node
    发表于 05-28 15:55

    FPGA开发过程中配置全局时钟需要注意哪些问题

    在FPGA开发过程中,配置全局时钟是至关重要的步骤,它直接影响到整个系统的时序和性能。以下是配置全局时钟时需要注意的些关键问题: 时钟抖动和延迟 :全局时钟资源的设计目标是实现最
    发表于 04-28 09:43

    鸿蒙ArkTS开始实例:【canvas实现签名板功能

    使用ArkTS的canvas实现签名板的功能,canvas画布大家都很熟悉,我们会用它经常实现些画板或者图表、表格之类的功能。canvas签名板是我在
    的头像 发表于 04-08 10:10 756次阅读
    鸿蒙ArkTS开始实例:【canvas实现签名板<b class='flag-5'>功能</b>】

    做项目的时候,是不是每一个功能做一个中断,怎么选择中断去实现功能

    我想问下,做项目的时候,是不是每一个功能做一个中断,怎么选择中断去实现功能,而又可以互相嵌套中断,有什么资料可以参考的
    发表于 04-08 06:00

    鸿蒙APP开发实战:【Api9】拍照、拍视频;选择图片、视频、文件工具类

    鸿蒙开发过程中,经常会进行系统调用,拍照、拍视频、选择图库图片、选择图库视频、选择文件。今天就给大家分享工具类。
    的头像 发表于 03-26 16:27 635次阅读
    鸿蒙<b class='flag-5'>APP</b><b class='flag-5'>开发</b>实战:【Api9】拍照、拍视频;选择图片、视频、文件工具类

    在BF707开发过程中向Flash烧写代码,然后断电进行加载,发现并未加载成功如何解决?

    在BF707开发过程中向Flash烧写过代码,然后断电进行加载,发现并未加载成功,当进行如下操作却失败】 1.利用CCES仿真器,在debug情况下对JTAG进行Test结果为
    发表于 01-12 06:03

    氮化镓芯片研发过程

    氮化镓芯片(GaN芯片)是种新型的半导体材料,在目前的电子设备逐渐得到应用。它以其优异的性能和特点备受研究人员的关注和追捧。在现代科技的进步,氮化镓芯片的研发过程至关重要。下面将
    的头像 发表于 01-10 10:11 853次阅读

    ASIC芯片开发过程

    电子发烧友网站提供《ASIC芯片开发过程.ppt》资料免费下载
    发表于 12-25 10:04 1次下载

    PCBA加工过程中一定要注意的事项

    ,是电子设备制造过程中重要环节。在PCBA加工生产过程中,有些注意事项需要特别关注,以确保产品质量和生产效率。本文将从PCBA加工生
    的头像 发表于 12-20 09:43 384次阅读

    如何用万界星空科技低代码平台快速开发MES系统?

    如今,越来越多的企业选择低代码开发平台,让企业实现数字化转型。但是你好奇“低代码开发平台”能做什么吗?“低
    的头像 发表于 12-08 11:39 476次阅读
    如何用万界星空科技低<b class='flag-5'>代码</b>平台快速<b class='flag-5'>开发</b><b class='flag-5'>一</b><b class='flag-5'>个</b>MES系统?

    为什么开发过程中有些不带光耦隔离的继电器需要引脚开漏输出控制?

    为什么开发过程中有些不带光耦隔离的继电器需要引脚开漏输出控制
    发表于 11-03 06:41

    日志设计开发过程中的常见问题

    日志是系统熵增最快的模块,它承载了业务野蛮生长过程中的所有副产品。本文介绍了日志治理案
    的头像 发表于 10-19 17:01 477次阅读
    日志设计<b class='flag-5'>开发过程中</b>的常见问题

    Android校园应用开发过程

    电子发烧友网站提供《Android校园应用开发过程.pdf》资料免费下载
    发表于 10-19 11:36 0次下载
    Android校园应用<b class='flag-5'>开发过程</b>

    自动驾驶系统(ADS)的开发过程

    开发接口 除了功能之外,还必须关注自动驾驶系统(ADS)的开发过程。下面简要概述调试功能,这些功能用于电子控制单元(ECU)的初始
    的头像 发表于 10-04 11:08 1177次阅读