nRF52のDFU試験2

# 概要
NordicのBLEモジュールnRF52のファームウェアを、アプリを使用して無線で更新（OTA）します。

[前回](https://qiita.com/Kosuke_Matsui/items/7ab5235b4cfa61a398ba)は1回更新すると、2回目の更新ができなくなる問題があったので、繰り返し更新できるサンプルプログラムを活用します


# (追記）DFUサービス追加方法
既存のアプリケーションにDFUサービスを追加する手順を紹介したサイトがありました。
Keilのプロジェクトを対象にしていますが、SESにも問題なく適用できます。
https://www.codetd.com/ja/article/10955888

（2021/6/25　上記サイトが消えていたので、手順を以下に記します。）

１. ファイルの追加
以下のファイルをプロジェクトに追加します

- 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

２. インクルードファイルの設定追加
以下の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

３. ビルドDefine設定追加

- BLE_SETTINGS_ACCESS_ONLY 
- NRF_DFU_SVCI_ENABLED 
- NRF_DFU_TRANSPORT_BLE = 1

４. sdk_define.hの設定

```C
#ifndef BLE_DFU_ENABLED
#define BLE_DFU_ENABLED 1
#endif
```

```C
#ifndef NRF_SDH_BLE_VS_UUID_COUNT 
#define NRF_SDH_BLE_VS_UUID_COUNT 2
#endif
```

５. メモリ

分かりにくいのは、UUIDを増やした分のRAMサイズ修正でした。
例えば、UUIDを1つ増やしたのに対応して、RAMサイズを0x20だけ増やす時、RAM STARTを0x20002270から0x20002290にします。

６. コードの修正

- ヘッダーファイルの追加

```C
#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

```C
/**@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);
}
```

```C
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();
```

７. ビルド

必要なzipファイルを生成するスクリプトは、以下のGitHubで公開されているサンプルプログラムが参考になります。
プロジェクトは、そのまま使用できるので、DFUを実際に試すことができます。

- [nRF52でBLEデバイスを開発する（２）OTAアップデート Buttonless DFUとSecure DFU](https://qiita.com/jiro-aqua/items/97c8ec6910d584281b7a)
    - https://github.com/jiro-aqua/nrf52-ota-example

# 環境
- OS
    - Windows 10
- ソフト：
    - Cygwin
    - Anaconda3
    - SES
- SDK
    - 16.0.0
- Softdevice
    - s140_nrf52_7.0.1
- BLEモジュール
    - nRF52840



# サンプルプログラムのビルド

手順はは[前回](https://qiita.com/Kosuke_Matsui/items/7ab5235b4cfa61a398ba)と同じです。
今回は使用するサンプルプログラムだけが異なります。

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機能を追加する方法を解説した記事を見つけました。

[nrf52832によって開発されたDFUサービスを追加](https://www.codetd.com/ja/article/10955888)

また、以下の記事とGithubもDFU機能を追加する際の参考になります。
[nRF52でBLEデバイスを開発する（２）OTAアップデート Buttonless DFUとSecure DFU](https://qiita.com/jiro-aqua/items/97c8ec6910d584281b7a)

https://github.com/jiro-aqua/nrf52-ota-example

(追記）
DFUとアプリケーションのUUIDが同じになってしまう現象が確認できました。
これは不便なので、以下のようにアプリケーションUUIDを変更するとDFUのUUIDとアプリケーションのUUIDが区別できます。

変更前

```c
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  }
    };
```

変更後

```c
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
```