nordic——NCS下的DFU升级(基于NCS)
阅读原文时间:2023年08月11日阅读:1

一、简介

  在NCS中有多种的DFU选择,强烈推荐使用MCUboot,当然如果你需要选择传统的nrf_DFU也是可以的,但是要用到官方修改的源文件。

关于mcuboot,原理性的东西在官网和官方博客中有讲,可以自行查看,后面只是简单的提一下:MCUmgr — Zephyr Project Documentation (nordicsemi.com)

二、nRF bootloader升级程序制作

这里是为了便于后续工程直接调用:

第一步:在nrf\subsys目录下的CMakeLists.txt文件中加入我们需要加入的库的目录文件

打开CMakeLists.txt文件在最后加入:

add_subdirectory_ifdef(CONFIG_NRF_DFU nrf_dfu)

add_subdirectory_ifdef(CONFIG_NRF_DFU_RPC_NET nrf_dfu)

第二步:在nrf\subsys目录下的Kconfig文件中加入对即将添加到nrf库的库文件nrf_dfu的宏定义配置文件应用:

打开Kconfig:加入rsource "nrf_dfu/Kconfig",如果不加入,当我们在工程项目的.conf文件中加如引用文件的宏时会找不的相关配置宏定义而报错:

第三步:在nrf\subsys目录下建立一个文件夹:名字和我们刚刚写到CMakeLists.txt和Kconfig文件中的名字一致,名为:nrf_dfu

第四步:把我们需要的.c和.h文件导入,那导入的是什么文件呢,也就是SDK中的DFU使用到的相关源文件,但是请注意,由于需要移植到zephyr中,不在是传统的SDK了,有些底层文件可能要做部分修改,当然这一部分已经不需要我们来做了,原厂已经帮我们修改好了这一部分代码,我们可以直接拿来用。

主要修改的有nrf_dfu_ble.c、nrf_dfu_types.c等文件,当然不是全部修改,但是修改起来很麻烦,你需要知道整个原理,还需要了解nordic的nrf bootloader升级流程和相关函数调用。本着已经有轮子,我们就不要去造轮子的原则,我直接拿来使用。那么如何获取官方开发人员已经修改好的文件呢?相关文件工程已经同步到了github上,连接如下:aiminhua/ncs_samples: nRF connect SDK samples (github.com)

Ncs_samples目录下可以下载整个项目,包含了不止DFU的实例,下载后可以进入到2所显示的目录,看到3处显示的目录,这个是NCS2.2版本已经添加好的nrf bootloader方式升级的文件,你可以选择直接把这个nrf_dfu文件加入,然后替换掉nrf\subsys目录下的CMakeLists.txt和Kconfig文件,就直接使用。

但是我这还是麻烦的一下,不直接使用,只是获取到官方修改过的.c和.h文件然后再NUS的透传例程上进行直接添加得到一个BLE的OTA例子。

(总结:虽然我们不会造轮子,但是有了轮子,我们要知道这么把轮子更好的安在车上,让车跑起来,而不是只知道把轮子粗暴安上把车开起来就行,那么可能出问题的时候就一脸蒙了)

在nrf_dfu文件中建立两个文件夹,一个是inc,放置nrf bootloader所有使用到的.h文件,还有一个就是放置.c文件的src文件夹。并把从github上下载下来的文件中的.c和.h文件都放到对应的文件中,注意把有几个.c文件就放置在了nrf_dfu目录下,如果你想把他们也放到src中,一定要更改CMakeLists.txt中的路径配置,每一个库都要有CMakeLists.txt和Kconfig,便于编译时编译链找到你的文件。

所以整理一下目录结构如下:

第五步:添加我们nrf_dfu库文件下的CMakeLists.txt和Kconfig文件内容。

1)、对于CMakeLists.txt有 如下定义:

可以看到在1处我们使用zephyr_include_directories加入了相关头文件存放的文件夹(就和keil中加入头文件一样),在2处使用add_subdirectory_ifdef加入了会使用到的.c文件存放的文件夹;3处即为要在src文件夹中去找slip文件(或者在src文件夹中的CMakeLists.txt中那slip.c给加入,两种方式任选一种)。

2)、对Kconfig配置定义了我们可以使用到的一些kconfig,主要关注的是在CMakeLists.txt中我们定义的相关红定于是否在kconfig中做了申明:

编写Kconfig的部分规则:

config xxxx /*本次配置的名称*/

xxxx /*表示改配置的类型(bool、int、hex(十六进制)、string、tristate(三太类型))*/

type “xxxxxx” /*简单描述*/

default x /*初始值*/

depends on xxxx /*依赖的选项*/

help “xxxx” /*帮助信息,一般是注释*/

menu 语法以 menu 开始,endmenu 结束。中间包含若干项config配置, 当然也可以包含其他语法。

例如:

menu "test menu"

config TEST\_MENU\_A

    tristate "menu test A"

config TEST\_MENU\_B

    bool "menu test B"

    default n                          

endmenu

第六步:制作.c文件夹里面的Kconfig文件:

把src文件夹中所有的文件都加入到Kconfig中,

这里为什么少了一个起那么第6步移动的slip.c文件的加入,因为起那么说了,两种方式任选一种加入即可,这已经在外层文件的CMakeLists.txt中要应用时,加入了改文件,所以可以不用写slip.c了。

到这就算已经加入了nrf bootloader的相关库了。

2.2.1、配置.conf文件

打开vs code,找到peripheral_uart例子然后打开(如何打开相关操作可以看nordic的入门教程),然后我们编译一下,可以看到肯定是能编译成功的。

在NUS例程中已经有一个原始的.conf文件了,那么只要往里面计入宏定义即可,没有的话,自己添加一个和板子型号一致的名字命名的.conf文件。

这已经有了,我们打开后加入如下语句:

在截图中我们加入了两个宏定义:

CONFIG_NRF_DFU=y

CONFIG_NRF_DFU_BT=y

那他们是哪里来的,为什么是这两个宏定义?回答是他们是我们刚刚制作nrf_duf库时自己亲手设置的。在第2.1的第六步中的添加的CMakeLists.txt文件中,有如下两条语句:

#添加使用的的.c文件所在的在的目录

add_subdirectory_ifdef(CONFIG_NRF_DFU src)

#如果定义 CONFIG_NRF_DFU_BT ,就加入ble的DFU

zephyr_library_sources_ifdef(CONFIG_NRF_DFU_BT nrf_dfu_ble.c)

可以看到分分别使用是两个语句加入了头文件和加入了nordic的ble的DFU的.c文件,他们都是说如果定义了什么什么就加入什么。到这就相信大家可以很好理解了,那么我们可以改嘛,当然可以,但是记住,如这里改了名字请在相应的Kconfig中也改对应,否则编译器还是不能正确找到并编译链接,以上两个宏定义在Kconfig中对应的定义如下:

加入后我们直接进行编译,记住保存后,点击全编译:

编译后没有编译正确,报了一个错:

说zephyr\include\zephyr\dfu\flash_img.h:32目录下的flash_img.h文件的32行居然没有定义,那直接在.config中加入该宏定义,定义为4K。发现居然打了波浪线,怀疑肯定有其依赖项没有加入,我们先编译一下看一下是否通过,编译后依然一样的报错,且查看编译后的autoconf.h文件也确实没有(他包含了工程中所有需要用到宏定义),每次加入,要从新全部构建,具体查看路径是在我们建立的工程目录下如下路径:

因此我们要确定其依赖项,报错说是flash_img.h文件找不到,且编译器也告诉我们路径在哪里了,那我们直接过去看一下该路径下有没有Kconfig文件:

发现居然我没有,却其上一级目录也没有,那可能是在flash_img.c文件处了。而不是在flash_img.h文件处,我直接在整个文件夹中去找flash_img.c(这里一般使用工具查找,很快就可以找到)。

找到flash_img.c文件:

在去上一级的目录果然找到一个对应的Kconfig文件:

打开该文件,然后全局搜索CONFIIG_IMG_BLOCK_BUF_SIZE,注意这里要去掉config进行搜索,即使用IMG_BLOCK_BUF_SIZE进行搜索,果然我们找到了该定义:

可以看到CONFIIG_IMG_BLOCK_BUF_SIZE有一个依赖项MCUBOOT_IMG_MANAGER,所以我们还要加入MCUBOOT_IMG_MANAGER这一个依赖项,即加入CONFIG_MCUBOOT_IMG_MANAGER到.conf中,大家可以看出来了,在定义Kconfig中的定义时时要去掉前面的CONFIG。

其中depends on xxx表示需要依赖于xxx的意思,前面有对kconfig语法进行了基本接收,不知道的请翻看前面。

在加入后依然没有去掉红色波浪线,且编译依然有问题,那可能CONFIG_MCUBOOT_IMG_MANAGER依然有依赖或者这个Kcongig文件有依赖。

查找后可以看到如图的定义:

需要定义IMG_MANAGER才能启用MCUBOOT_IMG_MANAGER,那么我们把CONFIG_MCUBOOT_IMG_MANAGER加入到.cofig中去,然后再次全编译。

没有报错,说明到目前为止我们加入正确,那么下面可以开始main.c中加入相关的DFU代码了。

2.2.2、在main中加入DFU功能函数

添加头文件:

#include "nrf_dfu.h"

#include "nrf_dfu_validation.h"

#include

// 加入重启系统的头文件

#include

加入系统重启的原因是,如果在DFU中出现错误而终止,应该复位系统,等待再次的DFU。

代码加入:

/**@brief Function for handling DFU events.
*/
static void dfu_observer(nrf_dfu_evt_type_t evt_type)
{
switch (evt_type)
{
case NRF_DFU_EVT_DFU_STARTED:
case NRF_DFU_EVT_OBJECT_RECEIVED:
break;
case NRF_DFU_EVT_DFU_COMPLETED:
case NRF_DFU_EVT_DFU_ABORTED:
LOG_INF("resetting…");
// 处理一条挂起的log,打印完成后才进行系统复位
while(log_process());
sys_reboot(SYS_REBOOT_WARM);
break;
case NRF_DFU_EVT_TRANSPORT_DEACTIVATED:
// Reset the internal state of the DFU settings to the last stored state.
LOG_INF("NRF_DFU_EVT_TRANSPORT_DEACTIVATED");
nrf_dfu_settings_reinit();
break;
default:
break;
}
}
int dfu_init(void)
{
int ret_val;
ret_val = nrf_dfu_settings_init(true);
if (ret_val != NRF_SUCCESS)
{
LOG_WRN("dfu settings init err %d", ret_val);
}
ret_val = nrf_dfu_init(dfu_observer);
if (ret_val != NRF_SUCCESS)
{
LOG_WRN("dfu init err %d", ret_val);
}
return ret_val;
}

可以看到,首先会使用nrf_dfu_settings_init()函数初始化校验页。

2.2.3、制作新的固件APP和升级

改名字后编译一个APP,在我们的build_5340\zephyr(build_5340是我给我的编译目录起的名字,你也需要在你自己的建立目录在找到)目录下找到merged.hex名字的hex文件,复制一下,然后杂工程的首目录下建议一个文件件(我建立的名字为updata)

然后把刚刚的merged.hex放到该文件中,使用nrfutil进行升级固件制作。

建立一个.bat的脚本,或者在该目录(updata)中直接运行命令窗口,运行如下

nrfutil pkg generate --application merged.hex --application-version 2 --hw-version 52 --sd-req 0x00 new.zip

pause

由此生成了一个压缩文件:

把这个文件给到手机,然后连接上蓝牙,点击DFU,选择ZIP格式,然后选择文件,我们就可以看到如下界面了:

等待更新完成即可,这里注意的是使用nrf bootloader最高速度只到18KB/s,由于还需要校验等步骤,只会更慢,在我的截图上最高只到了14kB/s,平均只有5.9KB/s。因此一般不建议使用这种升级协议。建议使用MCUboot。

三、MCUboot

简单一点来说,一个bootloader应该包括两个部分,一个是DFU时数据传输协议部分、一个是对image的管理部分。

DFU协议一般都是把升级的image文件分成一块一块的传给bootloader,在传输过程中,DFU会完成校验每一块的数据对不对,出现错误处理等工作。值得注意的是DFU协议不管你物理通道是什么,你可以选择ble、uart、USB等。同时在zephyr中有许多的协议,只对SMP做一下简单介绍。

3.1.1、SMP dfu协议

SMP-simple management protoco(简单管理协议),在nordic的NCS支持包中,mcumgr模块就是使用了SMP协议。

Mcumgr模块使用命令组(我的立即这就是一组函数)的方式去操作SMP协议进行数据的传输。这种方式对于mcumgr来说,只管把他需要SMP做的事高数SMP,SMP会去完成组包,解包等操作,mcumgr等着SMP返回的结果就。

在mcumgr中有两个命令组是和DFU有关的:

1)、img_mgmt:image管理命令组,包括3个三个命令集4个具体的命令(4个函数):

2)、os_mgmt:OS(系统)管理命令组,包括三个命令集4个具体命令(函数):

在后续的例程中我们会使用两条宏定义去启动上面的两个命令组。

接下来是在NCS中推荐使用的DFU方式——MCUboot,其功能强大,兼容的芯片多,不仅仅是nordic,是一个开源的第三方bootloader。MCUboot的存放地址是从0x00000000开始。MCUboot把存储区域分为了两块,分别是主存储区(Primary slot)和第二存储区(Secondary slot),对于nordic的NCS中,应用程序(app)只能在主存储区运行,第二存储区(Secondary slot)可以位于芯片内(flash),也可以位于外部的flash(external flash)。

在MCUboot中是根据其定义的一个变量swap_type,其确定了是等待升级还是跳转APP。swap_type的值由三个参数决定,官网上有这样一张图,通过它们不同的组合,导致swap_type有6种不同的值,如下图所示:

1)、BOOT_SWAP_TYPE_TEST:

在升级后,必须调用boot_write_img_confirmed()来把image_ok这个参数写为1。再次启动时MCUboot才会认为新的image正确,否则回滚。

2)、BOOT_SWAP_TYPE_ PERM:

在升级后不管image_ok的值,默认新的image是正确的,没有回滚。

3)、BOOT_SWAP_TYPE_ REVERT

一旦检测到,就直接进行回滚。

4)、BOOT_SWAP_TYPE_ NONE

不进行DFU,MCU直接跳转到app运行。

5)、BOOT_SWAP_TYPE_ FAIL

MCUboot在进行对primary slot进行校验时没有通过,程序将一直死在MCUboot。

6)、BOOT_SWAP_TYPE_ PANIC

当MCUboot启动时出现致命错误时,出现也将死在MCUboot。我们要程序能运行到APP,那么就要保证swap_type的值为BOOT_SWAP_TYPE_TEST和BOOT_SWAP_TYPE_ PERM,那么我们怎么设置这两种类型呢?需要使用函数boot_request_upgrade(bool choose)。当参数choose = flash时为BOOT_SWAP_TYPE_TEST当参数choose = true时为BOOT_SWAP_TYPE_ PERM

三个参数的取值如下:

magic:全为FF和0x96f3b83d(Good)

image_ok:全为FF(或者Any、Unset)和0x01

copy_done:全为FF(或者Any、Unset)或者0x01

(any表示可能是0x11,等非0x01的值)

如下图就为MCUboot打印的信息,我们可以通过其判断现在是那种模式:

  对应前面的第四种模式,红色框部分为当前MCUboot标志位的值,也不用我们一个一个去对,然后查表确定当前MCUboot的状态。只需要看Swap_type,也就是黄色框部分就知道是处于第几种模式,然后就可以对应当前是属于6中状态中的那种状态了。如果状态不对,就可以根据前面的状态解释来进行修改。

为什么要加一个安全,在MCUboot官网中有这样一个解释,为什么你在出门期间你信任你的家是安全的,因为你家只有一道门,由此有这样一个逻辑:

  1. 你信任一扇门,因为你信任锁
  2. 你信任锁,是因为你信任唯一的钥匙
  3. 你信任钥匙,是因为它正在你的口袋里

由此如果你口袋中的钥匙不见了,那么你将失去对门的信任。

在每次复位或者上电后运行安全bootloader,会通过验证bootloader中的下一个image的签名和元数据来建立一个信任根本,就像确定钥匙是不是唯一且在自己的口袋中,如果任意一次验证不通过,那么bootloader就好停止,由此保证bootloader中的下一个image在被篡改后不会启动。由此杜绝攻击者通过更改固件来入侵接管设备,以保证安全性。

总结:

1)、不可变的:bootloader是flash锁定的,如果不擦除整个设备,无法进行修改或者删除。

2)、安全的:拥有一套验证机制,保证自己和用户app的安全,不会被攻击者篡改。因此要提供自己的秘钥,让bootloader进行签名验证(下一个image是否被篡改)和元数据(Metadata)校验(检测image是否兼容)。

注意本章节添加的,不管是ble的还是uart的,在升级的时候都是后台式的,升级是你可以正常使用。

3.3.1、加入MCUboot

依然选择在peripheral_uart这个NUS例子中加入MCUboot的DFU功能。那具体要用到那些宏定义配置,如何查看:

1)、一是有参考例程:zephyr\samples\subsys\mgmt\mcumgr\smp_svr,该路径的例子中有全部的config宏定义,只要对比加入就行

2)、参考官方网站的说明进行加入:Adding a bootloader chain — nRF Connect SDK 2.3.99 documentation (nordicsemi.com)

3.3.2、工程添加修改(ble-OTA)

这里值得注意的是,不同版本之间这些CONFIG配置是会变的,所以在自己加入时一定要去看上面提到的参考,而不是无脑的复制粘贴。不然很可能你的版本和本文的版本不一样,导致你初始的工程编译都通过不了,稍后我会列举一下NCS2.1和NCS2.3的区别。

本次在NUS(透传)程序的基础上添加MCUboot,例子路径:nrf\samples\bluetooth\peripheral_uart。使用VS code建立一个该工程的映像,并且编译通过后,开始DFU的添加。

第一步:.config中加入全局宏:

注1:该全局宏定义版本是NCS2.1

# #~~MCUboot加入~~

#确保生成与MCUboot兼容的二进制文件

CONFIG_BOOTLOADER_MCUBOOT=y

#启用mcumgr

CONFIG_MCUMGR=y

#~ 启用大多数的核心程序,就是前面说的对SMP的命令组的启用,用于DFU传输~~

用于DFU支持已添加到应用程序中,因此需要重置芯片,以将控制权交给MCUboot,MCUboot将验证新上传的图像并将其与旧映像交换。最后,BOOTLOADER_MCUBOOT将MCUboot作为子映像包含在内

#启用用于imager管理的处理程序(传输/列举/校验确认imager)

CONFIG_MCUMGR_CMD_IMG_MGMT=y

#启用用于操作系统管理的处理程序(用于校验出错时的回滚,以及复位操作)

CONFIG_MCUMGR_CMD_OS_MGMT=y

允许传输大容量的数据包

CONFIG_BT_L2CAP_TX_MTU=252

CONFIG_BT_BUF_ACL_RX_SIZE=256

启用mcumgr SMP 传输,SMP是传输协议。

CONFIG_MCUMGR_SMP_BT=y

#同时确保禁用掉加密传输和连接时的身份验证(如果有绑定等功能的一定要关闭)

CONFIG_MCUMGR_SMP_BT_AUTHEN=n

把堆栈从原来的2048改为4096,为某些需要大堆栈的命令提供空间

CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096

#由于要加入DFU到主线程,增加一点主线程的堆栈空间

CONFIG_MAIN_STACK_SIZE=2348

#~~MCUboot加入结束~~

注2:这对于NCS2.3及之后版本(本文编写时更新到NCS2.3版本):

除了在NCS2.1中加入的全局宏,还应该多加入一条全局宏。

## NCS为 NCS3.1 以及以后需要加入,启用并注册一个SPM 进程,就不用在在main中添加代码了。

CONFIG_MCUMGR_CMD_STAT_MGMT=y

第二步:头文件加入:

注3:针对于NCS2.1

头文件加入:

#include

#include "os_mgmt/os_mgmt.h"

#include "img_mgmt/img_mgmt.h"

注4:如果是NCS2.0头文件加入是:

#include

#include "os_mgmt/os_mgmt.h"

#include "img_mgmt/img_mgmt.h"

注5:如果你的是NCS2.3版本以后,已经不需要在加入头文件了。

第三步:DFU程序加入:

注6:针对于NCS2.及NCS2.1

官方例程中需要加入的是函数有三个,只有加入他们到main()进行初始化,才可以在初始化时把SMP相关的代码加入,如果你不加入,会导致下面的第五步升级包选择后出现错误,由于错误无法进行数据的传输:

smp_bt_register();

os_mgmt_register_group();

img_mgmt_register_group();

注7:针对于NCS2.3及以后

由于官方对底层驱动又做了升级,单独使用一个进程对SMP进行初始化,只要使用一条宏定义开启进程,就不用添加这一步分代码了,已经通过——注2 所列出的全局宏加入了进程,如下图所示,使用MCUMGR_HANDLER_DEFINE(),添加了一个进程用于初始化,而且想对应于NCS2.1使用了static定义了

img_mgmt_register_group();函数,也无法在main()进行添加了。

第三步:编译下载

然后就可以编译了,编译成功后,直接下载,就可以使用nordic的官方APP——nrf connect搜索蓝牙广播的名字,连接后,可以看到已经有了DFU的服务。

第四步、固件制作与升级

在工程中修改一下蓝牙广播的名字改为Nordic_NEW,然后重新构建工程:

然后你可以在你的工程文件的编译子文件中找到升级的bin文件。如我在建立工程时,给编译的子工程起的名字为bulid_5340:

那么在我的工程目录下的zephyr中会找到一个app_updata.bin文件,这就是我们新的app。

传到手机上,在nrf connect中连接设备,然后点击右上脚的DFU图标,选择app_updata.bin文件,然后,如图进行操作:

等待升级完成,,断开连接,复位一下,就可以看到广播名字改变了。

打印信息也没有问题,先是test,进入DFU升级,升级完成后,复位板子,MCUboot模式为none,表示正常启动,升级成功。

特别注意:

如果你使用的版本和我本文的不同,且按照上述步骤不能升级,很有可能就是官方做了升级,改变了部分流程,请一定一定去参看一下路径:zephyr\samples\subsys\mgmt\mcumgr\smp_svr下的例子,我以上的更改也是根据这个来更改的。

3.3.3、使用自定义秘钥进行固件进行签名

如果你使用过SDK开发,那么在DFU时第一步就是生成自己的秘钥并进行替换。同样在NCS中实际项目中也请使用自己的秘钥。

在前面的升级中使用了NCS中自带的秘钥,这是不安全的,你可以在编译结果中找到如下一个警告:“MCUBOOT的秘钥使用的是默认秘钥,不能用于生产使用。”

秘钥一定要使用自己生成的,而不是使用NCS中自带的,而且最好保证秘钥存储的位置不在你的项目中。

还有一点就是:测试的时候你如果使用默认秘钥,那么在每一次删除项目进行重新构建默认秘钥都是可能被更改的,由此导致你不能使用新的APP对使用老版本固件的设备进行升级,因为你的秘钥已经在构建过程中改变了。所以最开始的固件也最好使用新建立的工程从新烧录。

在开始前,我先把官方文档参考连接给出(注意,如果你使用的版本和我的(NCS2.3)不一样,那么请切换到和你版本一致的问的文档):

Firmware updates — nRF Connect SDK 2.3.99 documentation (nordicsemi.com)

版本切换如下图:

第一步:秘钥生成

值的注意的是虽然生成秘钥的加密算法有许多中,但是NCS中的只支持部分,所以我们并不能随意的选择。

NCS中支持三种方式生成秘钥,分别是:

  • 使用openSSL生成秘钥
  • l使用imgtool生成秘钥
  • 使用python生成秘钥

如下截图是官方文档中给出的不同的bootloader支持所支持的秘钥加密算法。生成的时候一定不能使用不支持。否则DFU会失败。

1)、OpenSSL方式

要使用openssl生成秘钥,那么我们就需要有openssl这个插件,你可以在网络上直接搜索openssl安装,在你的PC端安装一个openssl。如果你电脑安装的有Git、VMware、Strawberry等,那么由于这些软件都自带openssl,因此可以直接使用。

首先可以直接运行一下win+R键运行CMD,打开命令行窗口,运行一下如下命令

openssl help

结果:

居然提示不能运行,但是我电脑确定已经装了git的,并且在git运行相同的命令是可以得到响应的。由此应该由于没有把openssl的路径加到环境变量中,导致电脑找不到openssl。因此打开git的安装目录在usr/bin文件下成功找到了openssl的exe文件:

注意如果你也是这种方式,那么请确定好你git安装的路径。接下来我复制该路径,打开环境变量并把该路径加入带系统变量中,流程看下列截图,按照箭头的指示一路下来:

添加并确认完毕后,再次打开cmd命令行窗口,记得一定要在添加完成后再次打开,不然原本是窗口由于没有刷新,依然不能运行openssl.exe 。

添加完毕再次运行命令:

openssl help

成功:

环境没有问题了,那么就可以开始制作秘钥了。

在我的工程文件中新建立一个名字为key的文件,然后再该文件中打开cmd窗口:

在打开的窗口运行一下指令确定依然可以在该路径下执行openssl.exe。

MCUboot支持的几种加密算法都是非对称加密的算法,会生成一对秘钥对,分别为私钥和公钥,公钥直接写入到MCUboot中,在NCS中我们只用把生成的私钥加入到我们的工程中,私钥或在程序的运行下生成公钥,并写入到hex中。同时也对固件进行加密。

选择RSA-2048算法来制作秘钥对:

私钥生成命令:

openssl genrsa -out private.pem 2048

然后就可以在文件夹中看到生成的秘钥了:

2)、使用imgtool

imgtool是一个NCS库中已经包含了的脚本工具,具体路径为:

NCS目录/bootloader/mcuboot/scripts/imgtool.py

因此要在想生成秘钥的文件夹中加入该脚本的绝对路径,如我在前一节的工程先建立的key文件夹下建立另一个文件imgtool,然后确定绝对路径,之后开始制作秘钥

命令如下:

python ../../../../v2.3.0/bootloader/mcuboot/scripts/imgtool.py keygen -t ecdsa-p256 -k priv.pem

由于我电脑是python,没有python3,所以我直接使用python,需要根据自己的环境进行选择:

Python3 ../../../../v2.3.0/bootloader/mcuboot/scripts/imgtool.py keygen -t ecdsa-p256 -k priv.pem

3)、使用python

命令如下:

python ../../../../v2.3.0/nrf/scripts/bootloader/keygen.py --private -o priv.pem

或者:

Python3 ../../../../v2.3.0/nrf/scripts/bootloader/keygen.py --private -o priv.pem

主要路径是nrf/scripts/bootloader/keygen.py,你要根据你需要生成的秘钥的文本去定位该路径的绝对路径。

第二步:替换工程编译时的秘钥

然后可以在工程目录下创建一个文件夹(可以任意,只要找得到秘钥的即可),该文件夹名字必须定义为child_image,并且在其中定义一个mcuboot.conf文件,为什么要求这么严格,因为自动建立脚本已经把响应的子文件的名字都确定好了,所以为了可以在建立时被应用到,必须按照要求来,加入完毕目录结构如下(peripheral_uart_23为项目的根目录):

然后再mcuboot.conf中加入如下的宏定义:

CONFIG_BOOT_SIGNATURE_KEY_FILE="D:/nordic_NCS/work/peripheral_uart_23/key/priv.pem"

其中D:\nordic_NCS\work\peripheral_uart_23\key为我放置私钥的文件位置。

建立编译后可以在编译结果中找到如下的编译信息,证明替换没有问题。

第三步:验证升级

先烧录刚刚的固件,然后改一改蓝牙广播名字,再次建立工程。然后依然是在建立的工程目录下build_5340\zephyr(build_5340是我自定义的编译输出文件的名字,和自己定义的进行匹配),找到app_update.bin,发送到手机上,然后打开手机连接上设备。

可以看到设备现在名字是NORDIC_TEST,点击DFU,开始选择升级固件

选择固件:

然后开始升级等待,这个过程手机最好不要退出去,保持在这个界面,不然很可能导致失败:

升级完成,断开连接再次扫描,会看到设备的ble名字已经变了:

Rtt打印信息如下:

由此测试完毕。

问题解决:在升级的过程中,有遇到有些手机会由于文件系统原因,导致发送失败,如果你有同样的现象,可以换一个文件管理系统进行测试。

3.3.4、UART-DFU工程添加修改

这里要说一下,我真正需要的仅仅是mcumgr.exe,如果说你已经有了那么直接在环境变量中添加好mcumgr.exe的路径,就可以在PC端的任意路径下cmd命令启动,并使用mcuboot的串口升级。如果你是需要其他平台上可以运行的mcumgr的可执行文件,也要自己去生成一个对应平台的可执行的文件。打个比方说,如果我想在Linux系统运行mcumgr.exe去进行升级,那么我怎么获取一个在可以在Linux上可运行的mcumgr.exe。是用linux版本的go去制作。

由于我使用的是Windows,所以我下面制作的是Windows上的mcumgr.exe。

第一步:PC端上位机环境搭建

需要使用mcumgr的功能,那么上位机端也应该支持mcumgr的传输协议SMP,否则没有办法进行传输。有由于支持mcumgr的上位机命令行工具是使用 go语言开发的,所以第一步就是应该在Windows端安装go。

1)、下载go的安装包

https://golang.google.cn/dl/ 打开go的官网进行下载,根据你的系统选择对应的安装包进行下载。

由于我的是win11的64位机,所以下载第一个,如果你的系统是macOS或者是linux,那么请根据你自己最终运行的平台去选择适合的go。

下载完成后点击msi文件进行安装,可以选择你的安装路径,然后一路next下去就行。

安装好后,确定系统环境变量中是否已经把go安装目录下的bin文件路径包含进去了:

然后我们win+R输入cmd后在命令行窗口输入go后回车:看到如下打印,说明go环境安装完成:

2) 、下载mcumgr

开始使用go安装mcumgr的上位机工具(相关命令参考为zephyr官网给出的文档,连接地址如下:

MCUmgr — Zephyr Project Documentation (nordicsemi.com)):

获取MCUmgr命令(需要在有go.mod的目录运行,请看问题解决):

go get github.com/apache/mynewt-mcumgr-cli/mcumgr

如果你无法获取,并报错,请参看后续问题解决,主要是网络原因。

下载完成后:还是无法运行mcumgr,请使用如下命令进行安装

go install github.com/apache/mynewt-mcumgr-cli/mcumgr@latest

如果mcumgr安装成功,那么在go的bin目录下会有mcumgr.exe文件

安装完成请确保已经把go的安装目录下的bin文件所在目录加到了环境变量中。

然后我们任意启用一个cmd命令窗口,输入mcumgr,回车,如下图,证明安装成功了。

问题解决1-缺少go.mod报错:

如果你安装的go改过默认路径或者版本原因,那么可能导致在安装文件中没有go.mod文件,在执行命令时会导致无法安装。

解决方式:在go安装路径下:

建立一个新的文件夹并命名为mod

然后我们进入这个文件夹,并输入一个cmd,打开命令行窗口:

然后运行指令:

go mod init mod 

然后就生成了运行时缺少的go.mod文件了:

然后继续运行mcumgr的安装命令进行安装即可。

问题解决2-网络原因下载失败:

解决了问题1,到这一步还可能会安装失败:

具体原因:默认go的代理网站是GOPROXY=https://proxy.golang.org,direct,是一个外网地址,国内访问不到(开VPN可能访问到,但是我测试时,开VPN也没有用,改代理网站没有问题),

1)、第一种方式时修改代理镜像

代理网站更改命令:

go env -w GOPROXY=https://goproxy.cn,direct

更改后,再去执行go set命令下载mcumgr。

go get github.com/apache/mynewt-mcumgr-cli/mcumgr

2)、第二种方式——直接拉取(前提是安装的有git环境)

先进入到go的安装目录下的src文件夹,然后再在src文件夹下打开cmd命令窗口,然后使用git

直接在github上拉取工具的源码。

拉取命令如下:

Git clone github.com/apache/mynewt-mcumgr-cli/mcumgr

拉取完成,安装即可。

第二步:创建一个工程

正常创建工程,只需要使用如下几个宏定义就行,就是加入到工程的.conf配置文件中配置就行了。

# #确保生成与MCUboot兼容的二进制文件
CONFIG_BOOTLOADER_MCUBOOT=y

#启用mcumgr

CONFIG_MCUMGR=y

#启用用于操作系统管理的处理程序(用于校验出错时的回滚,以及复位操作)

CONFIG_MCUMGR_CMD_OS_MGMT=y

#启用用于imager管理的处理程序(传输/列举/校验确认imager)

CONFIG_MCUMGR_CMD_IMG_MGMT=y

#启动uart传输的宏定义

CONFIG_MCUMGR_SMP_UART=y
CONFIG_CONSOLE=y

  这里的正常是什么意思呢?就是说你APP中没有使用到UART去做数据通讯,NUS透传的例子(peripheral_uart),原本已经使用了UART0作为应用中的数据通讯外设,这个时候不能再用UART0来作为mcuboot的升级传输口了,至少是现在的NCS_2.3版本是不行的,如果要做,那么工作量可能也会很大,不建议自己再去改底层。

  由于我使用的测试工程就是peripheral_uart,已经默然使用uart0作为透传历程了,我只能进行更多的修改。根据不同的需求有两种更改方式:分别是使用UART0作为mcuboot的升级串口,使用UART1或者UART2等其余串口作为mcuboot的串口,二种方式都需要芯片支持多个uart,直接在app中触发升级。还有就是直接使用一个串口,升级必须在mcuboot中。

1)、使用双bank+双UART升级(uart0做smp传输通道)

这个的好处是可以实现回滚。

a、修改app中的UART为uart1

在工程中修改或者添加一个可引用的overlay文件,添加时可以选择命名为app.overlay或者_Board_.overlay(_Board_表示板子的名字)。

打开我工程下的app.overlay,使能uart1,设置引脚,并进行引用,修改完成后如下:

/ {
chosen {
nordic,nus-uart = &uart1;
};
pinctrl: pin-controller {
nus_uart: nus_uart {
phandle = < 0xb >;
group1{
psels = < NRF_PSEL(UART_TX, 1,8 ) >;
};
group2{
psels = < NRF_PSEL(UART_RX, 1,6) >;
bias-pull-up;
};
};
};
};

&uart1 {
status = "okay";
pinctrl-0 = < &nus_uart >;
};

如果你直接修改,注意 phandle = < 0xb >;要确定和最终的zephyr.dts的不重复,就是一个文件不要有两个都为0xb。

以上添加方式不理解可以看如下官方给出的设备树说明博客:详解Zephyr设备树(DeviceTree)与驱动模型 - jayant97 - 博客园 (cnblogs.com)

由以上代码我们启动了uart1,并配置的P1.08和P1.06作为uart1 的通讯引脚。

b、加入宏定义,改变数据通讯UART的通讯方式

在前面提到的prj.conf文件中加入如下宏定义:

CONFIG_UART_ASYNC_API=y
CONFIG_UART_1_ASYNC=y
CONFIG_UART_1_INTERRUPT_DRIVEN=n

用于启动uart1的异步API,禁止中断方式,使用异步的方式通讯,如果你不加入这个三个宏定义,还是采用原本例程默认的,那么在编译下载后,通过RTT打印,你会看到如下报错:

这是说,在设置uart 的回调时,返回-88这个错误,表示系统不支持。具体原因我没有仔细确定,所以一定要进行修改。到这一步第一种uart升级就算加入完成了,直接下载就行。

2)、使用双bank+双uart升级(uart1做smp传输)

这种方式修改的就比较多了,首先是你需要修改MCUboot,让其把默认的为UART0的传输,改变为UART1。

a、工程目录下.conf中宏定义的修改

首先是.conf文件的修改,依然是在启动了mcuboot的基础上设置传输方式为UART,并且启动异步API,同时禁用掉UART0的中断回调方式,启动为异步回调:

CONFIG_MCUMGR_SMP_UART=y
CONFIG_CONSOLE=y

CONFIG_UART_ASYNC_API=y
CONFIG_UART_0_ASYNC=y
CONFIG_UART_0_INTERRUPT_DRIVEN=n

b、app(应用程序)的.overlay的修改

然后是APP中的.overlay的修改,(也就是我们的主应用,把其他mcuboot程序,网络核程序叫做子image)把app中触发升级的串口改为UART1,在工程根目录下的.overlay中加入配置代码:

/ {
chosen {
zephyr,uart-mcumgr = &uart1;
nordic,nus-uart = &uart0;
};
pinctrl: pin-controller {
dfu_uart: dfu_uart {
phandle = < 0xb >;
group1{
psels = < NRF_PSEL(UART_TX, 1,8 ) >;
};
group2{
psels = < NRF_PSEL(UART_RX, 1,6) >;
bias-pull-up;
};
};
};
};

&uart1 {
status = "okay";
pinctrl-0 = < &dfu_uart >;
};

启动了UART1,并配置uart1为mcuboot的uart传输通道。

c、修改mcuboot——子imger的overlay

打开工程目录,建立一个名字为child_image的文件夹:

然后建立一个名字为mcuboot的文件夹:

在进入并建立一个名字为boards的文件夹:

在再此文件夹中建立一个-board-.overlay的文件,其中-board-表示的是项目需要使用的板卡文件,可以简单理解为要使用那个系列的芯片就使用对应的名字:

我使用的是5340,且app运行在应用核,所以使用nrf5340dk_nrf5340_cpuapp这个名字就行。如果你不知道你使用的芯片应该怎么命名,可以在VS code中进行查看:

完整的overlay文件存放目录如下:

项目文件\child_image\mcuboot\boards

然后在nrf5340dk_nrf5340_cpuapp.overlay目录中加入如下的代码:

/ {

chosen {

    zephyr,uart-mcumgr = &uart1;

};

pinctrl: pin-controller {

    dfu\_uart: dfu\_uart {

        phandle = < 0xb >;

        group1{

            psels = < NRF\_PSEL(UART\_TX, 1,8 ) >;

        };  
        group2{  
            psels = < NRF\_PSEL(UART\_RX, 1,6) >;  
            bias-pull-up;  
        };  
    };  
};  

};
&uart1 {
status = "okay";
pinctrl-0 = < &dfu_uart >;
};

到这一步修改就完成了,我们全编译一下,然后需要查看最终的dts文件,看我们进行的修改是否生效,只有生效了才说明是正确的。

d、查看配置是否生效

在全编译完整后,在如下目录先可以找到主image(也就是app)的dts文件——zephay.dts查看:

其中build_5340是我建立工程时选择的生成文件所在目录的名字(根据你自己设置的文件名确认),可以看到在:建立目录\zephyr 路径下的zephyr.dts文件中按照我们的修改正确配置了。

Mcuboot子inage修改的查看:

在 创建目录\mcuboot\zephyr 路径下的zephyr中也是修改成功。

到此为止app就算制作完了。

第三步:使用串口下载升级包

在制作完成固件后,你可以在生成一个app——修改一下蓝牙的广播名,然后获得升级固件

命令:

nrfjprog --com
mcumgr --conntype serial --connstring "COM24,baud=115200" echo hello
mcumgr --conntype serial --connstring "COM23,baud=115200" image upload -e app_update.bin

把板子连上nrf设备后,在命令行窗口运行命令:

nrfjprog --com

获取端口的COM号,如图:

由于我使用的是nrf5340,其有三个虚拟串口,从UART打印端口确定,使用的串口号为COM23。

在开始传输前必须使用测试命令确保uart已经准备好,

命令:

mcumgr --conntype serial --connstring "COM12,baud=115200" echo hello

如果你是使用了第二中添加串口的方式,使用nrfjprog --com命令后可能不会把你串口列举出来,可以通过串口助手参看,确定好端口后:使用更行命令:

命令:

mcumgr --conntype serial --connstring "COM23,baud=115200" image upload -e app_update.bin

等待升级完成:

传输完成后使用如下命令进行激活:

mcumgr --conntype serial --connstring "COM12,baud=115200" image list

由返回可以确定,我们新上传了一个固件,放在bank2中,我们需要用到这个固件的hash值,本次测试升级固件的hash:73edc83fe5a2536bc0a5b9b3015841f6f7a88444ebd75d2dfe0248f6bb3323f4

即运行:

mcumgr --conntype serial --connstring "COM12,baud=115200" image test 73edc83fe5a2536bc0a5b9b3015841f6f7a88444ebd75d2dfe0248f6bb3323f4

然后复位测试确定一下,是否可以跳转到新固件执行:

使用命令:

mcumgr --conntype serial --connstring "COM12,baud=115200" reset

运行完成后要等待一下:

可以看到新的APP运行成功。但是这不是最终的固件修改,需要如下指令来固化:

mcumgr -c acm0 image confirm 73edc83fe5a2536bc0a5b9b3015841f6f7a88444ebd75d2dfe0248f6bb3323f4

然后使用命令复位一下

nrfjprog --reset

等待一会,等程序运行起来:

使用命令:

mcumgr -c acm0 image list

可以看到激活的固件的hash是我们升级固件的hash,升级成功。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章