Nordic nRF52840 の 32k クロックを内蔵 RC で使う

Nordic の nRF52840 は クロックが2つ必要で、メインクロックと32.768kHzの低速クロック。

低速クロックはカレンダー以外にも通信のタイミングなどに使っているらしい。低速クロックの精度が狂うと通信に影響がある。特に Bluetooth では 250ppm 以内に精度を押さえないといけないらしい。

低速クロックは外部クリスタルをつなげるようになっていて(Low frequency crystal oscillator : LFXO)、SDKでもそれ前提の設定となっている。

しかしながら外部クリスタルを省略して、内部RC発振器やメインクロックを分周して32kクロックを賄うこともできる。

# 外部クリスタル LFXO との違い

ではそれらを使った場合はどのような違いがあるか？

## メインクロックを分周（LFSYNT)する場合
https://devzone.nordicsemi.com/f/nordic-q-a/35989/clock-source-on-ble_app_uart
によると、LFSYNTは消費電力の点で不利。

## 内部RC発振器(Low frequency RC oscillator : LFRC )を使った場合：

### 内部RC発振器駆動のための消費電流増加

nRF52840_PS_v1.0.pdf の
> 5.4.4.3 Low frequency crystal oscillator (LFXO)
5.4.4.4 Low frequency RC oscillator (LFRC), Normal mode

によると、

LFXO 0.23uA
LFRC 0.7uA

となっている。

### 精度低下のための消費電流増加

また精度低下のために、後述のキャリブレーションおよびBluetoothリスニング時間の増大(Window widening)が必要となる。それぞれ更に消費電力が拡大する。

cf., nRF52832ではあるが実測値を含む具体的なデータ https://devzone.nordicsemi.com/f/nordic-q-a/14214/how-much-current-consumption-when-we-use-internal-32k-rc

### 通信プロトコルの制限

ANTでは精度の関係で内蔵RCは使えないらしい(精度 50ppm が必要)。

# 内蔵 RC の使用

では、内蔵 RC を使う設定にするにはどうしたらいいか？ SoftDevice を使う場合と使わない場合で方法は異なる。ここでは、SoftDeviceを使う場合について。

## 必要なキャリブレーション間隔

RC 発振器は精度が2%と大変悪い。
BLE stack を使う場合、250ppm 以内に収める必要があるので一定時間ごとにキャリブレーションを行って精度を保つ。8秒ごとのキャリブレーション、および0.5℃の変化時には4秒毎のキャリブレーションが推奨されている。

cf., https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.s132.api.v7.0.1/structnrf__clock__lf__cfg__t.html


## sdk_config.h の設定

元々の LFXC を使う設定は nRF_SoftDevice - NRF_SDH_ENABLED - clock の項目において下記のようになっている。

```

//==========================================================

// <h> Clock - SoftDevice clock configuration

//==========================================================
// <o> NRF_SDH_CLOCK_LF_SRC  - SoftDevice clock source.

// <0=> NRF_CLOCK_LF_SRC_RC 
// <1=> NRF_CLOCK_LF_SRC_XTAL 
// <2=> NRF_CLOCK_LF_SRC_SYNTH 

#ifndef NRF_SDH_CLOCK_LF_SRC
#define NRF_SDH_CLOCK_LF_SRC 1
#endif

// <o> NRF_SDH_CLOCK_LF_RC_CTIV - SoftDevice calibration timer interval. 
#ifndef NRF_SDH_CLOCK_LF_RC_CTIV
#define NRF_SDH_CLOCK_LF_RC_CTIV 0
#endif

// <o> NRF_SDH_CLOCK_LF_RC_TEMP_CTIV - SoftDevice calibration timer interval under constant temperature. 
// <i> How often (in number of calibration intervals) the RC oscillator shall be calibrated
// <i>  if the temperature has not changed.

#ifndef NRF_SDH_CLOCK_LF_RC_TEMP_CTIV
#define NRF_SDH_CLOCK_LF_RC_TEMP_CTIV 0
#endif

// <o> NRF_SDH_CLOCK_LF_ACCURACY  - External clock accuracy used in the LL to compute timing.

// <0=> NRF_CLOCK_LF_ACCURACY_250_PPM 
// <1=> NRF_CLOCK_LF_ACCURACY_500_PPM 
// <2=> NRF_CLOCK_LF_ACCURACY_150_PPM 
// <3=> NRF_CLOCK_LF_ACCURACY_100_PPM 
// <4=> NRF_CLOCK_LF_ACCURACY_75_PPM 
// <5=> NRF_CLOCK_LF_ACCURACY_50_PPM 
// <6=> NRF_CLOCK_LF_ACCURACY_30_PPM 
// <7=> NRF_CLOCK_LF_ACCURACY_20_PPM 
// <8=> NRF_CLOCK_LF_ACCURACY_10_PPM 
// <9=> NRF_CLOCK_LF_ACCURACY_5_PPM 
// <10=> NRF_CLOCK_LF_ACCURACY_2_PPM 
// <11=> NRF_CLOCK_LF_ACCURACY_1_PPM 

#ifndef NRF_SDH_CLOCK_LF_ACCURACY
#define NRF_SDH_CLOCK_LF_ACCURACY 7
#endif

```

このうち、有効なディレクティブは以下の4つ。

```

#define NRF_SDH_CLOCK_LF_SRC 1
#define NRF_SDH_CLOCK_LF_RC_CTIV 0
#define NRF_SDH_CLOCK_LF_RC_TEMP_CTIV 0
#define NRF_SDH_CLOCK_LF_ACCURACY 7
```
これらを以下のように変更

#### NRF_SDH_CLOCK_LF_SRC

SoftDevice clock source.

```

// <0=> NRF_CLOCK_LF_SRC_RC 
```

ということなので 0 を指定。

#### NRF_SDH_CLOCK_LF_RC_CTIV

SoftDevice calibration timer interval. 

ここに設定する値は rc_ctiv に設定する値と同様に 1/4 秒単位だと思うけど本当かな？
ドキュメント
https://infocenter.nordicsemi.com/topic/sdk_nrf5_v16.0.0/group__nrf__sdh__config.html

は、

> Note
This is an NRF_CONFIG macro.

とあるだけで役に立たない。NRF_CONFIG macroって何だ？

調べてみたら NRF_CONFIG macro についてはわからなかったが  NRF_SDH_CLOCK_LF_RC_CTIV は SDK 中の components/softdevice/common/nrf_sdh.c 中で、以下のように使われていた。

```

    nrf_clock_lf_cfg_t const clock_lf_cfg =
    {
        .source       = NRF_SDH_CLOCK_LF_SRC,
        .rc_ctiv      = NRF_SDH_CLOCK_LF_RC_CTIV,
        .rc_temp_ctiv = NRF_SDH_CLOCK_LF_RC_TEMP_CTIV,
        .accuracy     = NRF_SDH_CLOCK_LF_ACCURACY
    };
```

nrf_clock_lf_cfg_t 構造体は先に示した https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.s132.api.v7.0.1/structnrf__clock__lf__cfg__t.html
で定義されていて、rc_ctivは 1/4 秒単位。

ここでは下記の NRF_SDH_CLOCK_LF_RC_TEMP_CTIV と合わせてタイマー間隔を決定する。今回はここで温度変化が0.5℃以上ある場合の間隔を指定し、推奨値を満たすために16を指定する。これでタイマー間隔は4秒間隔になる。

#### NRF_SDH_CLOCK_LF_RC_TEMP_CTIV

 SoftDevice calibration timer interval under constant temperature. 
 How often (in number of calibration intervals) the RC oscillator shall be calibrated if the temperature has not changed.

温度が変更されていない場合のキャリブレーションの頻度。単位は先の先にタイマー間隔を4秒にした。温度が変更されていない場合のキャリブレーション間隔の推奨値は8秒。ここで2を指定することでタイマー2回毎にキャリブレーションが行われる。

#### NRF_SDH_CLOCK_LF_ACCURACY

External clock accuracy used in the LL to compute timing.

 Window widening などに関連するのではないかな？ RC 発振器でキャリブレート後の精度 250ppm として 0 を指定するのが良さそうですね・・・

### 設定値

結局、設定値はこうなります。

```

#define NRF_SDH_CLOCK_LF_SRC 0
#define NRF_SDH_CLOCK_LF_RC_CTIV 16
#define NRF_SDH_CLOCK_LF_RC_TEMP_CTIV 2
#define NRF_SDH_CLOCK_LF_ACCURACY 0
```

これらを反映した SDK_config.h の Clock セクションは下記の通り。

```

//==========================================================

// <h> Clock - SoftDevice clock configuration

//==========================================================
// <o> NRF_SDH_CLOCK_LF_SRC  - SoftDevice clock source.

// <0=> NRF_CLOCK_LF_SRC_RC 
// <1=> NRF_CLOCK_LF_SRC_XTAL 
// <2=> NRF_CLOCK_LF_SRC_SYNTH 

#ifndef NRF_SDH_CLOCK_LF_SRC
#define NRF_SDH_CLOCK_LF_SRC 1
#endif

// <o> NRF_SDH_CLOCK_LF_RC_CTIV - SoftDevice calibration timer interval. 
#ifndef NRF_SDH_CLOCK_LF_RC_CTIV
#define NRF_SDH_CLOCK_LF_RC_CTIV 0
#endif

// <o> NRF_SDH_CLOCK_LF_RC_TEMP_CTIV - SoftDevice calibration timer interval under constant temperature. 
// <i> How often (in number of calibration intervals) the RC oscillator shall be calibrated
// <i>  if the temperature has not changed.

#ifndef NRF_SDH_CLOCK_LF_RC_TEMP_CTIV
#define NRF_SDH_CLOCK_LF_RC_TEMP_CTIV 0
#endif

// <o> NRF_SDH_CLOCK_LF_ACCURACY  - External clock accuracy used in the LL to compute timing.

// <0=> NRF_CLOCK_LF_ACCURACY_250_PPM 
// <1=> NRF_CLOCK_LF_ACCURACY_500_PPM 
// <2=> NRF_CLOCK_LF_ACCURACY_150_PPM 
// <3=> NRF_CLOCK_LF_ACCURACY_100_PPM 
// <4=> NRF_CLOCK_LF_ACCURACY_75_PPM 
// <5=> NRF_CLOCK_LF_ACCURACY_50_PPM 
// <6=> NRF_CLOCK_LF_ACCURACY_30_PPM 
// <7=> NRF_CLOCK_LF_ACCURACY_20_PPM 
// <8=> NRF_CLOCK_LF_ACCURACY_10_PPM 
// <9=> NRF_CLOCK_LF_ACCURACY_5_PPM 
// <10=> NRF_CLOCK_LF_ACCURACY_2_PPM 
// <11=> NRF_CLOCK_LF_ACCURACY_1_PPM 

#ifndef NRF_SDH_CLOCK_LF_ACCURACY
#define NRF_SDH_CLOCK_LF_ACCURACY 7
#endif

```