NRF52832學(xué)習(xí)筆記(6)——OTA DFU接口使用

一、簡(jiǎn)介

1.1 OTA DFU

DFU(Device Firmware Update) 設(shè)備固件升級(jí),而 OTA(Over The Air) 空中升級(jí)技術(shù)是DFU其中一種類型。其他類型包括:UART、USB和SPI等有線方式升級(jí)。

第一次燒錄時(shí)需要將softdevice(協(xié)議棧)、bootloader(引導(dǎo)加載程序)、settings(引導(dǎo)程序配置頁(yè))、application(用戶應(yīng)用程序)合成一個(gè)hex文件進(jìn)行燒錄。

而之后的固件升級(jí),只需通過(guò)將application(用戶應(yīng)用程序)合成一個(gè)zip的升級(jí)包,用手機(jī)APP進(jìn)行上傳更新。

以下將分別說(shuō)明 bootloader、settings、application 、升級(jí)包在DFU中的作用及展示它們?cè)贔lash中的布局。

1.2 DFU Flash布局

DFU有兩種模式,分別為:dual banksingle bank
簡(jiǎn)單來(lái)說(shuō) dual bank 模式在升級(jí)時(shí)系統(tǒng)先進(jìn)入bootloader,然后把新系統(tǒng)(新固件)下載下來(lái)并校驗(yàn)成功,在擦除老系統(tǒng)(老固件),并運(yùn)行新系統(tǒng)。缺點(diǎn)是耗費(fèi)比較多的存儲(chǔ)空間。

關(guān)于dual bank模式詳細(xì)介紹查看 BLE DFU:Dual Bank

dual bank

single bank 模式在升級(jí)時(shí)系統(tǒng)先進(jìn)入bootloader,然后立即擦除老系統(tǒng)(老固件),再把新系統(tǒng)(新固件)下載到原本老系統(tǒng)的存儲(chǔ)區(qū)域,在運(yùn)行起來(lái)。缺點(diǎn)是如果升級(jí)過(guò)程中出現(xiàn)問(wèn)題或新固件有問(wèn)題,就會(huì)一直停留在bootloader,相當(dāng)于“變磚”了。
single bank

注:Bootloader在接收數(shù)據(jù)前會(huì)檢查Bank 1的大小,如果空間足夠,就執(zhí)行Dual Bank,否則當(dāng)前應(yīng)用程序,騰出空間執(zhí)行Single Bank。SDK自動(dòng)完成這個(gè)判斷動(dòng)作。

1.3 Bootloader

bootloader 是引導(dǎo)加載程序,DFU的基本流程就是在 bootloader中接收新固件數(shù)據(jù)并寫(xiě)入Flash,接收完畢后重啟跳轉(zhuǎn)并運(yùn)行新固件 。其中SDK v11及更早版本中bootloader為開(kāi)放透明性的Legacy DFU,而SDK v12之后有了加密性的Secure DFU。

關(guān)于Legacy DFU詳細(xì)介紹查看 BLE DFU:Legacy DFU
關(guān)于Bootloader詳細(xì)介紹查看 BLE DFU:Bootloader

1.4 Settings

settings 全稱bootloader settings,用于引導(dǎo)設(shè)備啟動(dòng)后由bootloader到application的自動(dòng)跳轉(zhuǎn)。bootloader啟動(dòng)時(shí)候,會(huì)檢查Settings中的 bank0_bank_code、bank0_img_crc,如果二者都正確,則執(zhí)行跳轉(zhuǎn)進(jìn)入application,否則駐留在bootloader中執(zhí)行DFU。

關(guān)于settings詳細(xì)介紹查看 解讀Settings文件

1.5 Application

在設(shè)備啟動(dòng)后bootloader到application進(jìn)行正常工作,而當(dāng)開(kāi)發(fā)者后續(xù)需要對(duì)系統(tǒng)進(jìn)行升級(jí)時(shí),則需要由application重新跳轉(zhuǎn)到bootloader中,這個(gè)過(guò)程可以采用按鍵方式(button)觸發(fā),也可以直接用BLE連接發(fā)送命令方式(buttonless),本文采取的切換DFU模式方式就是buttonless。

關(guān)于application詳細(xì)介紹查看 BLE DFU:Application


1.6 升級(jí)包

從SDK 12開(kāi)始,Nordic為DFU操作增加了簽名校驗(yàn)機(jī)制,稱為Secure DFU。執(zhí)行Secure DFU,需要使用升級(jí)包(zip),而不能直接使用二進(jìn)制文件(hex/bin)。
升級(jí)包 包括manifest.json(文件清單)、nrf52832_xxaa.bin(新固件)和nrf52832_xxaa.dat(init packet),其中init packet包含了meta信息:新固件的類型、大小、版本和簽名信息。這里的簽名將在執(zhí)行DFU被校驗(yàn)。

關(guān)于升級(jí)包詳細(xì)介紹查看 BLE DFU:生成升級(jí)包
關(guān)于init packet詳細(xì)介紹查看 弄懂 Init Packet

二、安裝軟件

注意:由于Secure DFU需要micro-ecc庫(kù)進(jìn)行簽名驗(yàn)證,需要micro_ecc_lib_nrf52.lib,所以需要使用GCC編譯器生成。

所以我們只需將micro_ecc_lib_nrf52.lib加入我們的工程(在后面編譯bootloader工程中說(shuō)明),即可忽略以下2.1及2.2的安裝

2.1 安裝gcc-arm-none-eabi(可跳過(guò))

作用:編譯micro-ecc

官網(wǎng)下載:https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
百度網(wǎng)盤(pán):https://pan.baidu.com/s/1AQLMOVun3sMcvRd6WqL7AQ 提取碼:jh4v

安裝gcc-arm-none-eabi過(guò)程查看 Secure DFU環(huán)境搭建 中2.1,注意要安裝在C盤(pán)

2.2 安裝MinGW(可跳過(guò))

作用:Windows的極簡(jiǎn)GNU編譯器(GCC),執(zhí)行make

官網(wǎng)下載:https://osdn.net/projects/mingw/releases/
百度網(wǎng)盤(pán):https://pan.baidu.com/s/1QU-DRp8K2xFkUaMNflbs7Q 提取碼:w5vy

安裝MinGW過(guò)程及環(huán)境變量配置查看 Secure DFU環(huán)境搭建 中2.2和2.3,注意要安裝在C盤(pán)

2.3 下載micro-ecc-master

作用:Nordic的DFU中采用了micro-ecc實(shí)現(xiàn)ECDSA算法,需要用到micro-ecc源碼

官網(wǎng)下載:https://github.com/kmackay/micro-ecc
百度網(wǎng)盤(pán):https://pan.baidu.com/s/1GYS5z-nbNgbyzrTk98NHog 提取碼:tumw

2.4 安裝Python

作用:pc-nrfutil需要用到python-2.7的環(huán)境

官網(wǎng)下載:https://www.python.org/downloads/
百度網(wǎng)盤(pán):https://pan.baidu.com/s/1kphEO80KTAjGtVvmoaDWLw 提取碼:du7i

安裝Python過(guò)程及環(huán)境變量配置查看 Secure DFU環(huán)境搭建 中2.5,注意要安裝在C盤(pán)

2.5 安裝pc-nrfutil

作用:Nordic發(fā)布的PC端工具,支持無(wú)線DFU和加密功能

官網(wǎng)下載:https://github.com/NordicSemiconductor/pc-nrfutil/
百度網(wǎng)盤(pán):https://pan.baidu.com/s/1L7-Rkv5P3nWG_8Lc-B6YhA 提取碼:payb

以下兩個(gè)選其一
1.把pc-nrfutil解壓。打開(kāi)有setup.py的文件夾,在此處打開(kāi)dos命令(shift + 鼠標(biāo)右鍵)


輸入python setup.py install,需要聯(lián)網(wǎng)

2.打開(kāi)命令行,輸入pip install nrfutil安裝nrfutil,需要聯(lián)網(wǎng)。
安裝完成后,輸入nrfutil version,如下則表示安裝成功。

三、修改Bootloader工程

3.1 添加工程并編譯

找到SDK中 secure_bootloader 工程(nRF5_SDK_15.3.0_59ac345\examples\dfu\secure_bootloader\pca10040_ble\arm5_no_packs)


然后進(jìn)行編譯,發(fā)現(xiàn)報(bào)出兩類錯(cuò)誤

  1. uecc.h 文件找不到
  1. 需要生成私有的 key

3.2 加入micro-ecc

把下載的 micro-ecc-master.zip 解壓,解壓后拷貝到nRF5_SDK_15.3.0_59ac345\external\micro-ecc文件中,重新命名為micro-ecc

3.3 生成micro_ecc_lib_nrf52.lib并加入

生成 micro_ecc_lib_nrf52.lib 過(guò)程查看 Secure DFU環(huán)境搭建 中2.4

或者下載鏈接:https://pan.baidu.com/s/1zIUhCe7F4UCuQBQLCHRY8A [18qt]
解壓后將 micro_ecc_lib_nrf52.lib 放到對(duì)應(yīng)SDK\external\micro-ecc\nrf52hf_keil\armgcc文件夾下

3.4 生成dfu_public_key.c并加入

3.4.1 首先生成私鑰

在D盤(pán)新建一個(gè)文件夾,命名為key,在cmd命令中輸入以下內(nèi)容:nrfutil.exe keys generate D:\key\private.key指令,生成私鑰文件private.key


注意:要保存好私鑰private.key,以后每個(gè)新固件升級(jí)時(shí),都要先通過(guò)這個(gè)私鑰進(jìn)行簽名,一旦丟失,DFU將無(wú)法進(jìn)行

3.4.2 然后生成公鑰

輸入生成公鑰指令:nrfutil.exe keys display --key pk --format code D:\key\private.key --out_file public_key.c

3.4.3 將公鑰加入工程

將公鑰public_key.c改名為 dfu_public_key.c,并將該文件替換掉目錄\examples\dfu下的dfu_public_key.c

3.5 根據(jù)情況屏蔽代碼

如果LED燈IO口跟secure_bootloader工程默認(rèn)引腳不同或原來(lái)LED引腳用作其他功能,需要將dfu_observer()函數(shù)中LED燈相關(guān)代碼屏蔽,否則會(huì)一直運(yùn)行在bootloader,不跳轉(zhuǎn)到application

3.6 配置sdk_config文件

點(diǎn)擊 sdk_config.h 文件


選擇 Configuration Wizard

將進(jìn)入DFU方式改為無(wú)按鍵的BLE連接發(fā)送命令方式

四、修改Application工程

4.1 配置sdk_config文件

點(diǎn)擊 sdk_config.h 文件


選擇 Configuration Wizard

勾選使能DFU服務(wù)

修改UUID大小,在原有基礎(chǔ)上+1


修改RAM空間大小,每新增一個(gè)UUID增加0x10

4.2 添加工程文件

4.2.1 添加源文件

在工程中增加一個(gè)文件夾nRF_DFU,并添加以下文件:

  • <sdk>\components\ble\ble_services\ble_dfu\ble_dfu.c
  • <sdk>\components\ble\ble_services\ble_dfu\ble_dfu_bonded.c
  • <sdk>\components\ble\ble_services\ble_dfu\ble_dfu_unbonded.c

在工程中增加一個(gè)文件夾nRF_SVC,并添加以下文件:

  • <sdk>\components\libraries\bootloader\dfu\nrf_dfu_svci.c

4.2.2 添加Include目錄

添加以下路徑:

  • ../../../../../../components/libraries/bootloader
  • ../../../../../../components/libraries/bootloader/ble_dfu
  • ../../../../../../components/libraries/bootloader/dfu
  • ../../../../../../components/libraries/svc

4.2.3 添加宏

添加下列項(xiàng):

  • NRF_DFU_TRANSPORT_BLE=1
  • BL_SETTINGS_ACCESS_ONLY


4.3 修改main.c

4.3.1 添加頭文件

在main.c中添加以下頭文件:

#include "nrf_dfu_ble_svci_bond_sharing.h"
#include "nrf_svci_async_function.h"
#include "nrf_svci_async_handler.h"
#include "ble_dfu.h"
#include "nrf_power.h"
#include "nrf_bootloader_info.h"

4.3.2 添加代碼

在main.c中添加以下函數(shù):

#if NRF_MODULE_ENABLED(BLE_DFU)
/**@brief Function for handling dfu events from the Buttonless Secure DFU service
 *
 * @param[in]   event   Event from the Buttonless Secure DFU service.
 */
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
{
    switch(event)
    {
        case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE:
        {
            NRF_LOG_INFO("Device is preparing to enter bootloader mode.");

            // Prevent device from advertising on disconnect.
            ble_adv_modes_config_t config;
            advertising_config_get(&config);
            config.ble_adv_on_disconnect_disabled = true;
            ble_advertising_modes_config_set(&m_advertising, &config);

            // YOUR_JOB: Disconnect all other bonded devices that currently are connected.
            //         This is required to receive a service changed indication
            //         on bootup after a successful (or aborted) Device Firmware Update.
            uint32_t conn_count = ble_conn_state_for_each_connected(disconnect, NULL);
            NRF_LOG_INFO("Disconnected %d links.", conn_count);
            break;
        }

        case BLE_DFU_EVT_BOOTLOADER_ENTER:
            // YOUR_JOB: Write app-specific unwritten data to FLASH, control finalization of this
            //           by delaying reset by reporting false in app_shutdown_handler
            NRF_LOG_INFO("Device will enter bootloader mode.");
            break;

        case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED:
            NRF_LOG_ERROR("Request to enter bootloader mode failed asynchroneously.");
            // YOUR_JOB: Take corrective measures to resolve the issue
            //           like calling APP_ERROR_CHECK to reset the device.
            break;

        case BLE_DFU_EVT_RESPONSE_SEND_ERROR:
            NRF_LOG_ERROR("Request to send a response to client failed.");
            // YOUR_JOB: Take corrective measures to resolve the issue
            //           like calling APP_ERROR_CHECK to reset the device.
            APP_ERROR_CHECK(false);
            break;

        default:
            NRF_LOG_ERROR("Unknown event from ble_dfu_buttonless.");
            break;
    }
}

/**@brief Handler for shutdown preparation.
 *
 * @details During shutdown procedures, this function will be called at a 1 second interval
 *          untill the function returns true. When the function returns true, it means that the
 *          app is ready to reset to DFU mode.
 *
 * @param[in]   event   Power manager event.
 *
 * @retval  True if shutdown is allowed by this power manager handler, otherwise false.
 */
static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event)
{
    switch(event)
    {
        case NRF_PWR_MGMT_EVT_PREPARE_DFU:
            NRF_LOG_INFO("Power management wants to reset to DFU mode.");
            // YOUR_JOB: Get ready to reset into DFU mode
            //
            // If you aren't finished with any ongoing tasks, return "false" to
            // signal to the system that reset is impossible at this stage.
            //
            // Here is an example using a variable to delay resetting the device.
            //
            // if (!m_ready_for_reset)
            // {
            //      return false;
            // }
            // else
            //{
            //
            //    // Device ready to enter
            //    uint32_t err_code;
            //    err_code = sd_softdevice_disable();
            //    APP_ERROR_CHECK(err_code);
            //    err_code = app_timer_stop_all();
            //    APP_ERROR_CHECK(err_code);
            //}
            break;

        default:
            // YOUR_JOB: Implement any of the other events available from the power management module:
            //      -NRF_PWR_MGMT_EVT_PREPARE_SYSOFF
            //      -NRF_PWR_MGMT_EVT_PREPARE_WAKEUP
            //      -NRF_PWR_MGMT_EVT_PREPARE_RESET
            return true;
    }

    NRF_LOG_INFO("Power management allowed to reset to DFU mode.");
    return true;
}

//lint -esym(528, m_app_shutdown_handler)
/**@brief Register application shutdown handler with priority 0.
 */
NRF_PWR_MGMT_HANDLER_REGISTER(app_shutdown_handler, 0);

static void buttonless_dfu_sdh_state_observer(nrf_sdh_state_evt_t state, void *p_context)
{
    if(state == NRF_SDH_EVT_STATE_DISABLED)
    {
        // Softdevice was disabled before going into reset. Inform bootloader to skip CRC on next boot.
        nrf_power_gpregret2_set(BOOTLOADER_DFU_SKIP_CRC);

        //Go to system off.
        nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
    }
}

/* nrf_sdh state observer. */
NRF_SDH_STATE_OBSERVER(m_buttonless_dfu_state_obs, 0) =
{
    .handler = buttonless_dfu_sdh_state_observer,
};


static void advertising_config_get(ble_adv_modes_config_t *p_config)
{
    memset(p_config, 0, sizeof(ble_adv_modes_config_t));

    p_config->ble_adv_fast_enabled  = true;
    p_config->ble_adv_fast_interval = APP_ADV_INTERVAL;
    p_config->ble_adv_fast_timeout  = APP_ADV_DURATION;
}


static void disconnect(uint16_t conn_handle, void *p_context)
{
    UNUSED_PARAMETER(p_context);

    ret_code_t err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
    if(err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("Failed to disconnect connection. Connection handle: %d Error: %d", conn_handle, err_code);
    }
    else
    {
        NRF_LOG_DEBUG("Disconnected connection handle %d", conn_handle);
    }
}
#endif

4.3.3 添加DFU服務(wù)

在main.c/services_init函數(shù)末尾添加DFU服務(wù):

/**@brief Function for initializing services that will be used by the application.
 */
static void services_init(void)
{
    ret_code_t err_code;
    nrf_ble_qwr_init_t qwr_init = {0};

    // Initialize Queued Write Module.
    qwr_init.error_handler = nrf_qwr_error_handler;

    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);
    
#if NRF_MODULE_ENABLED(BLE_DFU)     
    ble_dfu_buttonless_init_t dfus_init = {0};  
    // Initialize DFU.
    dfus_init.evt_handler = ble_dfu_evt_handler;
    err_code = ble_dfu_buttonless_init(&dfus_init);
    APP_ERROR_CHECK(err_code);
#endif
}

4.3.4 添加DFU iOS兼容

由于iOS DFU的時(shí)候默認(rèn)就會(huì)去改廣播名字,為了兼容iOS,一行代碼必須添加。
在main.c/main函數(shù)頭部添加:

/**@brief Application main function.
 */
int main(void)
{
    // Initialize the async SVCI interface to bootloader before any interrupts are enabled.
    ret_code_t err_code;
    err_code = ble_dfu_buttonless_async_svci_init();
    APP_ERROR_CHECK(err_code);
    ···
    ···
}

五、燒錄固件

5.1 生成bootloader settings

直接燒錄softdevice、bootloader和application,會(huì)發(fā)現(xiàn)application并未運(yùn)行,芯片一直跑在Bootloader中。

芯片啟動(dòng)后先進(jìn)入Bootloader,檢測(cè)Bootloader Settings中的數(shù)據(jù),如果這些數(shù)據(jù)指示Flash中有一個(gè)有效的Application,則跳轉(zhuǎn)進(jìn)入Application。Bootloader Settings是Flash中的一段區(qū)域,它包含了Application的大小、CRC等數(shù)據(jù),執(zhí)行DFU時(shí)也會(huì)在這里存儲(chǔ)狀態(tài)信息。

正常執(zhí)行DFU時(shí),Bootloader自動(dòng)生成和維護(hù)Bootloader Settings信息。而燒錄過(guò)程不同,需要手動(dòng)寫(xiě)入??梢愿鶕?jù)application.hex生成一個(gè)bl_settings.hex,以產(chǎn)生這些數(shù)據(jù),然后燒錄這個(gè)hex。

生成bl_settings.hex的命令為:
nrfutil settings generate --family NRF52 --application nrf52832_xxaa.hex --application-version 0 --bootloader-version 0 --bl-settings-version 2 settings.hex

其中SDK15.3之后 bl-settings-version值為2,之前的SDK版本值為1

5.2 生成四合一hex文件

mergehex 一次最大合并3個(gè)文件,所以我們需要分兩次合并,將softdevice(協(xié)議棧)、bootloader(引導(dǎo)加載程序)和application(應(yīng)用程序)編譯生成的hex文件存在單獨(dú)文件夾,鼠標(biāo)放在空白處,shift+右鍵執(zhí)行“在此處打開(kāi)命令窗口”,輸入命令:

mergehex -m s132_nrf52_6.1.1_softdevice.hex bootloader.hex nrf52832_xxaa.hex -o output3tol.hex

此時(shí)完成一次文件合并,再次輸入命令:
mergehex -m output3tol.hex settings.hex -o output4tol.hex

至此,直接用 nRFgo Studio 下載一個(gè)文件即可。

5.3 燒錄

5.3.1 使用nRFgo Studio燒錄

先擦除,在燒錄output4tol.hex

六、升級(jí)固件

6.1 生成升級(jí)包

將 private.key 和 nrf52832_xxaa.hex 放在同一文件夾,鼠標(biāo)放在空白處,shift+右鍵執(zhí)行“在此處打開(kāi)命令窗口”,輸入命令:
nrfutil pkg generate --hw-version 52 --application-version 1 --application nrf52832_xxaa.hex --sd-req 0xB7 --key-file private.key dfufile.zip
其中 --sd-req 0xB7 為協(xié)議棧的版本號(hào)
獲取協(xié)議棧版本號(hào)的方法如下:

  1. 通過(guò)命令nrfutil pkg generate --help查看
  2. 在線文檔論壇帖子中查看
  3. 燒錄一個(gè)BLE程序,在nrfgo Studio中查看

6.2 使用手機(jī)APP進(jìn)行升級(jí)

利用nRF Connect或nRF Toolbox進(jìn)行升級(jí)







隨后升級(jí)完成

七、調(diào)試

調(diào)試時(shí)候會(huì)頻繁的修改代碼,如果每次都要重新生成和下載一遍bl_settings.hex,會(huì)瘋掉。

我們可以在代碼中禁止DFU服務(wù),讓它直接跳轉(zhuǎn)進(jìn)入Application。

打開(kāi)應(yīng)用工程main.c,注釋掉main.c中的下面幾行代碼:

ble_dfu_buttonless_init_t dfus_init = {0};  
// Initialize DFU.
dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);

芯片上電后,Bootloader完全不理會(huì)DFU Mode,直接進(jìn)入Application。這樣就如同一個(gè)沒(méi)有Bootloader的工程,可以在Application中自由的調(diào)試,也無(wú)需生成bl_settings.hex。


? 由 Leung 寫(xiě)于 2019 年 12 月 28 日

? 參考:青風(fēng)電子社區(qū)
    nRF52832——無(wú)線DFU的原理與步驟(基于SDK15.2)
    Nordic--nrf52832--DFU(二)Sercure DFU bootloader
    NRF52832空中升級(jí)DFU
    nRF52832 BLE_DFU空中升級(jí)OTA(二)編譯下載(SDK14.2.0)
    如何實(shí)現(xiàn)藍(lán)牙空中升級(jí)BLE OTA
    nrf52832 DFU詳細(xì)步驟 SDK13
    BLE DFU:操作演示
    BLE DFU:Application
    BLE DFU:Bootloader
    BLE DFU:生成升級(jí)包
    BLE DFU:Legacy DFU
    BLE DFU:Dual Bank
    解讀Settings文件
    弄懂 Init Packet

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容