概要
NordicのBLEモジュールnRF52のファームウェアを、アプリを使用して無線で更新(OTA)します。
前回は1回更新すると、2回目の更新ができなくなる問題があったので、繰り返し更新できるサンプルプログラムを活用します
(追記)DFUサービス追加方法
既存のアプリケーションにDFUサービスを追加する手順を紹介したサイトがありました。
Keilのプロジェクトを対象にしていますが、SESにも問題なく適用できます。
https://www.codetd.com/ja/article/10955888
(2021/6/25 上記サイトが消えていたので、手順を以下に記します。)
1. ファイルの追加
以下のファイルをプロジェクトに追加します
- components\ble\ble_services\ble_dfu\ble_dfu.c
- components\ble\ble_services\ble_dfu\ble_dfu_bonded.c
- components\ble\ble_services\ble_dfu\ble_dfu_unbonded.c
- components\libraries\bootloader\dfu\nrf_dfu_svci.c
2. インクルードファイルの設定追加
以下のPathをビルドのインクルードファイル設定に追加します
- Libraries\components\libraries\svc
- Libraries\components\libraries\bootloader
- Libraries\components\libraries\bootloader\dfu
- Libraries\components\libraries\bootloader\ble_dfu
- Libraries\components\ble\ble_services\ble_dfu
- Libraries\components\softdevice\mbr\nrf52832\headers
3. ビルドDefine設定追加
- BLE_SETTINGS_ACCESS_ONLY
- NRF_DFU_SVCI_ENABLED
- NRF_DFU_TRANSPORT_BLE = 1
4. sdk_define.hの設定
# ifndef BLE_DFU_ENABLED
# define BLE_DFU_ENABLED 1
# endif
# ifndef NRF_SDH_BLE_VS_UUID_COUNT
# define NRF_SDH_BLE_VS_UUID_COUNT 2
# endif
5. メモリ
分かりにくいのは、UUIDを増やした分のRAMサイズ修正でした。
例えば、UUIDを1つ増やしたのに対応して、RAMサイズを0x20だけ増やす時、RAM STARTを0x20002270から0x20002290にします。
6. コードの修正
- ヘッダーファイルの追加
# include "nrf_power.h"
# include "nrf_bootloader_info.h"
# include "nrf_dfu_ble.h"
# include "ble_dfu.h"
# include "nrf_dfu_ble_svci_bond_sharing.h"
# include "nrf_svci_async_function.h"
# include "nrf_svci_async_handler.h"
- 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);
// 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 Function for initializing services that will be used by the application.
*/
static void services_init(void)
{
ret_code_t err_code;
ble_smartlock_init_t smartlock_init;
nrf_ble_qwr_init_t qwr_init = { 0 };
ble_dfu_buttonless_init_t dfus_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 defined(CHARI_WITHOUT_DFU)
# else
// Initialize DFU.
dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);
# endif
// Initialize the Test Service.
memset(&smartlock_init, 0, sizeof(smartlock_init));
smartlock_init.smartlock_write_handler = smartlock_write_handler;
err_code = ble_smartlock_init(&m_smartlock, &smartlock_init);
APP_ERROR_CHECK(err_code);
}
int main(void)
{
ret_code_t err_code;
// Initialize.
# if defined(CHARI_WITHOUT_DFU)
# else
// 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);
# endif
cpu_init();
7. ビルド
必要なzipファイルを生成するスクリプトは、以下のGitHubで公開されているサンプルプログラムが参考になります。
プロジェクトは、そのまま使用できるので、DFUを実際に試すことができます。
環境
- OS
- Windows 10
- ソフト:
- Cygwin
- Anaconda3
- SES
- SDK
- 16.0.0
- Softdevice
- s140_nrf52_7.0.1
- BLEモジュール
- nRF52840
サンプルプログラムのビルド
手順はは前回と同じです。
今回は使用するサンプルプログラムだけが異なります。
SESにおいて作業します。
nRF52840に対応したサンプルプログラムを開きます。
nRF5SDK160098a08e2\examples\ble_peripheral\ble_app_buttonless_dfu\pca10056\s140\ses\ble_app_buttonless_dfu_pca10056_s140.emProject
ビルドに成功すると
nRF5SDK160098a08e2\examples\ble_peripheral\ble_app_buttonless_dfu\pca10056\s140\ses\Output\Release\Exeにble_app_buttonless_dfu_pca10056_s140.hexが生成されます。
前回との違いはこれだけです。
次は、前回と同じようにzipファイルを生成します。
ただし、今回のサンプルプログラムにNRF_DFU_HW_VERSIONに相当するものはないので、--hw-versionは任意の値で問題ありません。
nrfutil pkg generate --hw-version 1 --application-version 1 --application ble_app_buttonless_dfu_pca10056_s140.hex --sd-req 0xCA --key-file ./private.key ble_app_buttonless_dfu_pca10056_s140.zip
iPhoneでDFUを実行すると、前回と同じようにDfuTargを選択し、生成したble_app_buttonless_dfu_pca10056_s140.zipで更新します。
前回と異なるのは、再度DFUを実行してファームウェア更新可能なことです。
今回のサンプルプログラムにはセキュアブート機能が実装されているので、何回でも更新できます。
(もちろん、セキュアブート機能のないファームウェアを更新すれば、それ以降は更新できなくなります。)
次にすること
簡単な紹介になりましたが、これで前回の最大の問題点、繰り返し更新できない、は解決しました。
このサンプルプログラムを元に修正すれば、オリジナルのBLEサービスを実装したり、ハードウェアバージョンの追加もできるはずです。
ですが、私のしたいことはサーバを介したファームウェア更新なので、このサンプルプログラムの解析は行いません。
サーバを介したファームウェア更新をするために、次回はBackground DFUについて調べます。
(追記)
既存のBLEアプリケーションにDFU機能を追加する方法を解説した記事を見つけました。
また、以下の記事とGithubもDFU機能を追加する際の参考になります。
nRF52でBLEデバイスを開発する(2)OTAアップデート Buttonless DFUとSecure DFU
(追記)
DFUとアプリケーションのUUIDが同じになってしまう現象が確認できました。
これは不便なので、以下のようにアプリケーションUUIDを変更するとDFUのUUIDとアプリケーションのUUIDが区別できます。
変更前
static void advertising_init(void)
{
ret_code_t err_code;
ble_advertising_init_t init;
ble_uuid_t m_adv_uuids[] =
/**< Universally unique service identifiers. */
{
{APPLICATION_UUID_SERVICE, BLE_UUID_TYPE_VENDOR_BEGIN }
};
変更後
static void advertising_init(void)
{
ret_code_t err_code;
ble_advertising_init_t init;
ble_uuid_t m_adv_uuids[] =
/**< Universally unique service identifiers. */
{
{APPLICATION_UUID_SERVICE, BLE_UUID_TYPE_VENDOR_BEGIN+1 }
};
(追記)sdk_configの設定
ボタンレスDFUモードを使用する際はsdk_config.hの以下の設定を変更しないといけません。
# define NRF_BL_DFU_ENTER_METHOD_BUTTON 0
# define NRF_BL_DFU_ENTER_METHOD_BUTTONLESS 1