一、简介
在NCS中有多种的DFU选择,强烈推荐使用MCUboot,当然如果你需要选择传统的nrf_DFU也是可以的,但是要用到官方修改的源文件。
关于mcuboot,原理性的东西在官网和官方博客中有讲,可以自行查看,后面只是简单的提一下:MCUmgr — Zephyr Project Documentation (nordicsemi.com)
为什么要使用mcubooot,原因是在NCS使用mcuboot,可以很快的实现DFU的加入,可以同时加入ble-dfu和uart-dfu。就是加入几个宏定义的事情,就完成了在SDK中可能要花很久才能完成的工程移植,既有ble也有uart的升级。
二、nRF bootloader升级程序制作
2.1、在nrf的库中加入nRF bootloader库文件
这里是为了便于后续工程直接调用:
第一步:在nrfsubsys目录下的CMakeLists.txt文件中加入我们需要加入的库的目录文件
打开CMakeLists.txt文件在最后加入:
add_subdirectory_ifdef(CONFIG_NRF_DFU nrf_dfu)
add_subdirectory_ifdef(CONFIG_NRF_DFU_RPC_NET nrf_dfu)
第二步:在nrfsubsys目录下的Kconfig文件中加入对即将添加到nrf库的库文件nrf_dfu的宏定义配置文件应用:
打开Kconfig:加入rsource “nrf_dfu/Kconfig”,如果不加入,当我们在工程项目的.conf文件中加如引用文件的宏时会找不的相关配置宏定义而报错:
第三步:在nrfsubsys目录下建立一个文件夹:名字和我们刚刚写到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文件加入,然后替换掉nrfsubsys目录下的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、程序添加
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中对应的定义如下:
加入后我们直接进行编译,记住保存后,点击全编译:
编译后没有编译正确,报了一个错:
说zephyrincludezephyrdfuflash_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_5340zephyr(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的管理部分。
注意一点,使用MCUboot时,传输的升级固件是app_update.bin,后续生成固件后,路径如下: 编译生成文件件zephyr 下。后续就不再说明了。
3.1、DFU数据传输协议
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个具体命令(函数):
在后续的例程中我们会使用两条宏定义去启动上面的两个命令组。
3.2、image管理部分
接下来是在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中状态中的那种状态了。如果状态不对,就可以根据前面的状态解释来进行修改。
3.3、一级不可升级的安全bootloader
为什么要加一个安全,在MCUboot官网中有这样一个解释,为什么你在出门期间你信任你的家是安全的,因为你家只有一道门,由此有这样一个逻辑:
- 你信任一扇门,因为你信任锁
- 你信任锁,是因为你信任唯一的钥匙
- 你信任钥匙,是因为它正在你的口袋里
由此如果你口袋中的钥匙不见了,那么你将失去对门的信任。
在每次复位或者上电后运行安全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)、一是有参考例程:zephyrsamplessubsysmgmtmcumgrsmp_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,例子路径:nrfsamplesbluetoothperipheral_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,表示正常启动,升级成功。
特别注意:
如果你使用的版本和我本文的不同,且按照上述步骤不能升级,很有可能就是官方做了升级,改变了部分流程,请一定一定去参看一下路径:zephyrsamplessubsysmgmtmcumgrsmp_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_NCSworkperipheral_uart_23key为我放置私钥的文件位置。
建立编译后可以在编译结果中找到如下的编译信息,证明替换没有问题。
第三步:验证升级
先烧录刚刚的固件,然后改一改蓝牙广播名字,再次建立工程。然后依然是在建立的工程目录下build_5340zephyr(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 = 1,8 ) >; }; group2{ psels = 1,6) >; bias-pull-up; }; }; }; }; &uart1 { status = "okay"; pinctrl-0 = ; };
如果你直接修改,注意 phandle = ;要确定和最终的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 = 1,8 ) >; }; group2{ psels = 1,6) >; bias-pull-up; }; }; }; }; &uart1 { status = "okay"; pinctrl-0 = ; };
启动了UART1,并配置uart1为mcuboot的uart传输通道。
c、修改mcuboot——子imger的overlay
打开工程目录,建立一个名字为child_image的文件夹:
然后建立一个名字为mcuboot的文件夹:
在进入并建立一个名字为boards的文件夹:
在再此文件夹中建立一个-board-.overlay的文件,其中-board-表示的是项目需要使用的板卡文件,可以简单理解为要使用那个系列的芯片就使用对应的名字:
我使用的是5340,且app运行在应用核,所以使用nrf5340dk_nrf5340_cpuapp这个名字就行。如果你不知道你使用的芯片应该怎么命名,可以在VS code中进行查看:
完整的overlay文件存放目录如下:
项目文件child_imagemcubootboards
然后在nrf5340dk_nrf5340_cpuapp.overlay目录中加入如下的代码:
/ { chosen { zephyr,uart-mcumgr = &uart1; }; pinctrl: pin-controller { dfu_uart: dfu_uart { phandle = 0xb >; group1{ psels = 1,8 ) >; }; group2{ psels = 1,6) >; bias-pull-up; }; }; }; }; &uart1 { status = "okay"; pinctrl-0 = ; };
到这一步修改就完成了,我们全编译一下,然后需要查看最终的dts文件,看我们进行的修改是否生效,只有生效了才说明是正确的。
d、查看配置是否生效
在全编译完整后,在如下目录先可以找到主image(也就是app)的dts文件——zephay.dts查看:
其中build_5340是我建立工程时选择的生成文件所在目录的名字(根据你自己设置的文件名确认),可以看到在:建立目录zephyr 路径下的zephyr.dts文件中按照我们的修改正确配置了。
Mcuboot子inage修改的查看:
在 创建目录mcubootzephyr 路径下的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,升级成功。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 音容笑貌,两臻佳妙,人工智能AI换脸(deepfake)技术复刻《卡萨布兰卡》名场面(Python3.10)
影史经典《卡萨布兰卡》是大家耳熟能详的传世名作,那一首壮怀激烈,激奋昂扬的马赛曲,应当是通片最为激动人心的经典桥段了,本次我们基于faceswap和so-vits库让AI川普复刻美国演员保罗·亨雷德高唱《马赛曲》的名场面。 配置人脸替换DeepFakes项目 …