- 操作系统
- 嵌入式
- 物联网
- 嵌入式OS
- RTOS
【免费下载链接】rt-thread
RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/
PWM(脉宽调制)是嵌入式系统中应用最广泛的信号输出方式之一。本篇技术指南以 RT-Thread 官方设备驱动文档为骨架,系统讲解 PWM 的工作原理、RT-Thread PWM 设备管理框架的四类核心接口(查找设备、设置周期与脉宽、使能、关闭)、FinSH 调试命令以及完整的 LED 呼吸灯实战示例,并深入仓库源码(PWM 框架实现、设备头文件 与 STM32 底层驱动)揭示其底层调用链,帮助读者在任意 RT-Thread BSP 上快速驱动 PWM 外设。
PWM 基本原理与典型应用场景
PWM(Pulse Width Modulation,脉冲宽度调制)是一种用数字方式对模拟信号电平进行编码的方法:通过不同频率的脉冲序列来编码特定模拟信号的幅值,输出端收到一系列等幅脉冲,用这些脉冲去驱动负载,从而在负载上等效出期望的波形。
上图是一个简单的 PWM 原理示意图。假设定时器工作在向上计数模式:当计数值小于阈值时,输出一种电平状态(例如高电平);当计数值大于阈值时,输出相反状态(例如低电平);当计数值到达最大值后,计数器从 0 重新计数并回到原始电平状态。高电平持续时间(脉宽)与周期的比值即占空比,取值范围为 0~100%。上图中高电平恰好占整个周期的一半,因此占空比为 50%。
一个常见的 PWM 控制场景是调节灯或屏幕的亮度。亮度通过改变占空比来调整:PWM 并不是连续地调整光强,而是不断地让灯或屏幕“开—关”切换。当开关频率足够快时,人眼会认为它始终是亮的。在开关切换过程中,灯灭的时间越长,肉眼看到的屏幕亮度越低;反之,灯亮的时间越长,屏幕看起来越亮。
RT-Thread PWM 设备访问接口总览
应用程序通过 RT-Thread 提供的 PWM 设备管理接口访问 PWM 设备硬件,相关接口如下:
| 函数 | 描述 |
|---|---|
| rt_device_find() | 根据 PWM 设备名称查找设备句柄 |
| rt_pwm_set() | 设置 PWM 周期和脉冲宽度 |
| rt_pwm_enable() | 使能 PWM 设备 |
| rt_pwm_disable() | 关闭 PWM 设备 |
除此之外,PWM 设备头文件 还提供了rt_pwm_set_period()(仅设置周期)、rt_pwm_set_pulse()(仅设置脉宽)、rt_pwm_set_dead_time()(设置死区时间)、rt_pwm_set_phase()(设置相位)和rt_pwm_get()(读取配置)等扩展接口,用于电机控制等高级场景。
查找 PWM 设备
应用程序根据 PWM 设备名称获取设备句柄,进而操作 PWM 设备,函数原型如下:
rt_device_t rt_device_find(const char* name);| 参数 | 描述 |
|---|---|
| name | 设备名称 |
| 返回 | —— |
| 设备句柄 | 找到对应设备时,返回对应的设备句柄 |
| RT_NULL | 未找到设备 |
通常注册到系统中的 PWM 设备名称为 pwm0、pwm1 等(具体名称由各 BSP 的驱动定义,例如 STM32 系列会按PWMx的命名规则注册)。用法示例如下:
#define PWM_DEV_NAME "pwm3" /* PWM 设备名称 */ struct rt_device_pwm *pwm_dev; /* PWM 设备句柄 */ /* 查找设备 */ pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);从源码看,rt_device_find()返回的是通用rt_device_t句柄,而 PWM 操作接口需要struct rt_device_pwm *类型,因此示例中做了显式类型转换。struct rt_device_pwm在 dev_pwm.h 中定义,包含struct rt_device parent(继承自通用设备对象)和const struct rt_pwm_ops *ops(底层操作集)两个成员。
设置 PWM 周期和脉冲宽度
通过以下函数设置 PWM 周期和占空比:
rt_err_t rt_pwm_set(struct rt_device_pwm *device, int channel, rt_uint32_t period, rt_uint32_t pulse);| 参数 | 描述 |
|---|---|
| device | PWM 设备句柄 |
| channel | PWM 通道 |
| period | PWM 周期(ns) |
| pulse | PWM 脉冲宽度时间(ns) |
| 返回 | —— |
| RT_EOK | 成功 |
| -RT_EIO | 设备为空 |
| -RT_ENOSYS | 设备操作方法为空 |
| 其他错误 | 执行失败 |
PWM 的输出频率由周期决定。例如:一个周期时间为 0.5ms(毫秒),则周期值应填 500000ns(纳秒),输出频率为 2KHz;占空比 =pulse / period,且 pulse 值不能超过 period。需要注意的是,rt_pwm_set()只负责下发配置,之后还需要调用rt_pwm_enable()才能真正输出波形。
用法示例如下:
#define PWM_DEV_NAME "pwm3" /* PWM 设备名称 */ #define PWM_DEV_CHANNEL 4 /* PWM 通道 */ struct rt_device_pwm *pwm_dev; /* PWM 设备句柄 */ rt_uint32_t period, pulse; period = 500000; /* 周期为 0.5ms,单位为纳秒 */ pulse = 0; /* PWM 脉冲宽度值,单位为纳秒 */ /* 查找设备 */ pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); /* 设置 PWM 周期和脉冲宽度 */ rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);使能 PWM 设备
设置完 PWM 周期和脉冲宽度后,通过以下函数使能 PWM 设备:
rt_err_t rt_pwm_enable(struct rt_device_pwm *device, int channel);| 参数 | 描述 |
|---|---|
| device | PWM 设备句柄 |
| channel | PWM 通道 |
| 返回 | —— |
| RT_EOK | 使能设备成功 |
| -RT_ENOSYS | 设备操作方法为空 |
| 其他错误 | 使能设备失败 |
用法示例如下:
#define PWM_DEV_NAME "pwm3" /* PWM 设备名称 */ #define PWM_DEV_CHANNEL 4 /* PWM 通道 */ struct rt_device_pwm *pwm_dev; /* PWM 设备句柄 */ rt_uint32_t period, pulse; period = 500000; /* 周期为 0.5ms,单位为纳秒 */ pulse = 0; /* PWM 脉冲宽度值,单位为纳秒 */ /* 查找设备 */ pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); /* 设置 PWM 周期和脉冲宽度 */ rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); /* 使能设备 */ rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL);关闭 PWM 设备通道
使用以下函数关闭 PWM 设备对应通道:
rt_err_t rt_pwm_disable(struct rt_device_pwm *device, int channel);| 参数 | 描述 |
|---|---|
| device | PWM 设备句柄 |
| channel | PWM 通道 |
| 返回 | —— |
| RT_EOK | 关闭设备成功 |
| -RT_EIO | 设备句柄为空 |
| 其他错误 | 关闭设备失败 |
用法示例如下:
#define PWM_DEV_NAME "pwm3" /* PWM 设备名称 */ #define PWM_DEV_CHANNEL 4 /* PWM 通道 */ struct rt_device_pwm *pwm_dev; /* PWM 设备句柄 */ rt_uint32_t period, pulse; period = 500000; /* 周期为 0.5ms,单位为纳秒 */ pulse = 0; /* PWM 脉冲宽度值,单位为纳秒 */ /* 查找设备 */ pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); /* 设置 PWM 周期和脉冲宽度 */ rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); /* 使能设备 */ rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL); /* 关闭设备通道 */ rt_pwm_disable(pwm_dev, PWM_DEV_CHANNEL);框架层实现原理:rt_pwm_set 的底层调用链
rt_pwm_set()、rt_pwm_enable()、rt_pwm_disable()是 RT-Thread PWM 设备框架(rt_drv_pwm.c)对外提供的统一接口,其内部并不直接操作寄存器,而是把参数封装进struct rt_pwm_configuration,再通过通用设备控制接口rt_device_control()下发到驱动层。
配置结构体与命令字
dev_pwm.h 中定义了核心配置结构体:
struct rt_pwm_configuration { rt_uint32_t channel; /* 0 ~ n 或 0 ~ -n,取决于具体 MCU 需求 */ rt_uint32_t period; /* 单位:ns,1ns~4.29s:1Ghz~0.23hz */ rt_uint32_t pulse; /* 单位:ns(pulse<=period) */ rt_uint32_t dead_time; /* 单位:ns */ rt_uint32_t phase; /* 单位:度,0~360,PWM 输出相位 */ /* * RT_TRUE : PWM 通道为互补输出 * RT_FALSE : PWM 通道为普通输出 */ rt_bool_t complementary; };period的合法取值范围为 1ns~4.29s(对应 1GHz~0.23Hz),pulse必须小于等于period。dead_time、phase、complementary是 2022 年后新增的扩展字段,用于电机控制中的死区插入、移相与互补输出。
框架层用宏定义了 12 个控制命令字(dev_pwm.h):PWM_CMD_ENABLE、PWM_CMD_DISABLE、PWM_CMD_SET、PWM_CMD_GET、PWMN_CMD_ENABLE/DISABLE(互补输出)、PWM_CMD_SET_PERIOD、PWM_CMD_SET_PULSE、PWM_CMD_SET_DEAD_TIME、PWM_CMD_SET_PHASE、PWM_CMD_ENABLE_IRQ/DISABLE_IRQ。所有命令字均基于RT_DEVICE_CTRL_BASE(PWM)派生,确保设备类型区分。
通道正负数约定:普通输出与互补输出
查看 rt_pwm_enable() 实现 可以发现一个关键的通道约定:通道为正数(0~n)表示使用普通输出引脚,通道为负数(0~-n)表示使用互补输出引脚(PWMN)。rt_pwm_enable()会先根据 channel 的符号下发PWMN_CMD_DISABLE(普通)或PWMN_CMD_ENABLE(互补),再统一下发PWM_CMD_ENABLE。rt_pwm_set()则在 内部 将 channel 归一化为正数后填入配置结构体,仅下发PWM_CMD_SET。
在 FinSH 中启用互补通道的典型写法是pwm enable -1(见下文命令说明),与底层约定一一对应。
底层驱动如何响应:以 STM32 为例
不同芯片的底层驱动只需实现一个control回调即可挂载到框架。以 STM32 PWM 驱动 为例:
static struct rt_pwm_ops drv_ops = { drv_pwm_control };驱动初始化时,在stm32_pwm_init()中根据BSP_USING_PWMx_CHy之类的宏配置启用对应通道,然后逐个调用rt_device_pwm_register()注册设备:
rt_device_pwm_register(&stm32_pwm_obj[i].pwm_device, stm32_pwm_obj[i].name, &drv_ops, &stm32_pwm_obj[i].tim_handle);drv_pwm_enable()内部把框架的通道号转换为 HAL 库通道号(0x04 * (configuration->channel - 1)),并根据complementary标志选择调用HAL_TIM_PWM_Start/Stop还是HAL_TIMEx_PWMN_Start/Stop。从源码结构可以推断:只要实现struct rt_pwm_ops中的control回调,任何平台的 PWM 定时器都可以接入 RT-Thread PWM 框架,这也是rt_pwm_set()等接口在所有 BSP 上行为一致的原因。
FinSH 调试命令
RT-Thread 的 FinSH 控制台提供了 PWM 的在线调试命令,方便在无代码改动的情况下验证硬件输出。框架层在 rt_drv_pwm.c 中以pwm为关键字导出了一组二级命令(pwm probe/enable/disable/get/set/phase/dead_time),使用前需先用pwm probe选定设备。
设置周期与占空比
设置 PWM 设备某个通道的周期和占空比,使用命令pwm_set pwm1 1 500000 5000。各参数含义:第一个参数是命令名,第二个参数是 PWM 设备名称,第三个参数是 PWM 通道,第四个参数是 PWM 周期(ns),第五个参数是脉冲宽度(ns)。
msh />pwm_set pwm1 1 500000 5000 msh />对应框架层的pwm set命令(参数顺序为 channel、period、pulse),例如pwm set 1 500000 5000。
使能通道
使能 PWM 设备某个通道,使用命令pwm_enable pwm1 1。第一个参数是命令名,第二个参数是 PWM 设备名称,第三个参数是 PWM 通道。
msh />pwm_enable pwm1 1 msh />对应框架层的pwm enable <channel>;当 channel 为负数时(如pwm enable -1)表示使能互补输出通道。
关闭通道
关闭 PWM 设备某个通道,使用命令pwm_disable pwm1 1。第一个参数是命令名,第二个参数是 PWM 设备名称,第三个参数是 PWM 通道。
msh />pwm_disable pwm1 1 msh />框架层pwm命令还支持pwm get <channel>回读周期、脉宽并打印实时占空比,以及pwm phase <channel> <phase>、pwm dead_time <channel> <dead_time>用于高级配置,可直接在终端查看命令用法(输入pwm回车即可看到 Usage 列表)。
完整示例:PWM 驱动 LED 呼吸灯
下面的示例代码是一个完整的 PWM 设备使用样例,核心步骤为:
- 查找 PWM 设备,获取设备句柄;
- 设置 PWM 周期和脉冲宽度;
- 使能 PWM 设备;
- 在 while 循环中每 50 毫秒修改一次脉冲宽度;
- 将 PWM 通道连接到 LED,可以看到 LED 由暗逐渐变亮,再由亮逐渐变暗。
/* * 程序清单:PWM 设备使用示例 * 该例程向控制终端导出 pwm_led_sample 命令 * 命令格式:pwm_led_sample * 程序功能:通过 PWM 设备控制 LED 灯的亮度,可以看到 LED 由暗逐渐变亮,再由亮逐渐变暗。 */ #include <rtthread.h> #include <rtdevice.h> #define PWM_DEV_NAME "pwm3" /* PWM 设备名称 */ #define PWM_DEV_CHANNEL 4 /* PWM 通道 */ struct rt_device_pwm *pwm_dev; /* PWM 设备句柄 */ static int pwm_led_sample(int argc, char *argv[]) { rt_uint32_t period, pulse, dir; period = 500000; /* 周期为 0.5ms,单位为纳秒 */ dir = 1; /* PWM 脉冲宽度值的增减方向 */ pulse = 0; /* PWM 脉冲宽度值,单位为纳秒 */ /* 查找设备 */ pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); if (pwm_dev == RT_NULL) { rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM_DEV_NAME); return -RT_ERROR; } /* 设置 PWM 周期和脉冲宽度默认值 */ rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); /* 使能设备 */ rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL); while (1) { rt_thread_mdelay(50); if (dir) { pulse += 5000; /* 从 0 值开始每次增加 5000ns */ } else { pulse -= 5000; /* 从最大值开始每次减少 5000ns */ } if (pulse >= period) { dir = 0; } if (0 == pulse) { dir = 1; } /* 设置 PWM 周期和脉冲宽度 */ rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); } } /* 导出到 msh 命令列表 */ MSH_CMD_EXPORT(pwm_led_sample, pwm sample);编译烧录后在 FinSH 控制台执行pwm_led_sample即可观察呼吸灯效果。该示例同样被收录在 PWM 设备头文件的 Doxygen 示例 中,作为官方 API 文档的一部分。
仓库中的其他 PWM 消费方与测试验证
PWM 框架不仅服务于应用层,也被 RT-Thread 多个组件复用,可作为二次开发的参考实现:
- LED 组件驱动 led-pwm.c:通过 PWM 调节 LED 亮度;
- 背光驱动 backlight-pwm.c:屏幕背光亮度控制;
- 温控风扇 thermal-cool-pwm-fan.c:根据温度调节风扇转速;
- 稳压器 regulator-pwm.c:电压调节。
在测试验证方面,PWM 单元测试用例 实现了“频率与占空比矩阵测试”:将period = 1e9 / frequency、pulse = period * duty / 10000的换算逻辑固化为测试点,支持固定输出、单次扫描、循环扫描三种输出模式,并通过rt_pwm_get()按容差回读校验周期与脉宽,覆盖rt_pwm_set、rt_pwm_enable、rt_pwm_disable的完整调用链。其默认配置(pwm_tc_config.h)中给出了从 500Hz 到 10KHz、占空比从 10% 到 75% 的波形点表,可直接作为评估 PWM 驱动精度的测试依据。
小结
RT-Thread 的 PWM 设备框架通过“应用层标准接口 + 驱动层单一 control 回调”的分层设计,屏蔽了不同芯片定时器差异。开发者只需掌握rt_device_find()+rt_pwm_set()+rt_pwm_enable()/rt_pwm_disable()四个接口,配合 FinSH 的pwm_set、pwm_enable、pwm_disable命令,即可完成从调试验证到应用落地的全流程。本文涉及的周期/脉宽换算(周期 0.5ms ↔ 500000ns ↔ 2KHz,占空比 = pulse/period)以及正数普通输出、负数互补输出的通道约定,是移植与排障时最容易踩坑、也最需要牢记的关键点。
- 操作系统
- 嵌入式
- 物联网
- 嵌入式OS
- RTOS
【免费下载链接】rt-thread
RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/
相关推荐
RT-Thread ADC 设备驱动使用指南:从模数转换原理到 rt_adc_read 实战采样
RT Thread ADC 设备驱动使用指南:从模数转换原理到 rt_adc_read 实战采样 导读 ADC(模拟数字转换器)是嵌入式系统中最常用的外设之一,
操作系统嵌入式物联网嵌入式OSRTOSRT-Thread 冷却设备(Cooling Devices)驱动详解:PWM 风扇、GPIO 风扇与 DVFS 降温
RT Thread 冷却设备(Cooling Devices)驱动详解:PWM 风扇、GPIO 风扇与 DVFS 降温 本文以 RT Thread 仓库中 co
操作系统嵌入式物联网嵌入式OSRTOSRT-Thread NUCLEO-F767ZI 开发板 BSP 详解:从快速上手到外设驱动配置实战
RT Thread NUCLEO F767ZI 开发板 BSP 详解:从快速上手到外设驱动配置实战 导读 本文以 bsp/stm32/stm32f767 st
操作系统嵌入式物联网嵌入式OSRTOS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考