記事の概要
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モジュールが有効になっているかをご確認ください。
# ifndef NRFX_PWM_ENABLED
# define NRFX_PWM_ENABLED 1
# endif
// <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インスタンス作成
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周期パルスになっています。
チャンネル毎の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周期パルスになっています。
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比を変更しています。
割り込み
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