BLEマイコンnRF52832覚書

# はじめに
Nordic社のnRF52シリーズを使うことになり、いろいろと調べることが多いのでその覚書として記載していきます。nRF5シリーズではnRF5SDKが用意されており、その中に豊富なサンプルコードがあり、これらを組み合わせることで目的のコードを作っていくのが、早道になります。
自分は組込みソフト開発の経験が浅いので、まずは環境構築やサンプルコードを読み解いて分かったことをつらつらと書いていき、勉強に役立てようと思います。

# ハードウェア
* [nRF52DK](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52-DK/Getting-Started) <br>
![nRF52SDK.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545001/fc1d9fe9-3b98-ed89-7d55-6b6492d193fb.jpeg)

これはNordicの純正品でnRF52832を搭載しています。残念ながら技適未取得のため、BLE部分を使用する場合には電波暗室などで作業をする必要がありますが、単にGPIOやI2Cなどのペリフェラル機能を試す分には、問題ないはずです。また、口述しますが書き込み機としても使えますので、一台あると他のnRF5シリーズへのプログラム書き込みができ、便利です。


* [Adafruit Feather nRF52 Bluefruit](https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide?view=all)
<br>
![bluefruit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545001/9bc8015d-e944-70ae-7ef0-0ba8e8de75b7.jpeg)

これはAdafruit社製の評価ボードです。Raytac社が技適を取得したアンテナがパッケージになったモジュールを搭載しており、電波を出しても問題ありません。
また、Arduino環境で動作することができる点も魅力的ですが、今回はnRFSDKの環境での開発を行います。


　
# 環境構築
Armマイコン向けの開発環境であるSegger Embedded Studio(SES)を使います。
Keilなどいくつか開発環境はあるのですが、SESに関してはNordic社製のモジュールを使っている限りライセンス制限なく使うことができます。(Keilではビルド後のバイナリサイズに制限があります)

## 手順　
基本的には[公式のドキュメント](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpin.html&cp=2_1_0_3&anchor=pin_assign)にすべて記載されています。
今回見るべきは、nRF52 Get Startedです。こちらにSeggerで始める場合の手順が記載されています 
1. Segger Embedded studioをインストールします
2. nRF52SDKをダウンロードします(インストールは必要ありません)
3. SESを起動し、Nordicのデバイスを使っていることを選択し、mailアドレスの登録からライセンスキーが発行されます
4. SDKのディレクトリからsesのprojectを開きます

## まずはLチカする
#### プログラムの場所
C直下にSDKを配置している場合、下記のディレクトリにLチカ用のプログラムが入っています。
```C:\nRF5SDK160098a08e2\examples\peripheral\blinky\pca10040\s132\ses\blinky_pca10040_s132.emProject```
nRFシリーズにはソフトデバイスというものがあり、nRF52832ではs132になります。
別のモジュールを使う場合には、適宜ソフトデバイスを変更してください

#### 書き込み方
PCにUSBケーブルでDKを接続すると、JLINKという名前で認識されるはずです。
この状態でBuild -> Bulid and  Runを押すとビルドと書き込みが実行され、Lチカが始まります。
※画像は、Lチカのサンプルではないですが、すべてのプロジェクトで共通です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545001/5480afef-490a-2ffb-53a6-a1a3bcb18ad4.png)

#### DK以外のボードに書き込みをする場合
他のボードには、nRF52SDK経由で書き込みをすることができます。
DKから対象のデバイスに対してラインをつなぎます。GNDdetectとVTGを繋ぐことでDKは自分用のプログラムではないと判断し、接続されているほかデバイスにプログラムが書き込まれます。
詳しくは下記のリンクで丁寧に説明されているのでこちらをご覧ください

* [nRF52でBLEデバイスを開発する（１）Adafruit FeatherとSEGGER Embeded Studio](https://qiita.com/jiro-aqua/items/63d3c04ba33eac57ebd0)
* [nRF52832をST-LINK v2 + PlatformIOで開発する](https://magazine.halake.com/entry/stlink_pio_nrf52)

【配線早見表】

|||
|---|---|
|DK|ターゲット|
|VTG|VDD|
|GND Detect|GND|
|SWDIO|SWDIO|
|SWDCLK|SWDCLK|

#### [ピンアサインメント](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpin.html&cp=2_1_0_3&anchor=pin_assign)
ピンの割り振りはかなり自由です。GPIOをI2C(Nordicではtwiと呼んでます)やSPIなどに割り当てることができます。回路の取り回しが楽になって良いですね。
ただし、SDKではピンのデフォルト設定があり、それを無効化しないと意図したアサインメントができないという罠がありますので、注意事項として後述します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545001/465dc92b-d8f8-97b1-559a-f2ce214da5bd.png)

##### 注意事項
前述のとおり、いくつかのピンではデフォルトの設定が入っているので、それを無効化する必要があります。下記に具体例を紹介します。

##### P0.06
* UARTのTXとしてデフォルトの設定が入っています。これを解除しないと常にHiになってしまいます(6番でLチカしようとしたけどうまくいかなくて気が付いた)
* sdk_config.h内に設定があるので、これを書き換えましょう！自分は25番にしましたが、何でもいいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545001/f3fc3293-ba52-2f08-d30f-1e29d4038dfe.png)

[★この事象に関連するdev zone](https://devzone.nordicsemi.com/f/nordic-q-a/43894/problem-i-got-problem-on-gpio-pin-in-nrf52810)

#### P0.09、P0.10
* デフォルトではNRFCという短距離通信のアンテナとして設定されています。
* system_nrf52.cの196行目です。デフォルトだとCONFIG_NFCT_PINS_AS_GPIOSが定義されていません。
* system_nrf52.hでCONFIG_NFCT_PINS_AS_GPIOSを定義しましょう

```system_nrf52.c
    /* Configure NFCT pins as GPIOs if NFCT is not to be used in your code. If CONFIG_NFCT_PINS_AS_GPIOS is not defined,
       two GPIOs (see Product Specification to see which ones) will be reserved for NFC and will not be available as
       normal GPIOs. */
    #if defined (CONFIG_NFCT_PINS_AS_GPIOS)
        if ((NRF_UICR->NFCPINS & UICR_NFCPINS_PROTECT_Msk) == (UICR_NFCPINS_PROTECT_NFC << UICR_NFCPINS_PROTECT_Pos)){
            NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos;
            while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}
            NRF_UICR->NFCPINS &= ~UICR_NFCPINS_PROTECT_Msk;
            while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}
            NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos;
            while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}
            NVIC_SystemReset();
        }
    #endif
```

```system_nrf52.h
#define CONFIG_NFCT_PINS_AS_GPIOS
```
* もしくは、レジスタから直接設定する方法もあります。main.c下記のコマンドを加えましょう。

``` main.c
    NRF_UICR->NFCPINS = 0xFFFFFFFE;
````
* DKを使っている場合は、さらなる壁があります。P0.09,P0.10はDK上で実はピンソケットに接続されていません。こちらをはんだ付けしましょう。R25,R26を外し、R27,R28をショートします。[詳しくはこちら](https://devzone.nordicsemi.com/f/nordic-q-a/29594/p0-09-and-p0-10-on-nrf52810)
* 以上さらっと書きましたが、丸一日はまってしまいました。特にDKのはんだ付けの罠...　関連リンクを乱雑ですが貼っておきます。

##### ピンアサインメント関連リンク集
* https://devzone.nordicsemi.com/f/nordic-q-a/29041/how-to-enable-p0-09-and-p0-10-as-gpio-pins-on-the-nrf52-instead-of-nfc-pins
* https://devzone.nordicsemi.com/f/nordic-q-a/29594/p0-09-and-p0-10-on-nrf52810
* https://datasheet.octopart.com/NRF52-DK-Nordic-Semiconductor-datasheet-62297784.pdf#%5B%7B%22num%22%3A328%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C56.692%2C752.879%2Cnull%5D
* https://datasheet.octopart.com/NRF52-DK-Nordic-Semiconductor-datasheet-62297784.pdf#%5B%7B%22num%22%3A3774%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C56.692%2C756.429%2Cnull%5D
* https://datasheet.octopart.com/NRF52-DK-Nordic-Semiconductor-datasheet-62297784.pdf


# そのほかのサンプル
ここから先は自分の書きたいコードを構築するにあたって必要なサンプルを見ていきます。

### NRF_LOG
nRFでログを吐く方法として、UART, NRF_LOG, NRF_SEGGER_RTTがあります。
UARTは重いので後者の2つを使うことが推奨されています。
NRF_LOGはどのサンプルでも最初にライブラリがインクルードされているので、これをUARTに経由してターミナルに表示させることでデバッグに役立ちます。

#### sdk_config.hの編集<br>
このファイルの中に、SDKの様々な機能をenableにするか、disableにするかが記載されています
なので、こちらからNRF_LOGを有効にしてやればよいだけなのですが、ここで非常にはまりました。
自分はdev_zoneなどで言われている、sdk_config.hを直接編集するやり方を何度試してもうまくいきませんでした。ネットを漁ったところ同様の状況に陥っている方も他にいたようです。<br>
結局のところ、[CMSIS Configuration Wizard](https://sourceforge.net/projects/cmsisconfig/)をインストールし、ここからsdk_config.hを編集することで解決しました。
下記の画面で、NRF_LOG_BACKEND_UART_ENABLEDとNRF_LOG_ENABLEDにチェックを付けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545001/b1cd0a5d-1397-6655-3048-f8dbca9fc2eb.png)



#### ターミナルを開く<br>
Tools -> Terminal Emulator -> Connect COMXX から接続することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545001/77fee429-96fd-1565-78fa-278532529b0b.png)



#### 注意点<br>
下記のNRF_LOG_USES_TIMESTAMPにはチェックを入れないでください。
ここにチェックを入れることで、ログだけでなくすべてのプログラムが動作しないという現象が起こりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545001/b649b7a6-0c3f-cb03-6656-83e323cb2cfa.png)


### SAADC
アナログ値の読み出しのためのインターフェースです。
config内に何ビット分解能にするかの設定値があります。
値の読み取りはサンプルプログラムそのままですが、VCCと直結させて読むとVCCの値よりもやや小さい値が返ってくる？ので、現在調査中です。
VCCとアナログを直結して確かめたので、電流が流れて全体の電圧がドロップしているのかもしれません。

### ビーコン
sample beaconのプログラムを改変していきます。
sd_ble_gap_adv_set_configure()でビーコンで送る内容を規定しています。パケットの内容に更新が必要な時もこの関数を呼び出せばよいです。
しかし、これをそのままメイン関数内で呼び出ししたところ、APP_ERROR_CHECKでfatal errorが発生してしまい、マイコンがリセットする現象に見舞われてしまいました。
ではどうするかというと、sd_ble_gap_adv_stop（）を呼び出して一回アドバタイズにストップをかけます。
すなわち、パケット改変の順序としては、以下の順序を踏むことになります。(ここでも3日はまりました。)devzoneでは、sd_ble_gap_adv_set_configure()を呼び出せというのはすぐ出てくるのですがsd_ble_gap_adv_stop()を呼ばないといけないという記事には自分は当たりませんでした。

* sd_ble_gap_adv_stop（）
* sd_ble_gap_adv_set_configure()
* advertising_start(void)

### ビーコンにセンサデータを乗っける
beaconのプログラムにtwiのプログラムを移植していき、twiで読んだデータを上記のパケット改変で送信データに乗せます。
基本はapp_beaconのプロジェクトに対してtwiのサンプルからの必要な部分をコピペしてくるスタンスでよいのですが、インスタンスをenableにし忘れるとコンパイルエラーが出ます。
対処法は、twi_instance 0を使う場合は、sdk_config.hを下記のように編集することです。

```sdk_config.h
// <e> TWI0_ENABLED - Enable TWI0 instance
//==========================================================
#ifndef TWI0_ENABLED
#define TWI0_ENABLED 1
#endif
```

上記でコンパイルは通りますが、リンクで失敗しました。
nrf_drv_twi_tx, nrf_drv_twi_rxが定義されていないというエラーです。
dev_zoneを見に行くと、上記のENABLEを有効にしろという記述ばかりが引っかかりますが、今回の問題はそこではなく、単純にプロジェクトに上記の関数を定義しているファイルを配置しろということのようです。下記のファイルをnRF_Driversのディレクトリに配置しましょう。地味ですが、コードを統合していく際にはこういう問題に結構ひっかかりますね。

* nrfx_twi.c
* nrfx_twim.c
* nrfx_drv_twi.c

具体的には、nRF_Driversのフォルダを右クリックし、add existing filesから配置することができます。
上記のファイルは、twiのサンプルコードのnRF_Driversには元々入っているので、右クリックからCopy Full Pathすればどこにあるかすぐに見つけられて便利です。
[ファイルの追加方法はこちらの動画から](https://www.youtube.com/watch?v=t-kh1EbesvI)

#### 消費電力に関する注意
twiインスタンスを作成し、レジスタにアクセスする関数を呼び出すと消費電力が跳ね上がりました。常時300μA程度食います。
twiのイニシャライズだけでは起こりませんが、twi通信の関数を呼び出した瞬間にそうなります。
回避するには、都度twiをdisable、enableにするしかないです。
公式のエラッタにも記述がありますが、[こちらのdevzone](https://devzone.nordicsemi.com/f/nordic-q-a/15908/nrf52-twi-excess-current-consumption?ReplySortBy=CreatedDate&ReplySortOrder=Ascending)が参考なりましたので参照してください。


#### DCDCを使って省電力化する
[nRF52832 Product Specificationv1.4](https://infocenter.nordicsemi.com/pdf/nRF52832_PS_v1.4.pdf#%5B%7B%22num%22%3A846%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C56.692%2C765.354%2Cnull%5D)の78ページにLDOとDCDCについての記述があります。DCDCを使用したほうが省電力にできるようですが、LCフィルターのハードウェアを追加したときのみ可能です。
enableにするには、main.cで下記を追加します。

```main.c
    // enable_dcdc
    NRF_POWER->DCDCEN = 1;
```

#### PPIとは
今回自分が使う予定がないので箇条書きですが、PPIという便利機能があるので書き残します。
* Programmable Peripheral Interconnectのこと。

```The Programmable peripheral interconnect (PPI) enables peripherals to interact autonomously with each other using tasks and events independent of the CPU. The PPI allows precise synchronization between peripherals when real-time application constraints exist and eliminates the need for CPU activity to implement behavior which can be predefined using PPI.```

* その名の通り、あるペリフェラルの変化がトリガーとなり、他のペリフェラルのイベントが駆動されます
* 例えば、あるピンが変化したらLEDがついたり、消えたりする機能が実装できるものです。
* CPUとは独立に制御するためnon-blockingになるのが利点と思われます。i2cのDMAのペリフェラルバージョンのような理解ですかね。?
* 1つのイベントで複数のタスクをドリブンできるとのこと
* [PPI公式リンク](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fppi.html)
* [タスクとイベントの対応について](https://devzone.nordicsemi.com/f/nordic-q-a/4258/question-about-ppi)

#### GPIOによるイベント生成
* GPIOTEというモジュールを使用すると思われる
* サンプルのGPIOTEは、PPIを使っているので、CPUへの割込みという意味では分かりにくいが,GPIOTEとPPIは必ずしも共存しなくてもよい（下記のgithubを参照）
* GPIOTE周りのドキュメントを引き続き調査
* [公式リファレンス](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpin.html&cp=2_1_0_3&anchor=pin_assign)
* https://devzone.nordicsemi.com/f/nordic-q-a/19882/configuration-of-gpio-to-wake-up-from-system-off
* https://github.com/t2t-sonbui/nRF52832-app_button-timer-ppi-gpiote-log/blob/master/main.c
* APIリファレンスの場所がわかりにくい : 参照 
Software Development Kit > nRF5 SDK v16.0.0 > API Reference > Peripheral drivers > Peripheral drivers > GPIOTE

#### ペリフェラルとセントラル

* [役に立ちそうなチュートリアル](https://devzone.nordicsemi.com/nordic/short-range-guides/b/bluetooth-low-energy/posts/ble-central-tutorial)
* [Adafruit guide on GAP](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gap)
* [Adafruit guide on GATT](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gatt)

[参考リンク]

* [biginner向け公式チュートリアル](https://devzone.nordicsemi.com/nordic/short-range-guides/b/bluetooth-low-energy/posts/ble-advertising-a-beginners-tutorial)
* [ボタンプッシュでbleパケットの中身を変えるプログラム](https://devzone.nordicsemi.com/f/nordic-q-a/43464/modify-ble-advertising-tutorial-to-change-advertising-data-on-button-press)→ sd_ble_gap_adv_set_configure()を呼び出す前にble_advdata_encode()を呼び出さなければならないとのこと
* [【dev_zone】idle_state_handle()内のnrf_pwr_mgmt_run()について](https://devzone.nordicsemi.com/f/nordic-q-a/39356/when-should-you-use-the-nrf_pwr_mgmt-module) → イベントが発生するまでCPUをスリープモードに入れているとのこと。WFI(wait for interruption)とか、他にも同じ機能を持つモジュールはあるが、これ使っておけばいい？
* [nRF52 でSleepからGPIOで復帰](https://qiita.com/nanbuwks/items/36c32989543399f3aa62)
* [電池消費に関する公式記事](https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs)