nRF52832蓝牙芯片,自带了3路PWM硬件模块,每个模块支持4路,总共支持12路PWM。在SDK15中提供了简化的API,能够快速、便捷的实现PWM功能。以驱动LED灯为例,介绍一下nRF5283在SDK15中PWM的使用步骤以及注意的问题。

SDK15 PWM的使用步骤

1. SDK 驱动配置中enable PWM模块

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模式。


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
	}

该组的波形为:

如一组定义:

	{
		.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
	}

该组的波形为:

如一组定义:

	{
		.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
	}

该组的波形为:

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的寄存器定义:

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虽然简单,但是对一个平台来说都有自己的特色。调试下来,对文档中说得不够清楚的,有些古怪的地方还是要调查一下,会对这个点的理解更深入一些。

(原创yanweichuan@163)

更多推荐

蓝牙芯片nRF52832之PWM的使用