news 2026/10/7 8:22:44

RT-Thread PWM 设备驱动详解:从占空比原理到 rt_pwm_set/Enable/Disable 实战开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RT-Thread PWM 设备驱动详解:从占空比原理到 rt_pwm_set/Enable/Disable 实战开发
  • 操作系统
  • 嵌入式
  • 物联网
  • 嵌入式OS
  • RTOS

【免费下载链接】rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/

项目地址:https://gitcode.com/gh_mirrors/rt/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);
参数描述
devicePWM 设备句柄
channelPWM 通道
periodPWM 周期(ns)
pulsePWM 脉冲宽度时间(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);
参数描述
devicePWM 设备句柄
channelPWM 通道
返回——
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);
参数描述
devicePWM 设备句柄
channelPWM 通道
返回——
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 设备使用样例,核心步骤为:

  1. 查找 PWM 设备,获取设备句柄;
  2. 设置 PWM 周期和脉冲宽度;
  3. 使能 PWM 设备;
  4. 在 while 循环中每 50 毫秒修改一次脉冲宽度;
  5. 将 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/

项目地址:https://gitcode.com/gh_mirrors/rt/rt-thread
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 8:22:02

搞懂热分层,才能看懂高大空间空调怎么选

高大空间取暖难&#xff1f;你可能忽略了“热分层”这个隐形对手走进一个层高8米的厂房或仓库&#xff0c;常常发现屋顶暖烘烘&#xff0c;地面却冷得跺脚。许多管理者第一反应是“空调功率不够”&#xff0c;于是加装更多设备&#xff0c;结果电费飙升&#xff0c;体感却改善有…

作者头像 李华
网站建设 2026/10/7 8:20:27

ponytail 插件怎么用?轻量级代码片段管理与快速注入工具实战指南

1. 从“ponytail”这个词说起&#xff1a;它到底是什么第一次看到“ponytail”这个项目标题&#xff0c;很多人脑子里蹦出来的第一反应大概是发型——马尾辫。没错&#xff0c;字面意思确实是马尾辫&#xff0c;但作为一个项目名、一个插件名&#xff0c;它显然不是让你去研究怎…

作者头像 李华
网站建设 2026/10/7 8:20:02

AnyPS5存档系统完全解析:libSceSaveData如何在PC上读写PS5存档文件

AnyPS5存档系统完全解析&#xff1a;libSceSaveData如何在PC上读写PS5存档文件 【免费下载链接】AnyPS5 Tool for automatic PS5 executables porting to Linux and Windows 项目地址: https://gitcode.com/GitHub_Trending/an/AnyPS5 如果你想在电脑上玩移植的PS5游戏&…

作者头像 李华
网站建设 2026/10/7 8:18:39

[HNCTF 2022 Week1]fmtstrre

格式化字符串漏洞利用读取特定地址内容 原思路的wp地址&#xff1a;https://www.nssctf.cn/note/set/13425里面有讲解位置参数的用法平台&#xff1a;NSSCTF 方向&#xff1a;Pwn 知识点&#xff1a;格式化字符串 难度&#xff1a;入门一、信息获取 可以先checksec一下NX、SHST…

作者头像 李华