nRF52840 PWM 实现方案详解:从原理到实战
2026/9/14 11:06:14 网站建设 项目流程

文章目录

    • 1. 引言
    • 2. 核心概念
    • 2.1 什么是 PWM
    • 2.2 nRF52840 的 PWM 资源概览
    • 2.3 三种实现方案对比
    • 3. 实战示例
    • 3.1 方案一:low_power_pwm(低功耗优先)
    • 3.2 方案二:pwm_library(性能优先)
    • 3.3 方案三:pwm_driver(均衡之选)
    • 3.4 选型决策流程
    • 4. 常见问题
    • 4.1 为什么 low_power_pwm 和 pwm_library 都只能输出 2 路?
    • 4.2 需要 8 路 PWM 该如何实现?
    • 4.3 低功耗模式下 PWM 还能工作吗?
    • 4.4 PWM 输出异常排查
      • 1. 引脚冲突
      • 2. 时钟配置错误
      • 3. 占空比设置无效
    • 5. 总结

1. 引言

在嵌入式开发中,PWM(脉冲宽度调制)是控制电机转速、LED 亮度、蜂鸣器频率等场景的核心技术。nRF52840 作为 Nordic 推出的低功耗蓝牙 SoC,提供了多种 PWM 实现途径,但不同方案在功耗、资源占用和输出通道数上差异显著。

本文将从 nRF52840 的 PWM 资源出发,系统对比三种主流实现方案,并通过实战示例帮助读者快速选型与落地。

2. 核心概念

2.1 什么是 PWM

PWM 通过调节方波信号的占空比(高电平时间占整个周期的比例)来模拟模拟输出。占空比越高,等效输出电压越高,从而实现对负载的精细控制。

2.2 nRF52840 的 PWM 资源概览

nRF52840 内置 3 组 PWM 外设模块,每组最多支持 4 路 PWM 输出,总计最多 12 路。此外,芯片还提供低功耗定时器(如 RTC)和通用硬件定时器(如 TIMER),它们也能产生 PWM,但通道数和功耗特性各不相同。

2.3 三种实现方案对比

方案底层资源功耗单定时器/模块通道数适用场景
low_power_pwm低功耗定时器(RTC)2 路对功耗敏感、通道需求少的场景
pwm_library硬件定时器(TIMER)2 路对实时性要求高、可接受较高功耗
pwm_driverPWM 外设模块中等4 路需要多通道输出、功耗与性能均衡

3. 实战示例

在动手编写代码之前,先了解 nRF52840 上常用的 PWM 引脚分配,有助于避免引脚冲突并快速选型。下表列出了 6 个典型引脚及其对应的 PWM 资源:

引脚号功能所属 PWM 模块/定时器适用方案
P0.06LED 亮度控制PWM0 通道 0pwm_driver
P0.08电机调速TIMER0 比较输出 0pwm_library
P0.11蜂鸣器驱动PWM1 通道 1pwm_driver
P0.13舵机控制PWM2 通道 2pwm_driver
P0.15低功耗呼吸灯RTC 比较输出 0low_power_pwm
P0.17风扇调速TIMER1 比较输出 1pwm_library
P0.20RGB LED 绿色通道PWM0 通道 3pwm_driver
P0.24低功耗指示灯RTC 比较输出 1low_power_pwm

说明:实际引脚功能需结合具体开发板原理图与 Nordic 引脚复用配置(GPIO 的 P0/P1 端口)确认,上表为常见典型分配示例。

3.1 方案一:low_power_pwm(低功耗优先)

#include"nrfx.h"#include"nrf_gpio.h"#include"nrfx_gpiote.h"#include"nrfx_rtc.h"#include"nrfx_ppi.h"/* low_power_pwm:使用 RTC + PPI + GPIOTE 实现低功耗 PWM 输出 */#defineLOW_PWM_RTC_ID2#defineLOW_PWM_PINNRF_GPIO_PIN_MAP(0,15)#defineLOW_PWM_PERIOD_TICKS1000U/* 32.768 kHz 下约 32.77 Hz,适合低功耗 LED 呼吸灯 */staticconstnrfx_rtc_tlow_pwm_rtc=NRFX_RTC_INSTANCE(LOW_PWM_RTC_ID);staticnrf_ppi_channel_tlow_pwm_ppi_set;staticnrf_ppi_channel_tlow_pwm_ppi_clr;staticnrf_ppi_channel_tlow_pwm_ppi_period;/* 将 0-100 的占空比转换为 RTC 比较计数值 */staticuint32_tlow_pwm_percent_to_ticks(uint8_tpercent){if(percent>100U){percent=100U;}return(LOW_PWM_PERIOD_TICKS*percent)/100U;}voidlow_power_pwm_init(void){nrfx_err_terr;/* 1. 配置输出引脚为 GPIOTE 任务模式,初始输出低电平 */nrfx_gpiote_out_config_tout_cfg={.action=NRF_GPIOTE_POLARITY_TOGGLE,.init_state=NRF_GPIOTE_INITIAL_VALUE_LOW,.task_pin=true,};err=nrfx_gpiote_out_init(LOW_PWM_PIN,&out_cfg);if(err!=NRFX_SUCCESS){return;}/* 2. 初始化 RTC,使用 32.768 kHz 低功耗时钟 */nrfx_rtc_config_trtc_cfg=NRFX_RTC_DEFAULT_CONFIG;rtc_cfg.prescaler=0;err=nrfx_rtc_init(&low_pwm_rtc,&rtc_cfg,NULL);if(err!=NRFX_SUCCESS){return;}/* 3. 分配并配置 PPI 通道: CC0 事件 -> GPIOTE SET:置位引脚,开始高电平 CC1 事件 -> GPIOTE CLR:清除引脚,结束高电平 CC2 事件 -> RTC CLEAR:清除计数器,开始下一个周期 */nrfx_ppi_channel_alloc(&low_pwm_ppi_set);nrfx_ppi_channel_alloc(&low_pwm_ppi_clr);nrfx_ppi_channel_alloc(&low_pwm_ppi_period);nrfx_ppi_channel_assign(low_pwm_ppi_set,nrfx_rtc_event_address_get(&low_pwm_rtc,NRF_RTC_EVENT_COMPARE_0),nrfx_gpiote_set_task_addr_get(LOW_PWM_PIN));nrfx_ppi_channel_assign(low_pwm_ppi_clr,nrfx_rtc_event_address_get(&low_pwm_rtc,NRF_RTC_EVENT_COMPARE_1),nrfx_gpiote_clr_task_addr_get(LOW_PWM_PIN));nrfx_ppi_channel_assign(low_pwm_ppi_period,nrfx_rtc_event_address_get(&low_pwm_rtc,NRF_RTC_EVENT_COMPARE_2),nrfx_rtc_task_address_get(&low_pwm_rtc,NRF_RTC_TASK_CLEAR));nrfx_ppi_channel_enable(low_pwm_ppi_set);nrfx_ppi_channel_enable(low_pwm_ppi_clr);nrfx_ppi_channel_enable(low_pwm_ppi_period);/* 4. 设置比较值并启动 RTC */nrfx_rtc_counter_clear(&low_pwm_rtc);nrfx_rtc_cc_set(&low_pwm_rtc,NRF_RTC_CC_CHANNEL_0,0,false);nrfx_rtc_cc_set(&low_pwm_rtc,NRF_RTC_CC_CHANNEL_1,0,false);nrfx_rtc_cc_set(&low_pwm_rtc,NRF_RTC_CC_CHANNEL_2,LOW_PWM_PERIOD_TICKS,false);nrfx_rtc_enable(&low_pwm_rtc);}voidlow_power_pwm_set_duty(uint8_tduty_percent){/* 仅更新比较值即可,PPI 会在下一个周期自动生效 */uint32_tduty_ticks=low_pwm_percent_to_ticks(duty_percent);nrfx_rtc_cc_set(&low_pwm_rtc,NRF_RTC_CC_CHANNEL_1,duty_ticks,false);}intmain(void){low_power_pwm_init();low_power_pwm_set_duty(50);/* 50% 占空比输出 */while(1){__WFE();/* 进入低功耗等待,PWM 由 RTC 和 PPI 自动维持 */}}

特点:功耗最低,适合电池供电的常驻场景;但单定时器仅支持 2 路,且频率精度受限于低功耗时钟源。

3.2 方案二:pwm_library(性能优先)

#include"nrfx.h"#include"nrf_gpio.h"#include"nrfx_gpiote.h"#include"nrfx_timer.h"#include"nrfx_ppi.h"/* pwm_library:使用 TIMER + PPI + GPIOTE 实现高频 PWM 输出 */#defineHPWM_TIMER_ID0#defineHPWM_PINNRF_GPIO_PIN_MAP(0,8)#defineHPWM_TIMER_FREQ_HZ16000000UL/* TIMER 运行频率 16 MHz */#defineHPWM_TARGET_FREQ_HZ20000UL/* 目标 PWM 频率 20 kHz */#defineHPWM_PERIOD_TICKS(HPWM_TIMER_FREQ_HZ/HPWM_TARGET_FREQ_HZ)/* 800 */staticconstnrfx_timer_thpwm_timer=NRFX_TIMER_INSTANCE(HPWM_TIMER_ID);staticnrf_ppi_channel_thpwm_ppi_set;staticnrf_ppi_channel_thpwm_ppi_clr;staticnrf_ppi_channel_thpwm_ppi_period;/* 将 0-100 的占空比转换为定时器比较计数值 */staticuint32_thpwm_percent_to_ticks(uint8_tpercent){if(percent>100U){percent=100U;}return(HPWM_PERIOD_TICKS*percent)/100U;}voidpwm_library_init(void){nrfx_err_terr;/* 1. 配置输出引脚为 GPIOTE 任务模式,初始输出低电平 */nrfx_gpiote_out_config_tout_cfg={.action=NRF_GPIOTE_POLARITY_TOGGLE,.init_state=NRF_GPIOTE_INITIAL_VALUE_LOW,.task_pin=true,};err=nrfx_gpiote_out_init(HPWM_PIN,&out_cfg);if(err!=NRFX_SUCCESS){return;}/* 2. 初始化 TIMER0 为定时器模式,16 MHz、16 位计数 */nrfx_timer_config_ttimer_cfg=NRFX_TIMER_DEFAULT_CONFIG;timer_cfg.frequency=NRF_TIMER_FREQ_16MHz;timer_cfg.mode=NRF_TIMER_MODE_TIMER;timer_cfg.bit_width=NRF_TIMER_BIT_WIDTH_16;err=nrfx_timer_init(&hpwm_timer,&timer_cfg,NULL);if(err!=NRFX_SUCCESS){return;}/* 3. 分配并配置 PPI 通道: CC0 事件 -> GPIOTE SET:置位引脚 CC1 事件 -> GPIOTE CLR:清除引脚 CC2 事件 -> TIMER CLEAR:清除定时器,开始下一个周期 */nrfx_ppi_channel_alloc(&hpwm_ppi_set);nrfx_ppi_channel_alloc(&hpwm_ppi_clr);nrfx_ppi_channel_alloc(&hpwm_ppi_period);nrfx_ppi_channel_assign(hpwm_ppi_set,nrfx_timer_event_address_get(&hpwm_timer,NRF_TIMER_EVENT_COMPARE0),nrfx_gpiote_set_task_addr_get(HPWM_PIN));nrfx_ppi_channel_assign(hpwm_ppi_clr,nrfx_timer_event_address_get(&hpwm_timer,NRF_TIMER_EVENT_COMPARE1),nrfx_gpiote_clr_task_addr_get(HPWM_PIN));nrfx_ppi_channel_assign(hpwm_ppi_period,nrfx_timer_event_address_get(&hpwm_timer,NRF_TIMER_EVENT_COMPARE2),nrfx_timer_task_address_get(&hpwm_timer,NRF_TIMER_TASK_CLEAR));nrfx_ppi_channel_enable(hpwm_ppi_set);nrfx_ppi_channel_enable(hpwm_ppi_clr);nrfx_ppi_channel_enable(hpwm_ppi_period);/* 4. 设置比较值并启动 TIMER */nrfx_timer_clear(&hpwm_timer);nrfx_timer_compare(&hpwm_timer,NRF_TIMER_CC_CHANNEL0,0,false);nrfx_timer_compare(&hpwm_timer,NRF_TIMER_CC_CHANNEL1,0,false);nrfx_timer_compare(&hpwm_timer,NRF_TIMER_CC_CHANNEL2,HPWM_PERIOD_TICKS,false);nrfx_timer_enable(&hpwm_timer);}voidpwm_library_set_duty(uint8_tduty_percent){/* 仅更新比较值即可,PPI 会在下一个周期自动生效 */uint32_tduty_ticks=hpwm_percent_to_ticks(duty_percent);nrfx_timer_compare(&hpwm_timer,NRF_TIMER_CC_CHANNEL1,duty_ticks,false);}intmain(void){pwm_library_init();pwm_library_set_duty(75);/* 75% 占空比输出 */while(1){__WFE();/* 等待中断或低功耗事件 */}}

特点:硬件定时器精度高、实时性强,适合电机控制等高频场景;但功耗较高,单定时器同样仅支持 2 路。

3.3 方案三:pwm_driver(均衡之选)

#include"nrfx.h"#include"nrf_gpio.h"#include"nrfx_pwm.h"/* pwm_driver:使用 nRF52840 的 PWM 外设直接输出 4 路 PWM */#definePWM_DRV_PIN0NRF_GPIO_PIN_MAP(0,6)#definePWM_DRV_PIN1NRF_GPIO_PIN_MAP(0,8)#definePWM_DRV_PIN2NRF_GPIO_PIN_MAP(0,11)#definePWM_DRV_PIN3NRF_GPIO_PIN_MAP(0,13)#definePWM_DRV_TOP_VALUE1000U/* top 值 1000,便于按 0.1% 粒度设置占空比 */staticnrfx_pwm_tpwm0=NRFX_PWM_INSTANCE(0);staticnrf_pwm_values_common_tpwm_seq_values[NRF_PWM_CHANNEL_COUNT]={0};staticnrf_pwm_sequence_tpwm_seq;voidpwm_driver_init(void){nrfx_err_terr;/* 1. 配置 PWM0 的四路输出引脚 */nrfx_pwm_config_tcfg=NRFX_PWM_DEFAULT_CONFIG(PWM_DRV_PIN0,PWM_DRV_PIN1,PWM_DRV_PIN2,PWM_DRV_PIN3);/* 2. 设置 1 MHz 计数时钟和 top 值,PWM 频率约为 1 kHz */cfg.base_clock=NRF_PWM_CLK_1MHz;cfg.top_value=PWM_DRV_TOP_VALUE;err=nrfx_pwm_init(&pwm0,&cfg,NULL);if(err!=NRFX_SUCCESS){return;}/* 3. 初始化 PWM 播放序列,后续通过更新数组值改变各通道占空比 */pwm_seq.values.p_common=pwm_seq_values;pwm_seq.length=NRF_PWM_VALUES_LENGTH(pwm_seq_values);pwm_seq.repeats=0;pwm_seq.end_delay=0;}/* 设置指定通道的占空比,duty_percent 范围 0-100 */voidpwm_driver_set_channel(uint8_tchannel,uint8_tduty_percent){if(channel>=NRF_PWM_CHANNEL_COUNT){return;}if(duty_percent>100U){duty_percent=100U;}pwm_seq_values[channel]=(PWM_DRV_TOP_VALUE*duty_percent)/100U;/* 使用 LOOP 标志循环播放,修改后立即重新加载序列 */nrfx_pwm_simple_playback(&pwm0,&pwm_seq,1,NRFX_PWM_FLAG_LOOP);}intmain(void){pwm_driver_init();pwm_driver_set_channel(0,25);/* 通道 0:25% 占空比 */pwm_driver_set_channel(1,50);/* 通道 1:50% 占空比 */pwm_driver_set_channel(2,75);/* 通道 2:75% 占空比 */pwm_driver_set_channel(3,100);/* 通道 3:100% 占空比 */while(1){__WFE();/* PWM 外设由硬件自动输出,无需 CPU 持续干预 */}}

特点:nRF52840 有 3 组 PWM 外设模块,每组最多 4 路,总计最多 12 路输出;功耗中等,是多数项目的首选方案。

3.4 选型决策流程

通道数需求

是否超过 2 路

功耗是否敏感

pwm_driver

low_power_pwm

pwm_library

使用 PWM 外设模块

4. 常见问题

4.1 为什么 low_power_pwm 和 pwm_library 都只能输出 2 路?

因为这两种方案都基于定时器实现,而 nRF52840 的每个定时器(无论是低功耗还是硬件定时器)在标准配置下最多只能产生 2 路比较输出。

4.2 需要 8 路 PWM 该如何实现?

可以使用 2 组 PWM 外设模块(每组 4 路),或 1 组 PWM 模块 + 1 个定时器组合。注意引脚分配和模块资源冲突。

4.3 低功耗模式下 PWM 还能工作吗?

low_power_pwm 基于低功耗定时器,在 System ON 低功耗模式下仍可运行;而 pwm_library 依赖的高频硬件定时器通常需要更高的工作模式,功耗随之上升。

4.4 PWM 输出异常排查

在实际调试中,PWM 输出异常大多来自硬件配置、时钟设置或更新流程。下面列出三类高频问题及排查步骤。

1. 引脚冲突

现象:波形输出到错误引脚、多路 PWM 互相干扰,或 GPIO 状态异常。

排查步骤

  1. 检查目标引脚是否被其他外设复用,例如 SPI、UART、I2C、QSPI,确认对应PSEL寄存器配置。
  2. 对照开发板原理图,确认代码中的引脚号与实际物理连接一致。
  3. 多路 PWM 同时使用时,确认各通道没有重复占用同一个比较输出或同一个 GPIO。

代码检查点

// 检查引脚是否已被外设占用if(NRF_P0->PIN_CNF[PWM_PIN]&(GPIO_PIN_CNF_MCUSEL_Msk)){// 该引脚已被外设占用,需要释放或更换引脚}

2. 时钟配置错误

现象:PWM 频率偏离预期,波形抖动,或 low_power_pwm 输出不稳定。

排查步骤

  1. 确认当前方案使用的时钟源:pwm_driver依赖 HFCLK,low_power_pwm依赖 LFCLK。
  2. 检查是否已启动对应时钟,并等待启动完成事件,避免在时钟稳定前配置 PWM。
  3. 若使用内部 RC 振荡器作为 LFCLK,注意校准时钟精度,否则低频输出可能漂移。

代码检查点

// 启动 LFCLK 并等待稳定NRF_CLOCK->EVENTS_LFCLKSTARTED=0;NRF_CLOCK->TASKS_LFCLKSTART=1;while(!NRF_CLOCK->EVENTS_LFCLKSTARTED){}

3. 占空比设置无效

现象:写入占空比后没有变化,输出一直为 0 或 100%,或占空比与设定值不符。

排查步骤

  1. 确认占空比单位:不同方案可能使用百分比、0-255、0-4095 等不同量程。
  2. 检查是否在 PWM 启动后才修改比较值,或修改后未触发对应的更新任务。
  3. 使用pwm_driver时,确认top值设置合理,实际占空比由compare/top计算得出。

代码检查点

// 修改占空比后,确保触发 PWM 外设更新if(NRF_PWM0->EVENTS_SEQEND[0]){NRF_PWM0->EVENTS_SEQEND[0]=0;NRF_PWM0->TASKS_SEQSTART[0]=1;}

小结:先确认硬件连接与引脚复用,再核对时钟源是否启动,最后检查占空比量程与更新触发条件。多数 PWM 输出异常都可以沿这三条路径快速定位。

5. 总结

nRF52840 的三种 PWM 方案各有侧重:low_power_pwm适合功耗敏感、通道少的场景;pwm_library适合高频高精度控制;pwm_driver凭借 3 组模块、每组 4 路的资源,在通道数与功耗之间取得最佳平衡。实际项目中,建议优先评估通道数量与功耗预算,再结合实时性要求做出选择。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询