在NCS中有多種的DFU選擇,強烈推薦使用MCUboot,當然如果你需要選擇傳統的nrf_DFU也是可以的,但是要用到官方修改的原始檔。
關於mcuboot,原理性的東西在官網和官方部落格中有講,可以自行檢視,後面只是簡單的提一下:MCUmgr — Zephyr Project Documentation (nordicsemi.com)
為什麼要使用mcubooot,原因是在NCS使用mcuboot,可以很快的實現DFU的加入,可以同時加入ble-dfu和uart-dfu。就是加入幾個宏定義的事情,就完成了在SDK中可能要花很久才能完成的工程移植,既有ble也有uart的升級。
這裡是為了便於後續工程直接呼叫:
第一步:在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的相關庫了。
開啟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程式碼了。
新增標頭檔案:
#include "nrf_dfu.h" #include "nrf_dfu_validation.h" #include <drivers/nrfx_errors.h> // 加入重啟系統的標頭檔案 #include <zephyr/sys/reboot.h>
加入系統重啟的原因是,如果在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()函數初始化校驗頁。
改名字後編譯一個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。
簡單一點來說,一個bootloader應該包括兩個部分,一個是DFU時資料傳輸協定部分、一個是對image的管理部分。
注意一點,使用MCUboot時,傳輸的升級韌體是app_update.bin,後續生成韌體後,路徑如下: 編譯生成檔案件\zephyr 下。後續就不再說明了。
DFU協定一般都是把升級的image檔案分成一塊一塊的傳給bootloader,在傳輸過程中,DFU會完成校驗每一塊的資料對不對,出現錯誤處理等工作。值得注意的是DFU協定不管你物理通道是什麼,你可以選擇ble、uart、USB等。同時在zephyr中有許多的協定,只對SMP做一下簡單介紹。
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個具體命令(函數):
在後續的例程中我們會使用兩條宏定義去啟動上面的兩個命令組。
在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官網中有這樣一個解釋,為什麼你在出門期間你信任你的家是安全的,因為你家只有一道門,由此有這樣一個邏輯:
由此如果你口袋中的鑰匙不見了,那麼你將失去對門的信任。
在每次復位或者上電後執行安全bootloader,會通過驗證bootloader中的下一個image的簽名和後設資料來建立一個信任根本,就像確定鑰匙是不是唯一且在自己的口袋中,如果任意一次驗證不通過,那麼bootloader就好停止,由此保證bootloader中的下一個image在被篡改後不會啟動。由此杜絕攻擊者通過更改韌體來入侵接管裝置,以保證安全性。
總結:
1)、不可變的:bootloader是flash鎖定的,如果不擦除整個裝置,無法進行修改或者刪除。
2)、安全的:擁有一套驗證機制,保證自己和使用者app的安全,不會被攻擊者篡改。因此要提供自己的祕鑰,讓bootloader進行簽名驗證(下一個image是否被篡改)和後設資料(Metadata)校驗(檢測image是否相容)。
注意本章節新增的,不管是ble的還是uart的,在升級的時候都是後臺式的,升級是你可以正常使用。
依然選擇在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)。
這裡值得注意的是,不同版本之間這些CONFIG設定是會變的,所以在自己加入時一定要去看上面提到的參考,而不是無腦的複製貼上。不然很可能你的版本和本文的版本不一樣,導致你初始的工程編譯都通過不了,稍後我會列舉一下NCS2.1和NCS2.3的區別。
本次在NUS(透傳)程式的基礎上新增MCUboot,例子路徑:nrf\samples\bluetooth\peripheral_uart。使用VS code建立一個該工程的映像,並且編譯通過後,開始DFU的新增。
注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 <zephyr/mgmt/mcumgr/smp_bt.h> #include "os_mgmt/os_mgmt.h" #include "img_mgmt/img_mgmt.h"
注4:如果是NCS2.0標頭檔案加入是:
#include <mgmt/mcumgr/smp_bt.h> #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下的例子,我以上的更改也是根據這個來更改的。
如果你使用過SDK開發,那麼在DFU時第一步就是生成自己的祕鑰並進行替換。同樣在NCS中實際專案中也請使用自己的祕鑰。
在前面的升級中使用了NCS中自帶的祕鑰,這是不安全的,你可以在編譯結果中找到如下一個警告:「MCUBOOT的祕鑰使用的是預設祕鑰,不能用於生產使用。」
祕鑰一定要使用自己生成的,而不是使用NCS中自帶的,而且最好保證祕鑰儲存的位置不在你的專案中。
還有一點就是:測試的時候你如果使用預設祕鑰,那麼在每一次刪除專案進行重新構建預設祕鑰都是可能被更改的,由此導致你不能使用新的APP對使用老版本韌體的裝置進行升級,因為你的祕鑰已經在構建過程中改變了。所以最開始的韌體也最好使用新建立的工程從新燒錄。
在開始前,我先把官方檔案參考連線給出(注意,如果你使用的版本和我的(NCS2.3)不一樣,那麼請切換到和你版本一致的問的檔案):
Firmware updates — nRF Connect SDK 2.3.99 documentation (nordicsemi.com)
版本切換如下圖:
值的注意的是雖然生成祕鑰的加密演演算法有許多中,但是NCS中的只支援部分,所以我們並不能隨意的選擇。
NCS中支援三種方式生成祕鑰,分別是:
如下截圖是官方檔案中給出的不同的bootloader支援所支援的祕鑰加密演演算法。生成的時候一定不能使用不支援。否則DFU會失敗。
要使用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
然後就可以在資料夾中看到生成的祕鑰了:
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
命令如下:
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列印資訊如下:
由此測試完畢。
問題解決:在升級的過程中,有遇到有些手機會由於檔案系統原因,導致傳送失敗,如果你有同樣的現象,可以換一個檔案管理系統進行測試。
由於我使用的是Windows,所以我下面製作的是Windows上的mcumgr.exe。
需要使用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中。
這個的好處是可以實現回滾。
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,升級成功。