如何為nRF52設備實現Buttonless DFU

MotoZe
23 min readNov 26, 2021

開發環境:

OS:macOS Big Sur 11.6

IDE:CLion 2021.2.2

開發板:nRF52840

nRF5_SDK:17.1.0

目標:將ble_app_blinky範例程式加入Buttenless_DFU

摘要:ble_app_blinky沒有Buttenless_DFU;ble_app_buttonless_dfu已經做好Buttenless_DFU了,所以將該專案實現Buttenless_DFU的部分移植到ble_app_blinky中

ble_app_blinky路徑:

nRF5_SDK_17.1.0_ddde560/examples/ble_peripheral/ble_app_blinky

ble_app_buttonless_dfu路徑:nRF5_SDK_17.1.0_ddde560/examples/ble_peripheral/ble_app_buttonless_dfu

步驟:

1.複製ble_app_blinky改叫ble_app_blinky_DFU,並以它為基礎修改

2.打開ble_app_buttonless_dfu將所需的程式移植到ble_app_blinky_DFU中

移植項目:

1 在Makefile設定include函數庫路徑,.c檔案路徑,CFLAGS,ASMFLAGS

3 sdk_config.h的define設定

2 複製dfu所需的函數到main.c

可以把自己的專案放左邊,要抄的內容放右邊比較好操作

2.1 在Makefile設定include函數庫路徑,.c檔案路徑,CFLAGS,ASMFLAGS

2.1.1 新增 ble_dfu 資料夾路徑

$(SDK_ROOT)/components/ble/ble_services/ble_dfu \

2.1.2 新增 ble_dfu.c , ble_dfu_bonded.c , ble_dfu_unbonded.c 路徑

$(SDK_ROOT)/components/ble/ble_services/ble_dfu/ble_dfu.c \
$(SDK_ROOT)/components/ble/ble_services/ble_dfu/ble_dfu_bonded.c \
$(SDK_ROOT)/components/ble/ble_services/ble_dfu/ble_dfu_unbonded.c \

2.1.3 新增 dfu 資料夾路徑

$(SDK_ROOT)/components/libraries/bootloader/dfu \

2.1.4 新增 nrf_dfu_svci.c 路徑

$(SDK_ROOT)/components/libraries/bootloader/dfu/nrf_dfu_svci.c \

2.1.5 新增CFLAGS(從ble_app_buttonless_dfu Makefile抄來)

CFLAGS += -DNRF_DFU_SVCI_ENABLED
CFLAGS += -DNRF_DFU_TRANSPORT_BLE=1

2.1.6 新增ASMFLAGS(從ble_app_buttonless_dfu Makefile抄來)

ASMFLAGS += -DNRF_DFU_SVCI_ENABLED
ASMFLAGS += -DNRF_DFU_TRANSPORT_BLE=1

2.2 sdk_config.h的define設定

2.2.1 BLE_DFU_ENABLED = 1

2.2.2 NRF_SDH_BLE_VS_UUID_COUNT數字+1

本來是10+1就變11

2.2.3 BLE_ADVERTISING_ENABLED = 1

開啟藍芽廣播

2.2.4 開啟RTT,NRF_LOG_BACKEND_RTT_ENABLED = 1

開啟後可以用RTT看到log

2.3 編輯main.c的內容

從ble_app_buttonless_dfu複製buttenless_dfu所需的程式

將ble_app_buttonless_dfu專案main.c第129–282行複製到自己專案前面(include和define之後)

需複製的函數列表:

  1. app_shutdown_handler()

NRF_PWR_MGMT_HANDLER_REGISTER()

2.buttonless_dfu_sdh_state_observer()

NRF_SDH_STATE_OBSERVER()

3.advertising_config_get()

4.disconnect()

5.ble_dfu_evt_handler()

需複製的函數內容:

/**@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);
}
}
// YOUR_JOB: Update this code if you want to do anything given a DFU event (optional).
/**@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);
// 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;
}
}

2.3.1 在services_init()函數加入此函數

dfu_buttonless_init();

services_init()前定義dfu_buttonless_init()函數

static void dfu_buttonless_init(void)
{
uint32_t err_code;
ble_dfu_buttonless_init_t dfus_init = {0};
// Initialize the async SVCI interface to bootloader before any interrupts are enabled.
err_code = ble_dfu_buttonless_async_svci_init();
APP_ERROR_CHECK(err_code);
dfus_init.evt_handler = ble_dfu_evt_handler; err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);
}

3.編譯ble_app_blinky_DFU

如果編譯過程發現main.c缺少include什麼檔案與ble_app_buttonless_dfu比較看要補什麼,補的同時也在Makefile中加入路徑及.c檔案

在按下Makefile中最下面的flash: default就可以編譯了

3.1 經過一番波折發現main.c所需要include以及define的有

#include "ble_dfu.h"
#include "nrf_power.h"
#include "nrf_bootloader_info.h"
#include "ble_advertising.h"
#include "ble_conn_state.h"
BLE_ADVERTISING_DEF(m_advertising); /**< Advertising module instance. */

3.2 經過一番波折發現Makefile所需要補的路徑及.c檔有

#.c檔
#前面已經加過的
$(SDK_ROOT)/components/ble/ble_services/ble_dfu/ble_dfu.c \
$(SDK_ROOT)/components/ble/ble_services/ble_dfu/ble_dfu_bonded.c \
$(SDK_ROOT)/components/ble/ble_services/ble_dfu/ble_dfu_unbonded.c \
$(SDK_ROOT)/components/libraries/bootloader/dfu/nrf_dfu_svci.c \
#後來補的
$(SDK_ROOT)/components/libraries/bootloader/nrf_bootloader_info.c \
$(SDK_ROOT)/components/ble/ble_advertising/ble_advertising.c \
#路徑
#前面已經加過的
$(SDK_ROOT)/components/ble/ble_services/ble_dfu \
$(SDK_ROOT)/components/libraries/bootloader/dfu \
#後來補的
$(SDK_ROOT)/components/libraries/bootloader \
$(SDK_ROOT)/components/ble/ble_advertising \

將以上內容補上後,就可以編譯成功了

4.將編譯完成後將產生的.hex檔與其他所需檔案合併為all.hex

合併項目:

  1. app.hex(自己專案的hex)
  2. bootloader.hex(用dfu範例程式編譯,需要公鑰)
  3. bootloader_setting.hex(用app.hex產生)
  4. softdevice.hex(SDK中已有提供,根據開發版型號選擇)

路徑:nRF5_SDK_17.1.0_ddde560/components/softdevice/開發板型號/hex

4.1 產生bootloader.hex

4.1.1 產生公鑰,私鑰

使用終端機執行以下指令產生private.pemdfu_public_key.c

# Generate a private key and store it in a file named private.pem
nrfutil keys generate private.pem
# Display the public key that corresponds to the generated private key (in code format to be used with DFU)
nrfutil keys display --key pk --format code private.pem
# Write the public key that corresponds to the generated private key to the file public_key.c (in code format)
nrfutil keys display --key pk --format code private.pem --out_file dfu_public_key.c

4.1.2 將dfu_public_key.c放在nRF5_SDK_17.1.0_ddde560/examples/dfu

4.1.3 編譯secure_bootloader專案

根據開發版型號選擇專案

nRF5_SDK_17.1.0_ddde560/examples/dfu/secure_bootloader/pca10056_s140_ble

4.2 產生bootloader setting.hex

用以下指令,輸入app.hex產生bootloader setting.hex

https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_settings_generate_display.html&cp=6_5_6

nrfutil settings generate --family NRF52840 --application app.hex  --application-version 3 --bootloader-version 2 --bl-settings-version 1 bl-settings.hex

4.3 合併hex檔案

使用以下指令將app.hex,bootloader.hex,bootloader_setting.hex,softdevice.hex合併為all.hex

mergehex --merge app.hex bootloader.hex bootloader_setting.hex softdevice.hex --output all.hex

5. 將all.hex刷入開發板中

可以用以下指令:

#清除開發版內的資料
nrfjprog -f nrf52 --eraseall
#刷入all.hex
nrfjprog -f nrf52 --program all.hex --sectorerase
nrfjprog -f nrf52 --reset

或是用nRF Connect → Programmer

5.1 記憶體空間調整

在刷入hex後發現搜尋不到開發板藍牙,使用RTT Viewer查看錯誤訊息

打開RTT Viewer後發現Softdevice分配的RAM不足需要調整記憶體起始位址及空間大小

照RTT Viewer訊息在ble_app_blinky_gcc_nrf52.ld中修改記憶體起始位址及空間大小

修改後重新編譯並刷入開發版就可以搜尋到開發版藍牙,並能夠使用開關燈的功能

6.產生DFU更新檔

將ble_app_blinky_DFU修改後重新編譯

將編譯後的.hex檔+私鑰產生升級.zip檔

可以用以下指令:

update.hex:重新編譯後的專案

private.pem:私鑰(4.1.1有提到)

update.zip:輸出的升級.zip檔

nrfutil pkg generate --application update.hex --application-version-string "1.1.0" --hw-version 52 --sd-req 0x100 --key-file private.pem update.zip

需要特別注意的是:

--sd-req 0x100

0x100是跟著開發板型號以及softdevice版本決定的,可以在SDK資料夾中的文件中查到

.\nRF5_SDK_17.0.2_d674dde\components\softdevice\s132\doc

或是在使用nRF Connect → Programmer刷機時也會顯示

7. 用手機 nRF Connect 連上開發版藍芽,選擇DFU,選擇升級.zip檔並升級

結果:升級後還能再使用DFU升級

參考文獻:

https://blog.csdn.net/m0_37621078/article/details/115541552

https://blog.csdn.net/m0_37621078/article/details/115771780

http://ops9.blogspot.com/2020/08/nordic-nrf52-ota-ble-dfu-part-iii.html

https://www.cnblogs.com/jiangjiu/p/10084222.html

https://devzone.nordicsemi.com/f/nordic-q-a/50269/uploading-application-and-secure-dfu-bootloader-application-not-starting?ReplySortBy=CreatedDate&ReplySortOrder=Ascending

https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_settings_generate_display.html&cp=6_5_6

--

--

MotoZe
0 Followers

不務正業的資工所研究生