nrf52——DFU升级USB/UART升级方式详解(基于SDK开发例程)
阅读原文时间:2023年07月08日阅读:1

摘要:在前面的nrf52——DFU升级OTA升级方式详解(基于SDK开发例程)一文中我测试了基于蓝牙的OTA,本文将开始基于UART和USB(USB_CDC_)进行升级测试。

整体升级流程:

整个过程希望你和我一样采用有log的bootloader进行。可以看到执行流程,也能在出错时进行检查。

1、生成秘钥(一定要保留好)

2、生成USB或者UART的BootLoader程序(需要算法库和秘钥)

3、生成APP工程

4、合成初始的固件、并下载到相应的硬件

5、生成需要更新的APP固件

6、合成升级包固件,一般为ZIP文件。

7、利用USB连接设备并上传升级固件

8、uart升级(接连第7节)

9、更改进入bootloader的方式,从APP触发进入

10、无直接的bootloader例程,如何升级

特别注意:在这过程中依然会遇到各种各样的细节问题,如果你在升级时有遇到问题,可以着重看一下是否和你遇到的问题类似,每一节遇到的问题,我都会以一个小节的方式放到后面供大家参考。

还是那句话,在操作前我默认您已经安装好了相关测试环境和基础工具,如果没有请参看如下的官方中文链接去安装:Nordic nRF5 SDK开发环境搭建(nRF51/nRF52芯片平台) - iini - 博客园 (cnblogs.com),同时本文也只是对整个升级流程进行一次完整操作,并记录一些遇到的问题及解决方式,如果你想知道具体原理,请参看如下的官方链接:详解蓝牙空中升级(BLE OTA)原理与步骤 - iini - 博客园 (cnblogs.com) 原理是一样的,只是数据传输方式选择不一样了而已。那么接下我们就正式开始,有些步骤可能和蓝牙OTA篇重叠,可以选择性观看。

开始之前先把我们要用到的基础指令全部贴出来:

//私钥生成命令:
nrfutil keys generate priv.pem //(priv.pem就是私钥)
//公钥生成命令:
nrfutil keys display --key pk --format code priv.pem --out_file dfu_public_key.c //(dfu_public_key.c就是公钥)

//settings包生成命令
nrfutil settings generate --family NRF52 --application app.hex --application-version 1 --bootloader-version 1 --bl-settings-version 2 settings.hex

//生成初始固件包命令
mergehex --merge bootloader.hex settings.hex --output bl_temp.hex

mergehex --merge bl_temp.hex app.hex s140_nrf52_7.2.0_softdevice.hex --output whole.hex

//擦除芯片命令
nrfjprog --eraseall -f NRF52

//下载命令
nrfjprog --program whole.hex --verify -f NRF52

//硬件复位命令
nrfjprog --reset -f NRF52

//生成升级包命令
nrfutil pkg generate --application app_new.hex --application-version 2 --hw-version 52 --sd-req 0x0100 --key-file priv.pem dfu.zip

//USB升级触发命令
nrfutil dfu usb-serial -pkg dfu.zip -p COM14 -fc 0 -b 9600

//UART升级触发命令
nrfutil dfu serial -pkg dfu.zip -p COM14 -fc 0 -b 9600

一、秘钥生成

还是熟悉的配方,先生成私钥,在任意一个位置建立一个文件夹,如DFU_USB,打开后直接在路径栏输入cmd回车打开我们命令窗口(其他方式打开也一样,但是请定位到刚刚这给文件夹)。

在命令框依此运行下面两条命令:

  • 私钥生成命令:nrfutil keys generate priv.pem (priv.pem就是私钥)
  • 公钥生成命令:nrfutil keys display --key pk --format code priv.pem --out_file dfu_public_key.c (dfu_public_key.c就是公钥)

运行完毕后生成了如下两个文件文件priv.pem和dfu_public_key.c,具体如图所示:

二、算法库生成(micro-ecc算法库)。

如果你已经使用过蓝牙OTA方式升级,那么你的算法库已经存在了,不需要再去生成了。如果你是第一次测试DFU升级,那么依然需要进行算法库生成:可以参考我上一篇文章的生成流程,链接如下:nrf52——DFU升级OTA升级方式详解(基于SDK开发例程) - 星辰_start - 博客园 (cnblogs.com),如果你环境没有问题,或者没出现过修改,那么直接运行SDK目录:external\micro-ecc下的build_all.bat脚本即可,有问题在去参考解决。

三、bootloade程序生成

把我们第一步我们生成的密钥替换SDK中的密钥,这一步如果你前面已经参考过蓝牙OTA例程,那么就可以用当时生成的密钥,不用在替换,如果没有是第一次进行DFU升级,那么请替换(路径:examples\dfu):

替换完成打开同目录下文件secure_bootloader,你会看到许多的例程bootloader,这些例程基本已经涵盖了nrf52系列全部的芯片和升级方式(BLE/USB/UART),在里面你会看到两种区别,有debug和无debug的,区别只是有debug的是有log打印信息的,在运行时你可以知道程序运行到哪里的,对于研发调试时很有必要的,但是相对的生成的hex文件就大,你可以自行选择。

选择芯片和你想要的升级方式打开后选择编译方式生成bootloader,但是如果没有怎么办,比如你想使用nrf52833的USB,没有怎么办,不要慌,我会在后面的章节进行讲解,先让我们走一遍正常流程。

芯片对于如下:

那我就来选择一个进行升级把,我选择nrf52840,先生成USB的选择如下工程:pca10056_usb_debug,编译生成hex文件。如果你编译时出现uECC,h的报错,说明前面算法库生成,你可能只是简单的双击了build_all.bat脚本,但是执行出错了,并没有完全生成算法库,那请去看我关于蓝牙OTA的例程进行解决。在正确生成hex文件后把这个文件复制到DFU_USB文件夹中并改名为bootloader_usb(为了一会和UART区分开),

四、APP程序生成

这一步没有蓝牙OTA那样麻烦,我们直接任意找一个程序蓝牙程序即可,我选择最经典的NUS程序(透传)路径如下:examples\ble_peripheral\ble_app_uart,直接打开编译即可,获得一个蓝牙APP的HEX文件,放到DFU_USB文件中,改名为app,同时注意你工程的协议栈版本是多少,可以这样查看:

然后我们去如下目录找到对应版本的协议栈HEX文件,复制到我们的DFU_USB中,路径:components\softdevice。然后在更改一下APP中的蓝牙名称,默认为Nordic_UART,编译在获得一个APP程序,复制到DFU_USB中改名为app1,便于接下来制作升级包时使用。

五、生成BootLoader settings page

采用官方博客中说的版本2方式生成settings.hex文件。命令如下:(如果文件中没有按照脚本中的方式命名,请根据自己生成的文件名进行更改)

nrfutil settings generate --family NRF52840 --application app.hex --application-version 1 --bootloader-version 1 --bl-settings-version 2 settings.hex

由于我采用的硬件芯片为52840,所以命令中标红字段为nrf52840,如果是其余系列请参考下表:

setting.hex生成命令

对应芯片型号

NRF51

nrf51系列芯片

NRF52810

nrf52810

NRF52QFAB

nrf52820

NRF52

nrf52832、nrf52833

NRF52840

nrf52840

在执行了命令后会有结果显示如下,可以看一下start Address是否和芯片定义的地址一致,

由以上两图对比,可以看到地址一致,没有问题。

六、原始固件烧写

然后开始合成固件,脚本命令(主意文件名要和你实际的一样,不然会出错):

mergehex --merge bootloader_usb.hex settings.hex --output bl_temp.hex
mergehex --merge bl_temp.hex app.hex s140_nrf52_7.2.0_softdevice.hex --output whole.hex

烧写hex文件命令(以nrfjprog为例),也可以用nRF connect(如果没有nRF connect请参看中文博客开发环境搭建篇进行环境搭建):

nrfjprog --eraseall -f NRF52
nrfjprog --program whole.hex --verify -f NRF52
nrfjprog --reset -f NRF52

用手机APP搜索可以看到我们蓝牙:

七、固件升级

 确定协议栈版本是必不可少的一步,如果不知如何确定,请看蓝牙OTA升级篇。

升级包生成命令如下,请修改版本号为你使用的协议栈版本号:

nrfutil pkg generate --application app1.hex --application-version 2 --hw-version 52 --sd-req 0x0100 --key-file priv.pem dfu_usb.zip

插入USB接口,确定USB端口接入,如果你是使用的DK板,那么按住按键4,紧接着复位一下,bootloader会检测到P0.25口电平,进入DFU升级模式,如果不是DK板,那么我们需要进行程序的修改,修改方式后续进行讲解。进入DFU后,你可以通过串口助手或者电脑的设备管理中查看,看到你的USB接口,记住端口号。(我的为31)

这个时候我们运行下列指令:

nrfutil dfu usb-serial -pkg dfu_usb.zip -p COM31

-b为波特率(这是一个虚拟串口)。

可以看到升级成功

使用手机APP看一下,蓝牙名称是否改变:

自此,USB升级完成。

在运行上一步时,你可能会遇到这样的问题,等了半天,没有升级,反而给我们报了许多错,这个在老的芯片如nrf52833,52832等UART或者USB(nrf52833、52820)时可能会经常遇到。

这个时候我们可以禁止流控,减小波特率。

运行下列指令:

nrfutil dfu usb-serial -pkg dfu_usb.zip -p COM31 -fc 0 -b 9600

其中-fc 0为禁止流控,-b为波特率(这是一个虚拟串口)。

然后就可以升级成功了。

8、UART升级

通过前面的讲解,我们来快速实现一下UART升级,选择UART的bootloader,编译后改名为bootloader_uart。

然后命令如下:

生成setting:

nrfutil settings generate --family NRF52840 --application app.hex --application-version 1 --bootloader-version 1 --bl-settings-version 2 settings.hex

合成固件:

mergehex --merge bootloader_uart.hex settings.hex --output bl_temp.hex
mergehex --merge bl_temp.hex app.hex s140_nrf52_7.2.0_softdevice.hex --output whole.hex

烧写:

nrfjprog --eraseall -f NRF52
nrfjprog --program whole.hex --verify -f NRF52
nrfjprog --reset -f NRF52

合成升级包:

nrfutil pkg generate --application app1.hex --application-version 2 --hw-version 52 --sd-req 0x0100 --key-file priv.pem dfu_uart.zip

查看端口号:

升级:

nrfutil dfu serial -pkg dfu_uart.zip -p COM3

等待升级成功:

一样的问题,如果遇到无法升级的问题,请加上流控禁止和波特率。

命令:

nrfutil dfu serial -fc 0 -pkg dfu_uart.zip -p COM3 -b 115200

9、更改进入DFU的方式(不使用按键)

在实际的项目中肯定不能说还要去按住一个按键然后复位才可以进入DFU进行USB或者UART升级。那我们如何改呢?方法很简单,只要修改DFU标志,然后触发软件复位即可。由于是测试,我这就利用软件定时器定时一定时间,然后修改DFU标志,修改完成后触发软件复位即可。实际中你可以是UART或者usb给到一条指令,通过解析指令去完成上诉操作。

下面列出DFU的触发方式,你可以进行搭配使用,用户设定的也只有1和2两种。

  1)、按键是否按下

  2)、保持寄存器GPREGRET1是否为0xB1

  3)、上次DFU过程是否还在进行中

  4)、应用程序校验是否通过

按键触发定义在bootloader中,如果需要修改其余方式,我们要在bootloader中去修改。修改sdk_config中的宏定义,默认的定义如下图,使用了按键触发方式和寄存器方式:

下面来讲一下如何从APP中进入DFU,我们按照前面说的,添加一个软件定时器,在app广播开始20s后修改寄存器的值为0xB1,然后触发软件复位,从协议栈重新进入到bootloader去判断寄存器值,由于我们已经在软复位前更改寄存器值为0xB1,所以会进入DFU模式等待UART或者USB的升级指令,不在跳转到APP。

继续打开如下目录例程下nrf52840的透传例程:examples\ble_peripheral\ble_app_uart。然后加入如下代码:

//全局定义一个定时器别名
APP_TIMER_DEF(my_timer_id_DFU);//一个定时器

//main中初始化这个定时器
uint32_t err_code;
err_code = app_timer_create(&my_timer_id_DFU,APP_TIMER_MODE_REPEATED, my_timeout_handler);
APP_ERROR_CHECK(err_code);
err_code = app_timer_start(my_timer_id_DFU, APP_TIMER_TICKS(20000), NULL);
APP_ERROR_CHECK(err_code);

//编写定时器回调,子啊回调中停止定时器,并修修改寄存器值
#define BOOTLOADER_DFU_GPREGRET (0xB0)
#define BOOTLOADER_DFU_START_BIT_MASK (0x01)
#define BOOTLOADER_DFU_START (BOOTLOADER_DFU_GPREGRET | BOOTLOADER_DFU_START_BIT_MASK)

static void my_timeout_handler (void * p_context)
{
uint32_t err_code;
//停止定时器
app_timer_stop(my_timer_id_DFU);

NRF\_LOG\_DEBUG("In     ble\_dfu\_buttonless\_bootloader\_start\_finalize\\r\\n");  

  //一定先要清除GPREGRET寄存器
err_code = sd_power_gpregret_clr(0, 0xffffffff);
  APP_ERROR_CHECK(err_code);
  //再来修改GPREGRET寄存器的值为0xB1
  err_code = sd_power_gpregret_set(0, BOOTLOADER_DFU_START);
  APP_ERROR_CHECK(err_code); // Signal that DFU mode is to be enter to the power management module   
  nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_DFU);
}

加入如上的代码后,我们就可以编译生成app了,并起名为app_soft作为原始app,然后在修改广播名为app_soft_new作为升级app。然后我们开始利用原来的bootloader

按照升级流程在来一遍。

1)、生成原始固件并下载

//settings包生成命令
nrfutil settings generate --family NRF52840 --application app_soft.hex --application-version 1 --bootloader-version 1 --bl-settings-version 2 settings.hex

//生成初始固件包命令
mergehex --merge bootloader_usb.hex settings.hex --output bl_temp.hex 
mergehex --merge bl_temp.hex app_soft.hex s140_nrf52_7.2.0_softdevice.hex --output whole.hex

//擦除芯片命令
nrfjprog --eraseall -f NRF52

//下载命令
nrfjprog --program whole.hex --verify -f NRF52

//硬件复位命令
nrfjprog --reset -f NRF52

下载完成,依然可以看到我们的广播名为Nordic_UART,等待20s后,我们的定时时间到后会进入DFU模式,(如果你使用的是DK板,你会发现你的板子LED1和LED2亮起,接入USB,后LED1和LED2亮起,注意进入DFU后在bootloader中才会初始化USB)。

2)、升级

制作升级包命令:

//生成升级包命令
nrfutil pkg generate --application app_soft_new.hex --application-version 2 --hw-version 52 --sd-req 0x0100 --key-file priv.pem dfu_soft.zip

//USB升级触发命令
nrfutil dfu usb-serial -pkg dfu_soft.zip -p COM54

然后可以看到我们升级成功,注意看你的USB端口号是什么,这里注意一点,USB端口号要在进入bootloader后初始化完成才能看到。

蓝牙名称如下,我们升级成功。

经过上面的实验,可以确定在实际过程中,你可以通过串口发送一条指令或者中断等方式去修改寄存器值然后软复位进入DFU。经过第8章节UART升级的讲解,和USB的区别只是替换为USB的bootloader然后,最后命令换一下,在这就不在列举了。

10、无直接的bootloader例程,如何升级

 在SDK中是没有52833的USB或者UART升级的bootloader的,因此如果你需要这些在SDK中没有的bootloader时怎么办。

在开始前,来看一下我们前面制作的固件是怎么放置的,下面是我使用工具programmer读取到的关于最后的whole文件的地址存放信息,如果你页想看到下图的存储地址,你可以把我们生成的文件一个一个的放到programmer中查看,可能不同的芯片由于flash大小不一样,会导致起始地址不一样,但是每一个文件在最终合成固件中放置的相对位置是不会变的。我们接下来的对每一个固件组成部分的地址也是按照这个的格式来制作的,需要严格遵守,否则会出现如合并错误(后面会给出该错误的截图,或者你走运地址没有冲突合并成功,但是会发现,程序无法完整运行起来(PC指针跳转到的地址没有程序,导致死机等情况))。

下面是官方给出的flash存储和我们的对照图,其中backup page就是setting的MBR(我把它叫做这个名字,是为了和后面程序代码对应,你可以理解为都是一个东西),setting page和backup page内容是一样的,都是一页,就是4K大小,为什么要这样设置,因为你传输数据肯定要校验是否正确啊,你不校验怎么感让你升级的APP跑,不怕变砖算你头铁,想看详细解释去看官方的解释,我就不在废话了,链接如下:详解蓝牙空中升级(BLE OTA)原理与步骤 - iini - 博客园 (cnblogs.com)

10.1.1、setting地址修改(重要)

选择一个相近系列的芯片的uart或者usb的bootloader进行修改、没有近似系列的也没关系,直接打开一个相应的USB或者uart例程,我们直接进行修改。如针对52833,没有usb的bootloader,我们可以选择52820的bootloader来进行修改,因为52820是52833的子集。选择如下目录的例程打开:examples\dfu\secure_bootloader\pca10100e_usb_debug,在打开的例程中找到如下文件nrf_dfu_types.h

在上面的截图中有两个地址,上面那个是标明了52820这款芯片对应程序校验参数存放部分,即setting(setting中的setting page)的存放起始地址和大小,下面的MBR也是setting,不过这个setting我们把它叫做“setting的MBR”(即backup page),他们的大小都是4K,也就是flash的一页,放在flash的最后两页,到这我也终于把这部分解释清楚了。

虽然我们解释清楚了setting的组成部分,但是在升级过程中我们怎么告诉bootloader,说你在接收完升级固件,去哪里找校验值去验证密钥对不对等等(反正就是官方博客中的那些要校验的东西),看过来,在main->nrf_bootloader_init->nrf_dfu_settings_init->nrf_dfu_settings_reinit->settings_crc_ok,在settings_crc_ok();函数中设定了settings的大小和地址。

跳转到m_dfu_settings_buffer定义的地方,我们可以看到如下代码(作用是在flash中从NRF_MBR_PARAMS_PAGE_ADDRESS地址开始保留一个页面(4k)):

uint8\_t m\_mbr\_params\_page\[NRF\_MBR\_PARAMS\_PAGE\_SIZE\]  
    \_\_attribute\_\_((at(NRF\_MBR\_PARAMS\_PAGE\_ADDRESS)))  
    \_\_attribute\_\_((used));  

/*其意义了一个buffer,并且设定了其大小,然后指定其存储从flash的NRF_MBR_PARAMS_PAGE_ADDRESS地址开始,也就是0x0003E000处。

其中__attribute__( at(绝对地址) ) :表示绝对定位,有两个作用绝对定位到flash,或者绝对定位到RAM,这由于0x0003E000为flash的地址,所以绝对定位到了flash。

__attribute__((used)):告诉编译器在目标文件中保留一个静态函数,即使它没有被引用。*/

下面是我在 52820的手册上截取的flash地址截图,看是不最后一页的地址和我们的setting(即前面的setting程序部分)地址一样,所以你如果要修改bootloader请根据你选择的芯片来修改好setting的存放地址。

由于这次我使用的是52833,那么修改有有两步:

1)修改启动文件和setting page的地址

把原本52820的地址修改为52833flash地址的最后一页开始地址,给前面留出更多的空间存储给app使用,修改如下图把原本52820的启动文件改为52833,这样会把整个工程中的都切换过来:

第一步:

选择第一个选项:

打开如下窗口操作如图:

然后打开下列

然后打开:

在此勾选选择启动文件,保存:

2)修改backup page(setting的MBR)的起始地址

把全局宏定义中的NRF52820_XXAA 修改为NRF52833_XXAA

完成上面两步,可以看都如下结果,两个地址都修改完成了,一个起始地址为0x0007F000,一个为0x0007E000,说明修改完成,你要问我为什么是这两个地址,那是因为52833flash空间最大就是80000,最后两页地址就是这么来的。

10.1.2、bootloader存放地址修改(重要)

前面我们告诉bootloader校验值在哪里去寻找了,那我们接下来解决一下bootloader自己的问题,(不能只考虑别人的问题,自己的也要解决一下对吧),下面是我截取的我使用的bootloader的大小有0xF593这么大,但是我们在他和setting之间还是要留一点空间的,那我们看看起始地址到setting有多大,计算一下为0x12000,那么相对于52833的setting地址计算一下,就是0x7E000-0x12000=0x6C000,由此就决定吧bootloder的相对地址放在0x6c000吧,这样就给app流出了足够空间了,如果后期你开发的app很大,还可以往后面在调整一下bootloader的大小,但是注意不要让bootloader内容去覆盖到setting,如果这些地址大小没有注意个很容易出现合并失败的错误。

什么是合并出错,那下面我们就来犯一次错,我在修改了setting后,直接编译,不去修改我们bootloder的地址,然后合并一下:

然后会发现居然报错了,说hex文件在0地址处冲突,不能合并,不能合并我们就做不了升级了。如果你前面不注意app大小和bootloader的起始地址,你还可能出现在其余地址合并冲突的情况,不要说怎么你报错的地址和我的不一样,有这冲突就说明你该去调整地址了,各个文件大小和地址你没有合理安排好,导致整个flash都被你糟蹋了,还不能让人家报个错。这个时候如果要进行下去,可以用programmer去查看一下各给文件的大小和你现在生成文件的大小,然后调整一下,这调整的主要是app和bootloader,协议栈是固定大小的(当然不同版本的协议栈栈大小不一样,你可以换个小的协议栈),然后app紧跟着协议栈后面。如果发现app很大,bootloader已经都挨着setting放置了还是放不下app,那没办法,要不你裁剪一下app,要不你不要DFU了,要不就换一个小的协议栈,要不你就换一个更大flash的芯片。

来我们看一下为什么0地址报错,打开下面截图的地方,你会发现,怎么地址是0,这是由于你在从52820切换到52833时修改了原本例程设置的值,就是前面的0x0002C000,所以这要改一下,不改你就和协议栈冲突了,协议栈就是从0地址开始放的。

这一步有两点需要注意需要更改ROM和RAM的值,ROM处填写的是bootloader的存储初始地址,在程序中会引用到这个值:在main->nrf_bootloader_flash_protect();函数的第一个参数为BOOTLOADER_START_ADDR,其代码如下:

#define CODE_START ((uint32_t)&Load$$LR$$LR_IROM1$$Base)

#define BOOTLOADER_START_ADDR (CODE_START)

1)flash值设定

它设置为我们前面计算的值6c000(为什么是这个值,前面已经一步一步计算过了,没看到明白可以在研究一下),然后size也要改,怎么改,根据我们选择芯片的flash来,0x80000(52833)-0x6C000=0x14000,填写0x14000即可;

2)数据RAM地址修改(有两个RAM,data RAM和code RAM)

这两个地址如下图(52833数据手册上截取),起始地址要为0x20000008,大小用data RAM减去8即可。

最后我们的设置如下图,如果你要修改为其他芯片,那么请对应去更改。

结论:更改启动文件后,修改ROM和data RAM的地址(IRAM1),去除IRAM2,不要使用,如果你勾选了,虽然编译没有问题,但是你合并下载后会发现程序无法启动运行的。

到这我们就搞定了bootloder,编译一下,然后找到52833的app工程编译一下获得。

下面我们来简单制作一下升级的APP,也就是在第9节基础上改一改名字,就叫SW_Nordic_UART,编译后移动APP,改名,完成。原始的APP依然是那个。

原始固件制作:

nrfutil settings generate --family NRF52 --application app33.hex --application-version 1 --bootloader-version 1 --bl-settings-version 2 settings_33.hex

mergehex --merge bootloader_33.hex settings_33.hex --output bl_temp_33.hex

mergehex --merge bl_temp_33.hex app33.hex s140_nrf52_7.2.0_softdevice.hex --output whole_33.hex

烧写:

nrfjprog --eraseall -f NRF52

nrfjprog --program whole_33.hex --verify -f NRF52

nrfjprog --reset -f NRF52

然后芯片运行起来,20s后进入DUF,在20内,我们看到广播名为soft_Nordic_UART。

新固件制作:

nrfutil pkg generate --application app_SN.hex --application-version 2 --hw-version 52 --sd-req 0x0100 --key-file priv.pem dfu_SN.zip

然后在进入DFU后,插上USB,输入升级命令(注意端口正确,你自己的端口):

nrfutil dfu usb-serial -pkg dfu_SN.zip -p COM30

然后复位一下,你会看到你的蓝牙广播名变为了SW_Nordic_UART。

这和前面的流程一样,主要时修改bootloader,一样的方式,不在赘述了。

所有的升级讲解完成。