Nordic社製BLEのnRF5 SDK v16.0.0におけるPWM driver設定方法

# 記事の概要
Nordic社製BLEチップnRF52シリーズにおいては、PWM機能を使用する際は極性反転の脆弱性のないPWM driverの使用が推奨されています。
https://devzone.nordicsemi.com/f/nordic-q-a/60261/nrf52-pwm-library-or-driver/245040

本記事は、nRF5 SDK v16.0.0におけるPWM driverの設定方法について解説します。

本記事は、SDKインストール時に含まれる以下のサンプルプロジェクトを参照しています。
https://infocenter.nordicsemi.com/topic/sdk_nrf5_v16.0.0/pwm_hw_example.html

# 共通設定

```c
#include <stdio.h>
#include <string.h>
#include "nrf_drv_pwm.h"
#include "app_util_platform.h"
#include "app_error.h"
#include "boards.h"
#include "bsp.h"
#include "app_timer.h"
#include "nrf_drv_clock.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

static nrf_drv_pwm_t m_pwm0 = NRF_DRV_PWM_INSTANCE(0);
static nrf_drv_pwm_t m_pwm1 = NRF_DRV_PWM_INSTANCE(1);
static nrf_drv_pwm_t m_pwm2 = NRF_DRV_PWM_INSTANCE(2);

#define PWM_PIN0  (13)
#define PWM_PIN1  (14)
#define PWM_PIN2  (15)
#define PWM_PIN3  (16)

static void pwm_sample(void)
{
//以下で紹介する各コード
}

int main(void)
{
    pwm_sample();

    for (;;)
    {
    }
}
```

また、sdk_config.hにおいてPWMモジュールが有効になっているかをご確認ください。

```c:sdk_config.h
#ifndef NRFX_PWM_ENABLED
#define NRFX_PWM_ENABLED 1
#endif
```

```c:sdk_config.h
// <q> PWM0_ENABLED  - Enable PWM0 instance
 

#ifndef PWM0_ENABLED
#define PWM0_ENABLED 1
#endif

// <q> PWM1_ENABLED  - Enable PWM1 instance
 

#ifndef PWM1_ENABLED
#define PWM1_ENABLED 1
#endif
```

# Duty比2:8の100msec周期パルスの生成

```c
static void pwm_sample(void)
{
    nrf_drv_pwm_config_t const config0 =
    {
        .output_pins =
        {
            PWM_PIN0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
            PWM_PIN1 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
            NRF_DRV_PWM_PIN_NOT_USED, // channel 2
            NRF_DRV_PWM_PIN_NOT_USED  // channel 3
        },
        .irq_priority = APP_IRQ_PRIORITY_LOWEST,
        .base_clock   = NRF_PWM_CLK_125kHz, //1cyc 8us
        .count_mode   = NRF_PWM_MODE_UP,
        .top_value    = 12500, //100ms period
        .load_mode    = NRF_PWM_LOAD_COMMON,
        .step_mode    = NRF_PWM_STEP_AUTO
    };
    APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));

    static uint16_t seq_values[] =
    {
        2500 //20msec
    };

    nrf_pwm_sequence_t const seq =
    {
        .values.p_common = seq_values,
        .length          = NRF_PWM_VALUES_LENGTH(seq_values),
        .repeats         = 0,
        .end_delay       = 4
    };

    (void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1, NRF_DRV_PWM_FLAG_LOOP);
}
```
M:50msecのオシロスコープ波形データです。
上がch0、下がch1です。
duty比2:8の100msec周期パルスになっています。

![PWM_sample1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/b06e09f8-6836-bef6-a386-81be713af385.jpeg)


## PWMインスタンス作成
nRF52480の場合、PWMモジュールはPWM0、PWM1、PWM2の3つがあります。
使用するPWMモジュールのインスタンスを作成します。

```c
static nrf_drv_pwm_t m_pwm0 = NRF_DRV_PWM_INSTANCE(0);
static nrf_drv_pwm_t m_pwm1 = NRF_DRV_PWM_INSTANCE(1);
static nrf_drv_pwm_t m_pwm2 = NRF_DRV_PWM_INSTANCE(2);
```

## PWM出力するピンの設定
各PWMモジュールは4つのチャンネルを持ち、それぞれにPWM出力を割り当てられます。
今回は2つの端子にPWM出力を割り当てるので、以下のように設定します。

```c
        .output_pins =
        {
            PWM_PIN0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
            PWM_PIN1 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
            NRF_DRV_PWM_PIN_NOT_USED, // channel 2
            NRF_DRV_PWM_PIN_NOT_USED  // channel 3
        },
```
NRF_DRV_PWM_PIN_INVERTEDはactive highとactive lowを設定するビットを制御します。
PWM出力がない場合に、端子出力がHighだとactive highで、LOWだとactive lowです。
NRF_DRV_PWM_PIN_INVERTEDを使用すると、active highになります。

active lowにしたい場合はNRF_DRV_PWM_PIN_INVERTEDを使用しません。

```c
        .output_pins =
        {
            PWM_PIN0, // channel 0
```

https://devzone.nordicsemi.com/f/nordic-q-a/34157/invert-pwm-pin-pwm-pwm-intialize

## 周期
今回はカウントに125kHzクロックを使用しました。

```c
.base_clock   = NRF_PWM_CLK_125kHz, //1cyc 8us
```

他にも以下の設定が可能です。

```c
enum  	nrf_pwm_clk_t {
  NRF_PWM_CLK_16MHz = PWM_PRESCALER_PRESCALER_DIV_1,
  NRF_PWM_CLK_8MHz = PWM_PRESCALER_PRESCALER_DIV_2,
  NRF_PWM_CLK_4MHz = PWM_PRESCALER_PRESCALER_DIV_4,
  NRF_PWM_CLK_2MHz = PWM_PRESCALER_PRESCALER_DIV_8,
  NRF_PWM_CLK_1MHz = PWM_PRESCALER_PRESCALER_DIV_16,
  NRF_PWM_CLK_500kHz = PWM_PRESCALER_PRESCALER_DIV_32,
  NRF_PWM_CLK_250kHz = PWM_PRESCALER_PRESCALER_DIV_64,
  NRF_PWM_CLK_125kHz = PWM_PRESCALER_PRESCALER_DIV_128
}
```

1サイクルはtop_valueにて設定します。
今回は、12500*8usec=100msecなので12500を設定しています。

```c
.top_value    = 12500, //100ms period
```

無限ループで実行するのでNRF_DRV_PWM_FLAG_LOOPを設定しています。

```c
(void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1, NRF_DRV_PWM_FLAG_LOOP);
```

もし3回だけパルス出力したい場合はNRF_DRV_PWM_FLAG_STOPに設定し、第3引数に回数を設定します。

```c
(void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 3, NRF_DRV_PWM_FLAG_STOP);
```

## Duty比
Duty比はseq_valuesに設定します。
以下では2500*8usec=20msecに設定しているので、20msecで極性が反転し、2:8のパルスが生成されます。

```c
    static uint16_t seq_values[] =
    {
        2500 //20msec
    };
```

load_modeをNRF_PWM_LOAD_COMMONに設定しているので、全てのチャンネルに共通のDuty比が適用されます。

```c
.load_mode    = NRF_PWM_LOAD_COMMON,
```

# Duty比2:8と4:6の100msec周期パルスの生成

```c
static void pwm_sample(void)
{
    nrf_drv_pwm_config_t const config0 =
    {
        .output_pins =
        {
            PWM_PIN0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
            PWM_PIN1 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
            NRF_DRV_PWM_PIN_NOT_USED, // channel 2
            NRF_DRV_PWM_PIN_NOT_USED  // channel 3
        },
        .irq_priority = APP_IRQ_PRIORITY_LOWEST,
        .base_clock   = NRF_PWM_CLK_125kHz, //1cyc 8us
        .count_mode   = NRF_PWM_MODE_UP,
        .top_value    = 12500, //100ms period
        .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
        .step_mode    = NRF_PWM_STEP_AUTO
    };
    APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));

    static nrf_pwm_values_individual_t seq_values[] =
    {
        { 2500, 5000, 0, 0 }
    };

    nrf_pwm_sequence_t const seq =
    {
        .values.p_individual = seq_values,
        .length              = NRF_PWM_VALUES_LENGTH(seq_values),
        .repeats             = 0,
        .end_delay           = 0
    };

    (void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1, NRF_DRV_PWM_FLAG_LOOP);
}
```
M:50msecのオシロスコープ波形データです。
上がch0、下がch1です。
ch0がduty比2:8、ch1がduty比4:6の100msec周期パルスになっています。

![PWM_sample2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/8cb9963a-e8fd-a8e0-0da6-136558795e4e.png)

## チャンネル毎のDuty比 
チャンネル毎にDuty比を変えたい場合は、nrf_pwm_values_individual_t を使用します。

```c
    static nrf_pwm_values_individual_t seq_values[] =
    {
        { 2500, 5000, 0, 0 }
    };
```

4つの値は、0chから3chまでの設定です。今回は0chに2500*8usec=20msec、1chに5000*8usec=40msecを設定しています。
2chと3chは使用していないので0です。

この設定を使用する場合は、load_modeをNRF_PWM_LOAD_INDIVIDUALに設定します。

```c
.load_mode    = NRF_PWM_LOAD_COMMON,
```


# Duty比が変わる100msec周期パルスの生成

```c
static void pwm_sample(void)
{
    nrf_drv_pwm_config_t const config0 =
    {
        .output_pins =
        {
            PWM_PIN0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
            PWM_PIN1 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
            NRF_DRV_PWM_PIN_NOT_USED, // channel 2
            NRF_DRV_PWM_PIN_NOT_USED  // channel 3
        },
        .irq_priority = APP_IRQ_PRIORITY_LOWEST,
        .base_clock   = NRF_PWM_CLK_125kHz, //1cyc 8us
        .count_mode   = NRF_PWM_MODE_UP,
        .top_value    = 12500, //100ms period
        .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
        .step_mode    = NRF_PWM_STEP_AUTO
    };
    APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));

    static nrf_pwm_values_individual_t seq_values[] =
    {
        { 2500, 5000, 0, 0 },
        { 2500, 5000, 0, 0 },        
        { 5000, 2500, 0, 0 }
    };

    nrf_pwm_sequence_t const seq =
    {
        .values.p_individual = seq_values,
        .length              = NRF_PWM_VALUES_LENGTH(seq_values),
        .repeats             = 0,
        .end_delay           = 0
    };

    (void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1, NRF_DRV_PWM_FLAG_LOOP);
}
```
M:50msecのオシロスコープ波形データです。
上がch0、下がch1です。
ch0がduty比2:8→2:8→4:6、ch1がduty比4:6→4:6→2:8の100msec周期パルスになっています。

![PWM_sample3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/2d852ef2-70b7-3897-0eab-01dda687b547.png)

## Duty比の変化
seq_valuesに複数行の設定を行うと、設定した順番にパルス出力します。
この設定全てで1回のサイクルとみなされます。
無限ループの場合は関係ないですが、パルス出力の回数を指定したい場合はご注意ください。

```c
    static nrf_pwm_values_individual_t seq_values[] =
    {
        { 2500, 5000, 0, 0 },
        { 2500, 5000, 0, 0 },        
        { 5000, 2500, 0, 0 }
    };
```
load_modeがNRF_PWM_LOAD_COMMONの場合も、同様のことができます。

```c
    static uint16_t seq_values[] =
    {
        2500,
        2500,
        5000
    };
```


# Duty比を割り込みで変更する

```c
static uint16_t pwm_ch0_duty  = 2500;
static uint16_t pwm_ch1_duty  = 5000;
```

```c
static nrf_pwm_values_individual_t m_seq_values;
static nrf_pwm_sequence_t const    m_seq =
{
    .values.p_individual = &m_seq_values,
    .length              = NRF_PWM_VALUES_LENGTH(m_seq_values),
    .repeats             = 0,
    .end_delay           = 0
};

static void pwm0_handler(nrf_drv_pwm_evt_type_t event_type)
{
    if (event_type == NRF_DRV_PWM_EVT_FINISHED)
    {
        uint16_t * p_channels = (uint16_t *)&m_demo1_seq_values;
        uint16_t value[2];
        value[0] = p_channels[0];
        value[1] = p_channels[1];
 
        p_channels[0] = value[0]+2500;
        p_channels[1] = value[1]+2500;
    }
}
static void pwm_sample(void)
{
     nrf_drv_pwm_config_t const config0 =
    {
        .output_pins =
        {
            PWM_PIN0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
            PWM_PIN1 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
            NRF_DRV_PWM_PIN_NOT_USED, // channel 2
            NRF_DRV_PWM_PIN_NOT_USED  // channel 3
        },
        .irq_priority = APP_IRQ_PRIORITY_LOWEST,
        .base_clock   = NRF_PWM_CLK_125kHz, //1cyc 8us
        .count_mode   = NRF_PWM_MODE_UP,
        .top_value    = 12500, //100ms period
        .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
        .step_mode    = NRF_PWM_STEP_AUTO
    };
    APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, pwm0_handler));
    m_used |= USED_PWM(0);

    m_seq_values.channel_0 = pwm_ch0_duty;
    m_seq_values.channel_1 = pwm_ch1_duty;
    m_seq_values.channel_2 = 0;
    m_seq_values.channel_3 = 0;    
    
    (void)nrf_drv_pwm_simple_playback(&m_pwm0, &m_seq, 2, NRF_DRV_PWM_FLAG_LOOP);
}
```

M:50msecのオシロスコープ波形データです。
上がch0、下がch1です。
100msec周期パルスの2サイクルごとに割り込みを発生させ、Duty比を変更しています。
![PWM_sample4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/d56396c8-cec1-ed27-b2c3-47406104ea7d.png)

## 割り込み
nrf_drv_pwm_init関数で割り込みハンドラーを指定します。

```c
nrf_drv_pwm_init(&m_pwm0, &config0, pwm0_handler)
```

割り込みハンドラーpwm0_handlerにおいて、イベントがNRF_DRV_PWM_EVT_FINISHEDの場合に実行されるようにしています。

```c
static void pwm0_handler(nrf_drv_pwm_evt_type_t event_type)
{
    if (event_type == NRF_DRV_PWM_EVT_FINISHED)
```

NRF_DRV_PWM_EVT_FINISHEDは設定したサイクル回数毎に発生します。
例えば、以下では2回に設定しているので、2サイクルごとに割り込みハンドラーが呼ばれています。

```c
(void)nrf_drv_pwm_simple_playback(&m_pwm0, &m_seq, 2, NRF_DRV_PWM_FLAG_LOOP);
```

ハンドラー内ではduty比の変数に加算することでduty比を変更しています

```c
        p_channels[0] = value[0]+2500;
        p_channels[1] = value[1]+2500;
```

# その他
割り込みを使用せずにDuty比を変更することもできます。
割り込みを使用しないことで、スリープモードを解除せずにPWM出力を制御することができます。

他にも様々な設定があるので、サンプルプロジェクトや参照のSDKマニュアルをご参照ください

# 参照
[sdk_nrf5_v16.0.0 PWM](https://infocenter.nordicsemi.com/topic/sdk_nrf5_v16.0.0/hardware_driver_pwm.html)
[sdk_nrf5_v16.0.0 PWM driver](https://infocenter.nordicsemi.com/topic/sdk_nrf5_v16.0.0/group__nrf__pwm.html)
[PWM Driver Example](https://infocenter.nordicsemi.com/topic/sdk_nrf5_v16.0.0/pwm_hw_example.html)