3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-08

記事の概要

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

共通設定

# 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モジュールが有効になっているかをご確認ください。

sdk_config.h
# ifndef NRFX_PWM_ENABLED
# define NRFX_PWM_ENABLED 1
# endif
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周期パルスの生成

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

PWMインスタンス作成

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

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出力を割り当てるので、以下のように設定します。

        .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を使用しません。

        .output_pins =
        {
            PWM_PIN0, // channel 0

周期

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

.base_clock   = NRF_PWM_CLK_125kHz, //1cyc 8us

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

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を設定しています。

.top_value    = 12500, //100ms period

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

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

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

(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のパルスが生成されます。

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

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

.load_mode    = NRF_PWM_LOAD_COMMON,

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

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

チャンネル毎のDuty比

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

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

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

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

.load_mode    = NRF_PWM_LOAD_COMMON,

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

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

Duty比の変化

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

    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の場合も、同様のことができます。

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

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

static uint16_t pwm_ch0_duty  = 2500;
static uint16_t pwm_ch1_duty  = 5000;
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

割り込み

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

nrf_drv_pwm_init(&m_pwm0, &config0, pwm0_handler)

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

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サイクルごとに割り込みハンドラーが呼ばれています。

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

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

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

その他

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

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

参照

sdk_nrf5_v16.0.0 PWM
sdk_nrf5_v16.0.0 PWM driver
PWM Driver Example

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?