沁恒微蓝牙空中升级说明(二、应用细节说明)
沁恒微蓝牙空中升级补充说明 ...... 矜辰所致
前言
之前我们写过一篇文章,讲解了一下沁恒微蓝牙空中升级流程,当时我们是基于官方给定的示例进行的测试说明,在实际应用中,我们有可能是先基于普通的不带 OTA 的示例进行的我们 APP 开发,然后再添加 OTA 功能,博主自己测试时候也发现有一些需要注意的地方。
所以本文我们对沁恒微的空中升级流程做一些补充说明 以及 基于普通例程开发的 APP 如何实现带 OTA 功能。
相关博文:沁恒微蓝牙无线升级(OTA)测试说明沁恒微 RISC-V 芯片开发工具 MounRiver Studio 使用
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!
本文目录一、 OTA 示例的补充说明├─ 1.1 OTA 各部分代码存放位置├─ 1.2 带库升级示例说明└─ 1.3 不带库升级示例说明二、 普通示例修改带 OTA 功能├─ 2.1 想要实现的 APP 效果├─ 2.2 JumpIAP 修改├─ 2.3 IAP 修改├─ 2.4 APP 修改│ ├─ 2.4.1 工程配置│ ├─ 2.4.2 Link.ld 修改│ ├─ 2.4.3 Startup_CH585.S 修改│ ├─ 2.4.4 增加 APP判断文件有效性标志│ └─ 2.4.5 添加Jump_OTA代码└─ 2.5 效果测试结语
一、 OTA 示例的补充说明
基础的流程说明请先过一遍 《 沁恒微蓝牙无线升级(OTA)测试说明 》 。
1.1 OTA 各部分代码存放位置
这里我在原始图区域图加上了示例代码位置,Flash 地址信息标注,大家直接看图。
带库升级 OTA 示例 Flash 存放示意图:

IAP 我们放在 Flash 的最后区域,而不是按照跳转流程顺序排放的。
不带库升级 OTA 示例 FLash 存放示意图:

这里就需要注意一下,对于 CH585 来说,示例里面 app 只给了 44K 空间,然后给最新的库预留了 192 K 的空间,库的存放地址为 0x0004 0000 。 所以还有 192K 的剩余空间。
为什么示例这里这么设计,是因为之前有一些芯片的 Codeflash 大小为 192K, 之前的蓝牙库和文档上面标注的一样为 128K, 这种布局正好放满 192 K 的 Codeflash ,如下图:

上面的 OTA 示例存放的位置在代码里面的 Link.ld 文件和跳转部分代码都是会有体现的,这个在本文后面会有说明。
通过上面我们知道了对于 Flash 大的芯片,我们是有很多剩余空间的,官方示例只有 44K 给 APP,应用中我们可以加大我们 APP 占用的空间以满足实际需求。这也正是我们下文要说明的重点之一。
1.2 带库升级示例说明
我们再来说明一下官方示例中两种升级方式中各部分代码做的工作,可以让大家能更好的明白为什么两种 OTA 方式在流程上的不一样,也便于大家在以后应用中选择适合自己的方式。
JumpIAP:
工程: BackupUpgrade_JumpIAP
Flash 存放位置:从地址 0 开始,占 4K 空间,在链接文件 Link.ld 中设定如下
MEMORY{//代码存放位置,从 0开始,占 4K 空间 FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 4K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K}
代码功能:跳转到 IAP ,里面什么都没做,只是在 startup_CH585.S 里面进行了跳转:
.section .init,"ax",@progbits .global _start .align 1_start: j 0x6D000
如需修改跳转地址,直接修改启动文件 startup_CH58x.s 中跳转指令 j 之后的地址。
IAP:
工程: BackupUpgrade_IAP
Flash 存放位置:从地址 0x6D000 开始,占 12K 空间,在链接文件 Link.ld 中设定如下
MEMORY{//代码存放位置,从0x6D000开始,占 12K 空间 FLASH (rx) : ORIGIN = 0x0006D000, LENGTH = 12K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K}
代码功能:功能也比较简单直接,检查 IAP 标志位,看看是否要把 B 区代码搬运到 A 区,然后跳转到 A 区执行 APP。
if(CurrImageFlag == IMAGE_IAP_FLAG){ 擦除 A 区 把 B 区内容 复制 到 A 区 把标志改成 IMAGE_A_FLAG 擦除 B 区}jumpApp(); // 跳去 A 区运行
APP(应用程序):
工程: BackupUpgrade_OTA
Flash 存放位置:从地址 0x1000 开始,占 216K 空间,在链接文件 Link.ld 中设定如下
MEMORY{//代码存放位置,从0x1000开始,占 216K 空间 FLASH (rx) : ORIGIN = 0x00001000, LENGTH = 216K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K}
代码功能:最终运行的应用程序,基于 Ble 从机示例修改的代码,是个从机设备,广播名为 "Simple Peripheral" 。代码中增加了与主机交互的 OTA 流程,所以通过沁恒微的 手机 APP <OTA升级工具> 可以直接扫描连接进行 OTA 流程。
1.3 不带库升级示例说明
JumpIAP:
工程: OnlyUpdateApp_JumpIAP
Flash 存放位置:从地址 0 开始,占 4K 空间,在链接文件 Link.ld 中设定如下
MEMORY{//代码存放位置,从 0开始,占 4K 空间 FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 4K//不带库的 RAM 区域有调整,留了一部分给协议栈使用 RAM (xrw) : ORIGIN = 0x20005000, LENGTH = 108K}
注意不带库 RAM 是从 0x20005000 开始,前面一部分留给了协议栈使用。
代码功能:和带库一样,只是调整,但是跳转的地址不一样,因为两种示例 IAP 的位置不一样:
.section .init,"ax",@progbits .global _start .align 1_start: j 0xC000
如需修改跳转地址,直接修改启动文件 startup_CH58x.s 中跳转指令 j 之后的地址。
IAP:
工程: OnlyUpdateApp_IAP
Flash 存放位置:从地址 0xC000 开始,占 16K 空间,在链接文件 Link.ld 中设定如下
MEMORY{//代码存放位置,从0xC000开始,占 16K 空间 FLASH (rx) : ORIGIN = 0x0000C000, LENGTH = 16K RAM (xrw) : ORIGIN = 0x20005000, LENGTH = 108K}
代码功能:检查是否是软复位 (软复位不跳 APP)和 检查 标志位判断是否需要跳转到 APP 代码执行,如果不跳转到 APP,需要进行 OTA, IAP 程序就会初始化从机功能,是一个 BLE 从机设备。基于 Ble 从机示例修改的代码,广播名为 "OTAOTA_OTAOTA_OTA" 。代码中增加了与主机交互的 OTA 流程,通过沁恒微的 手机 APP <OTA升级工具> 可以直接扫描连接进行 OTA 流程。
还有一点需要注意,因为库是分开的,需要用到蓝牙库,所以在 startup_CH585.S 文件里面最后部分有关联库地址的的汇编代码。
//startup_CH585.S 最后几行,库 hex 的存放位置为0x00040000 la t0, main csrw mepc, t0 la a0, 0x00040000 jr a0 mret
对于不同的芯片固定库,这个跳转位置是不一样的,具体以对应芯片 EVT 包中的位置为准。
APP(应用程序):
工程: OnlyUpdateApp_Peripheral
Flash 存放位置:从地址 0x1000 开始,占 216K 空间,在链接文件 Link.ld 中设定如下
MEMORY{//代码存放位置,从0x1000开始,占 44K 空间 FLASH (rx) : ORIGIN = 0x00001000, LENGTH = 44K RAM (xrw) : ORIGIN = 0x20005000, LENGTH = 108K}
代码功能:最终运行的应用程序,基本就是 Peripheral 示例一样的APP,广播名为 "Simple Peripheral" ,增加了跳转 OTA 升级流程的处理 Jump_OTA,但是本身不处理 OTA 流程 。
case SIMPLEPROFILE_CHAR3: {uint8_t newValue[SIMPLEPROFILE_CHAR3_LEN]; tmos_memcpy(newValue, pValue, len); PRINT("profile ChangeCB CHAR3..\n"); PRINT("jump OTA \n"); mDelaymS(5); Jump_OTA();break; }
在我们修改其他应用的时候,我们可以按照这个示例,把这部分代码复制过去,然后使用本示例的 IAP 流程进行空中升级 ,这下面会在示例中体现说明。
库文件:
库文件位置如下,是已经做好的 hex 文件,烧录的时候直接选中对应芯片的库即可 :

使用固定库,需要定义一个宏 CH58xBLE_ROM = 1,工程代码如下:

二、 普通示例修改带 OTA 功能
经过上面的基础分析,我们基于不带库升级示例来实现一个应用。
2.1 想要实现的 APP 效果
把普通的主机示例 Central 修改成带 OTA 功能的,并且把 APP 程序空间加大 。
实现一个如下效果的 APP:

2.2 JumpIAP 修改
在 OnlyUpdateApp_JumpIAP 工程 上修改
通过计算可得,我们 IAP 的地址应该从 0x0003 C000 开始,所以我需要在 JumpIAP 的 startup_CH585.S 中跳转到这个地址:
.section .init,"ax",@progbits .global _start .align 1_start: j 0x3C000
2.3 IAP 修改
在 OnlyUpdateApp_IAP 工程上修改
因为 IAP 程序位置变了,我们需要在 IAP 的 Link.ld 文件中修改一下自己的起始地址:
MEMORY{ FLASH (rx) : ORIGIN = 0x0003C000, LENGTH = 16K RAM (xrw) : ORIGIN = 0x20005000, LENGTH = 108K}
还有关键点,因为我们的 APP 例程变大了,我们的 IAP 负责 OTA 流程,接收新的 APP 程序,我们要修改一下定义 APP 区域大小的代码:

这个是根据我们自己想要给定 APP 区域大小来设置的,如果我们想 APP 是100K ,这里对应的 IMAGE_A_SIZE 就对应改成 100K 。
2.4 APP 修改
在 Central 工程上修改
2.4.1 工程配置
最关键的就是我们 APP 的修改,首先我们改成用固定库,在工程中添加宏 CH58xBLE_ROM = 1 :

可以看到改成使用库以后 Flash 和 RAM 占用都小了很多。
还有一个要注意的点,以前说过很多次的,因为原始的示例工程有很多共用文件,我们最好是把工程独立出来。
说明如下(我复制了 Central 一份,改了个名字,文件夹有小标志的就是使用的链接文件,是共用的):

如何不使用共用文件,请参考博文 《 沁恒微 RISC-V 芯片开发工具 MounRiver Studio 使用 》中第四章节:

对于本示例呢,我们只需要把 LD 和 Startup 改成自己的就行了,如果你不明白,全部独立出来也没问题,这样不会影响其他使用这两个文件的工程。
这里建议直接把示例 OnlyUpdateApp_Peripheral 中的 LD 和 Startup 文件夹直接拷贝过去,拷贝过去不要忘记工程还得配置一下不用链接文件了:
最后改完以后是这样的:

2.4.2 Link.ld 修改
这里我们修改,是基于上面使用 OnlyUpdateApp_Peripheral 里面现成的 ld 和 startup 文件进行的。
只需要修改一下 MEMORY :
MEMORY{ FLASH (rx) : ORIGIN = 0x00001000, LENGTH = 236K RAM (xrw) : ORIGIN = 0x20005000, LENGTH = 108K/* RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K *//*CH584*/}
2.4.3 Startup_CH585.S 修改
我们使用 OnlyUpdateApp_Peripheral 里面复制过来的 Startup_CH585.S ,就不需要修改。
2.4.4 增加 APP判断文件有效性标志
在central_main.c文件中添加如下代码, 直接从 OnlyUpdateApp_Peripheral 里的 peripheral_main.c拷贝过来即可:
/* 用于APP判断文件有效性 */constuint32_t Address = 0xFFFFFFFF;__attribute__((aligned(4))) uint32_t Image_Flag __attribute__((section(".ImageFlag"))) = (uint32_t)&Address;

2.4.5 添加Jump_OTA代码
Jump_OTA 代码的实现可以直接按照示例中的来:
```c/* OTA 升级标志 */#define IMAGE_OTA_FLAG 0x03/* 存放在DataFlash地址,不能占用蓝牙的位置 */#define OTA_DATAFLASH_ADD 0x00077000 - FLASH_ROM_MAX_SIZE/* flash的数据临时存储 */__attribute__((aligned(8))) uint8_t block_buf[16];/********************************************************************* * @fn Jump_OTA * * @brief 跳转OTA升级 * * @return none */voidJump_OTA(void){uint16_t i;uint32_t ver_flag;/* 读取第一块 */ EEPROM_READ(OTA_DATAFLASH_ADD, (uint32_t *)&block_buf[0], 4);/* 擦除第一块 */ EEPROM_ERASE(OTA_DATAFLASH_ADD, EEPROM_PAGE_SIZE);/* 更新Image信息 */ block_buf[0] = IMAGE_OTA_FLAG;/* 编程DataFlash */ EEPROM_WRITE(OTA_DATAFLASH_ADD, (uint32_t *)&block_buf[0], 4);/* 软复位 */ SYS_ResetExecute();}
我们把上面一段直接放在central.c 代码最后面。
然后我们还要实现的就是触发 Jump_OTA 的操作,就是在哪里调用 Jump_OTA ,这里就是根据大家自己实际应用需求来,比如是一个按键按下,收到某个消息等。
博主这里测试,就简单点,只要扫描到某个固定的广播名称的从机设备 "888888 Peripheral" ,就进入 OTA 流程,此操作可以参考博主之前的博文 《CH58x 主机扫描事件相关应用(扫描到广播包)》,这里直接放一下对于代码,如下:
staticconstchar TARGET_NAME[] = "888888 Peripheral";voidJump_OTA(void);staticuint8_tscan_for_name(constuint8_t *pData, uint16_t dataLen){uint16_t pos = 0;while (pos < dataLen - 1) {uint8_t fieldLen = pData[pos]; // Lengthuint8_t fieldType = pData[pos + 1]; // AD Typeif (fieldLen == 0) break; // 安全终止/* 只处理 Short/Complete Local Name */if (fieldType == 0x08 || fieldType == 0x09) {uint8_t nameLen = fieldLen - 1; // 去掉 type 字节char tmp[32]; // 最长 31 足够if (nameLen >= sizeof(tmp)) nameLen = sizeof(tmp) - 1;memcpy(tmp, &pData[pos + 2], nameLen); // 拷出名字 tmp[nameLen] = '\0'; // 手工 '\0' 结尾if (strstr(tmp, TARGET_NAME)) // 子串匹配return TRUE; } pos += fieldLen + 1; // 跳到下一个 AD 结构 }return FALSE;}staticvoidcentralEventCB(gapRoleEvent_t *pEvent){switch(pEvent->gap.opcode) {case GAP_DEVICE_INIT_DONE_EVENT: { PRINT("Discovering...\n"); GAPRole_CentralStartDiscovery(DEFAULT_DISCOVERY_MODE, DEFAULT_DISCOVERY_ACTIVE_SCAN, DEFAULT_DISCOVERY_WHITE_LIST); }break;case GAP_DEVICE_INFO_EVENT: {// const uint8_t *p = pEvent->deviceInfo.pEvtData;if (scan_for_name(pEvent->deviceInfo.pEvtData,pEvent->deviceInfo.dataLen)) { PRINT("Name matched: %s\r\n", TARGET_NAME);//找到名字做自己想做的事情,根据名字连接 PRINT("jump OTA \n"); mDelaymS(5); Jump_OTA(); }// Add device to list centralAddDeviceInfo(pEvent->deviceInfo.addr, pEvent->deviceInfo.addrType); }break;//......
OK ! 到这里,一个支持 OTA 的主机代码就完成了。
2.5 效果测试
本示例操作流程:
-
1. 烧录 4个 Hex 文件,测试板开始跑主机代码 -
2. 然后给广播名为 888888 Peripheral的设备上电,主机扫描到就进入 OTA 流程,主机复位 -
3. 主机复位进入 IAP 程序,把自己变成一个广播名为 "OTAOTA_OTAOTA_OTA"的从机设备等待手机连接 -
4. 手机通过 APP < OTA升级工具 >扫描到对应设备进行 OTA 操作(OTA 具体操作步骤参考上一篇博文 沁恒微蓝牙无线升级 (OTA)测试说明))。 -
5. 升级完成可直接观察到新的 APP 运行(如果新的 APP 进入 OTA 升级流程没变记得把进入 OTA 的条件去掉防止运行后又进入 OTA 流程,如果按照本示例就是关掉广播名为 888888 Peripheral的设备。)
下面按照步骤测试,上一些图片说明:

程序正常运行:

做个新的 APP Hex 用作升级使用:

把待升级的 Hex 放手机对应文件夹中:

给广播名为 888888 Peripheral 的设备上电,使得需要测试的主机进入 OTA 流程:

然后打开手机 APP < OTA升级工具 > 按照步骤操作一遍:
打开 APP —> 扫描附件蓝牙设备 —> 选择
"OTAOTA_OTAOTA_OTA"的蓝牙设备 —> GETINFO —> IMAGEA —> 选择 Hex 文件 —> 最下方 START—> 手动选择芯片类型(需要的话)

通过 Debug 可以查看过程:

OTA 升级成功,测试完成!
结语
好了,本文从实际应用出发,讲解了沁恒微蓝牙 OTA 流程更细节的一些东西,也展示了如何把一个普通的工程变成带 OTA 流程的示例。
虽然本文只展示也不带库的升级示例,但是只要明白流程,更简单的带库升级那应当也不在话下。
好了,本文就到这里。谢谢大家!

分享💬 点赞👍 在看❤️ “三连”支持
夜雨聆风