蓝牙芯片nRF52832之PWM的使用
nRF52832蓝牙芯片,自带了3路PWM硬件模块,每个模块支持4路,总共支持12路PWM。在SDK15中提供了简化的API,能够快速、便捷的实现PWM功能。以驱动LED灯为例,介绍一下nRF5283在SDK15中PWM的使用步骤以及注意的问题。
SDK15 PWM的使用步骤
1. SDK 驱动配置中enable PWM模块
图1
2. 初始化PWM实例
#include "nrf_drv_pwm.h"
static nrf_drv_pwm_t m_pwm0 = NRF_DRV_PWM_INSTANCE(0);
3. 初始化配置
nrf_drv_pwm_config_t const config0 =
{
.output_pins =
{
BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
BSP_LED_2 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
BSP_LED_3 | NRF_DRV_PWM_PIN_INVERTED, // channel 2
BSP_LED_1 | NRF_DRV_PWM_PIN_INVERTED // channel 3
},
.irq_priority = APP_IRQ_PRIORITY_LOWEST,
.base_clock = NRF_PWM_CLK_125kHz,
.count_mode = NRF_PWM_MODE_UP,
.top_value = 15625,
.load_mode = NRF_PWM_LOAD_INDIVIDUAL,
.step_mode = NRF_PWM_STEP_AUTO
};
APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));
每路PWM硬件模块支持4个channel,nrf_drv_pwm_config_t中:
- output_pins:映射PWM输出的pin脚号(spec上硬件的定义如P0.00,P0.01,00,01就是pin脚号,开发板中4个LED用的p0.17 - p0.20端口, 对应的pin脚号就是17-20);如果对应的通道没有硬件连接就使用NRF_DRV_PWM_PIN_NOT_USED。每个通道可以使用NRF_DRV_PWM_PIN_INVERTED来设置空闲引脚的电平为高电平,不使用时空闲引脚的电平为低电平
- irq_priority:定义中断优先级,一般不使用;
- base_clock定义PWM的时钟,从16M的clock分频而来,最高16MHZ,最低125kHz, 这个非常重要,每个clock多长时间来计算cycle持续多长时间;
- count_mode:有两种,一种是NRF_PWM_MODE_UP和NRF_PWM_MODE_UP_AND_DOWN。NRF_PWM_MODE_UP边沿对齐,NRF_PWM_MODE_UP_AND_DOWN中心对齐。两者区别如图2和图3所示;
- top_value:定义PWM的每个duty cycle中clock的最大个数;
- load_mode:后面sequnce的装载方式,是每个通道独立加载,还是所有通道共享一个,还是分组;
- step_mode:定义下一个cycle的进行方式,一般用NRF_PWM_STEP_AUTO,还有一种trigger模式。
图2
图3
4. 定义sequence数据
static nrf_pwm_values_individual_t /*const*/ seq_values[] =
{
{ 0x8000, 0, 0, 0 },
{ 0, 0x8000, 0, 0 },
{ 0, 0, 0x8000, 0 },
{ 0, 0, 0, 0x8000 }
};
先定义每个通道的占空比,通过clock数来定义,不能超过top_value。如果四个通道均使用,则每个数组均有4个人duty cycle数据,至少有一组数据,如{ 0x8000, 0, 0, 0 },通道1的cycle数是0x8000,2-4均为0。根据需要,可以使用不同的占空比来形成一个序列,示例中就定义了4个序列,seq_values的size为4。每个通道使用独立数据,则在config中用.load_mode = NRF_PWM_LOAD_INDIVIDUAL。
如果所有的通道共用一个sequence数据,则需要使用nrf_pwm_values_common_t,实际就是uint16_t,则在config中用.load_mode = NRF_PWM_LOAD_COMMON。
nrf_pwm_sequence_t const seq =
{
.values.p_individual = seq_values,
.length = NRF_PWM_VALUES_LENGTH(seq_values),
.repeats = 0,
.end_delay = 0
};
nrf_pwm_sequence_t中,
- values:前面的定义的占空比数据,通道独立数据初始化values.p_individual ,共用一个数据则初始化.values.p_common;
- length:通过宏NRF_PWM_VALUES_LENGTH自动计算数据长度
- repeats:每个duty cycle重复的次数,注意是每个duty cycle!不是重复sequece。如{ 0x8000, 0, 0, 0 },在repeats为2的情况下最后PWM生效的数据为,channel 1的duty cycle为0x8000,0x8000,第2-4的duty cycle为0,0
- end_delay: 每个sequence中,最后一个duty cycle重复的次数,如通道1的duty cycle为 {0x8000, 0},在end_delay为4的情况下,最后PWM的生效数据为:{0x8000, 0,0,0,0,0}
5. 触发PWM
(void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1, NRF_DRV_PWM_FLAG_LOOP);
通过play_back来触发PWM,第三个参数为播放的次数,最后一个参数定义模式,NRF_DRV_PWM_FLAG_LOOP为永远重复,NRFX_PWM_FLAG_STOP播放定义的次数后结束,还有其他的模式定义起始seq等(最多可以有两个seq)。
6. 完整的代码
static void pwm_demo(void)
{
nrf_drv_pwm_config_t const config0 =
{
.output_pins =
{
BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
BSP_LED_2 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
BSP_LED_3 | NRF_DRV_PWM_PIN_INVERTED, // channel 2
BSP_LED_1 | NRF_DRV_PWM_PIN_INVERTED // channel 3
},
.irq_priority = APP_IRQ_PRIORITY_LOWEST,
.base_clock = NRF_PWM_CLK_125kHz,
.count_mode = NRF_PWM_MODE_UP,
.top_value = 15625,
.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 /*const*/ seq_values[] =
{
{ 0x8000, 0, 0, 0 },
{ 0, 0x8000, 0, 0 },
{ 0, 0, 0x8000, 0 },
{ 0, 0, 0, 0x8000 }
};
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);
}
*注:copy自examples/peripheral/pwm_driver/main.c中的demo5
其他的各种使用demo参考SDK中examples/peripheral/pwm_driver/main.c
PWM输出波形的影响因素
1. 影响PWM输出的波形有config和sequence两个定义。
nrf_drv_pwm_config_t const config =
{
.base_clock,
.count_mode,
.top_value,
};
nrf_pwm_sequence_t const seq =
{
.values,
.repeats,
.end_delay
};
说明:
- base_clock:定义基准始终,从16MHz到125kHz,每个clock的时间为 1/base_clock,如NRF_PWM_CLK_125kHz,则每个clock的时间为 1/(125 * 1000) = 0.008ms
- count_mode:NRF_PWM_MODE_UP模式,则每个duty cycle的总时间为:top_value * ( 1 / base_clock),如果NRF_PWM_MODE_UP_AND_DOWN模式,则每个duty cycle的总时间为:2 * top_value * ( 1 / base_clock),如图2 图3
- top_value:每个cycle总的clock数,一个cycle的总时间为:top_value * ( 1 / base_clock)
- values: sequence的定义,每个duty cycle中,有效的clock数
- repeats:每个duty cycle重复的次数
- end_delay:最后一个duty cycle的重复的次数
如一组定义:
{
.base_clock = NRF_PWM_CLK_125kHz,
.count_mode = NRF_PWM_MODE_UP,
.top_value = 1000,
.values = {1000, 0} //一个100%的占空比,一个0%的占空比
.repeats = 0,
.end_delay = 0
}
该组的波形为:
图3
如一组定义:
{
.base_clock = NRF_PWM_CLK_125kHz,
.count_mode = NRF_PWM_MODE_UP,
.top_value = 1000,
.values = {1000, 0} //一个100%的占空比,一个0%的占空比
.repeats = 2,
.end_delay = 0
}
该组的波形为:
图4
如一组定义:
{
.base_clock = NRF_PWM_CLK_125kHz,
.count_mode = NRF_PWM_MODE_UP,
.top_value = 1000,
.values = {1000, 0} //一个100%的占空比,一个0%的占空比
.repeats = 0,
.end_delay = 2
}
该组的波形为:
图5
2. 官方demo中duty cycle 值为0x8000的调查
官方SDK中examples/peripheral/pwm_driver/main.c示例中,nrf_pwm_sequence_t中的duty cycle数值均用0x8000,一直百思不得其解。不同的例子,有的LED为200ms,有的为250ms,但duty cycle均采用0x8000,并且都能工作。why?
看到nrf_pwm_configure的源码中有个assert,NRFX_ASSERT(top_value <= PWM_COUNTERTOP_COUNTERTOP_Msk),进一步调查发现PWM_COUNTERTOP_COUNTERTOP_Msk定义为0x7FFF。也就是COUNTERTOP是个14位的寄存器,查看官方spec验证了这一想法。
实际上top_value的最大值是0x7FFF,所以nrf_pwm_sequence_t的每个duty cycle值也不应该大于0x7FFF。0x8000刚好比最大值大1,无论top_value是多少,都是占空比100%。猜测,如果设置的值大于0x7FFF,系统应该会自动将其置为0x7FFF。在示例中,将0x8000替换为0x9000,0xA000均能工作,算是做了一个验证。
__STATIC_INLINE void nrf_pwm_configure(NRF_PWM_Type * p_reg,
nrf_pwm_clk_t base_clock,
nrf_pwm_mode_t mode,
uint16_t top_value)
{
NRFX_ASSERT(top_value <= PWM_COUNTERTOP_COUNTERTOP_Msk);
p_reg->PRESCALER = base_clock;
p_reg->MODE = mode;
p_reg->COUNTERTOP = top_value;
}
#define PWM_COUNTERTOP_COUNTERTOP_Msk (0x7FFFUL << PWM_COUNTERTOP_COUNTERTOP_Pos) /*!< Bit mask of COUNTERTOP field. */
Spec中COUNTERTOP的寄存器定义:
图6
3. COUNTERTOP的限制进一步思考
既然top_value的最大值是0x7FFF(十进制32768),sequence中的值也应该均比0x7FFF小。对于一个base_clock为 NRF_PWM_CLK_125kHz的PWM来说,最大的cycle时间为:32768 * ( 1 / 125000) = 262.144ms。如果需要一个cycle time为1秒的波形该怎么办呢?这时得动用repeats来拓展了。将top_value设为250ms极为 250 / 0.008 = 31250。将repeats设为4,即为250*4 = 1秒了。也可以组合end_delay实现各种不同的波形。
在官方的例子中都是一个占空比100%(values值0x8000),一个占空比为0%的波形(values值0)。可以将top_value设置为小于32768,values中的值设置成0-32768,即可调整占空比。
总结
PWM虽然简单,但是对一个平台来说都有自己的特色。调试下来,对文档中说得不够清楚的,有些古怪的地方还是要调查一下,会对这个点的理解更深入一些。