• nordic——NCS(基于NCS—zephyrRTOS)下的DFU升级(ble加uart)


    一、简介

      在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库文件

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

    第一步:在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.txtKconfig文件中的名字一致,名为:nrf_dfu

     

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

    主要修改的有nrf_dfu_ble.cnrf_dfu_types.c等文件,当然不是全部修改,但是修改起来很麻烦,你需要知道整个原理,还需要了解nordicnrf 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.txtKconfig文件,就直接使用。

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

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

     

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

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

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

    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)
    复制代码

    可以看到分分别使用是两个语句加入了头文件和加入了nordicbleDFU.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\zephyrbuild_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.1DFU数据传输协议

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

    3.1.1SMP dfu协议

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

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

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

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

     

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

     

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

    3.2image管理部分

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

     

     

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

     

    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

    不进行DFUMCU直接跳转到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官网中有这样一个解释,为什么你在出门期间你信任你的家是安全的,因为你家只有一道门,由此有这样一个逻辑:

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

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

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

    总结:

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

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

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

    3.3.1、加入MCUboot

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

    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.1NCS2.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相关的代码加入,如果你不加入,会导致下面的第五步升级包选择后出现错误,由于错误无法进行数据的传输:

    1
    2
    3
    4
    5
    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文件下成功找到了opensslexe文件:

     

    注意如果你也是这种方式,那么请确定好你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\zephyrbuild_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的官网进行下载,根据你的系统选择对应的安装包进行下载。

    由于我的是win1164位机,所以下载第一个,如果你的系统是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安装成功,那么在gobin目录下会有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升级(uart0smp传输通道)

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

    a、修改app中的UARTuart1

    在工程中修改或者添加一个可引用的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升级(uart1smp传输)

    这种方式修改的就比较多了,首先是你需要修改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
    复制代码

    bapp(应用程序)的.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,并配置uart1mcubootuart传输通道。

    c、修改mcuboot——子imgeroverlay

    打开工程目录,建立一个名字为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文件中按照我们的修改正确配置了。

     

    Mcubootinage修改的查看:

    创建目录\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值,本次测试升级固件的hash73edc83fe5a2536bc0a5b9b3015841f6f7a88444ebd75d2dfe0248f6bb3323f4

    即运行:

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

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

    使用命令:

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

    运行完成后要等待一下:

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

    1
    mcumgr -c acm0 image confirm 73edc83fe5a2536bc0a5b9b3015841f6f7a88444ebd75d2dfe0248f6bb3323f4

    然后使用命令复位一下

    nrfjprog --reset

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

    使用命令:

    mcumgr -c acm0 image list

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

     

  • 相关阅读:
    甲壳素晶须/羟丁基壳聚糖温敏水凝胶/羟乙基壳聚糖/透明质酸双网络水凝胶的制备
    uniapp前端微信支付代码
    【计算机毕业设计】22.学校试卷生成系统+vue
    K8S 极速入门
    【机器学习】实验1布置:基于决策树的英雄联盟游戏胜负预测
    “摸不着”的数字孪生,如何带来“看得见”的数据效益?
    【Web UI自动化测试】Web UI自动化测试之框架篇(全网最全)
    javascript异步编程之generator(生成器函数)与asnyc/await语法糖
    一站式全链路压力测试平台Pone有哪些功能?
    提交本地项目到GitHub
  • 原文地址:https://www.cnblogs.com/HW-liu/p/17480372.html