底层的寄存器操作(PWM控制器驱动)通常由芯片厂商(如 NXP, Rockchip 等)写好了,但作为驱动开发者,我们需要知道如何获取 PWM 设备、配置参数以及开启输出。
Linux PWM 子系统架构
在 Linux 中,PWM 子系统采用了标准的 Consumer (消费者) / Provider (提供者) 模型。
html
Provider (控制器驱动):
由芯片原厂提供(通常在 drivers/pwm/ 目录下)。
它直接读写 SoC 的寄存器,负责产生实际的波形。
不需要写这部分代码,只需要确保内核配置中开启了对应的 SoC PWM 支持。
Core (子系统核心):
内核提供的中间层,管理 PWM 设备列表,提供统一的 API。
Consumer (消费者驱动):
功能是:向核心层申请一个 PWM 通道,设置周期和占空比,然后让它工作。
应用场景: 背光驱动 (pwm-backlight)、蜂鸣器驱动 (pwm-beeper)、风扇控制等。
参数
编写 PWM 驱动时,必须通过代码精确控制以下三个核心参数。注意:Linux PWM 子系统通常使用纳秒 (ns) 作为时间单位。
1.Period (周期): 一个完整波形的时间长度。
T=1/FrequencyT = 1 / FrequencyT=1/Frequency
2.Duty Cycle (占空比): 一个周期内高电平持续的时间。
它决定了灯的亮度或电机的速度。
范围:0 到 Period。
3.Polarity (极性):
Normal: 占空比时间内为高电平。
Inversed: 占空比时间内为低电平。
核心 API 接口
html
A. 获取 PWM 设备
struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
功能: 从设备树中获取与当前设备绑定的 PWM 句柄。
参数: dev 是当前驱动的设备结构体;con_id 对应设备树中的名称(如果设备树里只有一个 PWM,通常填 NULL)。
返回值: 成功返回 pwm_device 指针,后续操作都靠它。
B. 配置 PWM 波形
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
功能: 设置 PWM 的周期和占空比。
参数 period_ns: 周期(纳秒)。
参数 duty_ns: 占空比时间(纳秒)。
C. 使能与禁止
int pwm_enable(struct pwm_device *pwm): 开启 PWM 输出,硬件引脚开始产生波形。配置好参数后,必须调用此函数才会有波形。
void pwm_disable(struct pwm_device *pwm): 停止输出。
D. 释放
void pwm_free(struct pwm_device *pwm): 释放 PWM 资源。如果使用了 devm_ 开头的函数(资源托管),通常不需要手动释放。
相关代码
设备树代码:
c
/dts-v1/;
/plugin/;
#include <dt-bindings/pinctrl/stm32-pinfunc.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/mfd/st,stpmic1.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
/ {
fragment@0 {
target = <&timers2>;
__overlay__ {
status = "okay";// 启用该定时器
/delete-property/dmas;
/delete-property/dma-names;
pwm2: pwm {
/* configure PWM pins on TIM2_CH3 */
pinctrl-0 = <&pwm2_pins_a>;
pinctrl-1 = <&pwm2_sleep_pins_a>;
pinctrl-names = "default", "sleep";
/* enable PWM on TIM2 */
#pwm-cells = <2>;// 声明引用该 PWM 时需要 2 个参数:<通道号 周期>
status = "okay";
};
};
};
fragment@1 {
target = <&pinctrl>;
__overlay__ {
/* select TIM2_CH3 alternate function 1 on 'PB10' */
pwm2_pins_a: pwm2-0 {
pins {
/* 将 PB10 引脚复用为 AF1 (Alternate Function 1) */
/* 在 STM32MP1 手册中,PB10 的 AF1 对应 TIM2_CH3 */
pinmux = <STM32_PINMUX('B', 10, AF1)>;
bias-pull-down;
drive-push-pull;
slew-rate = <0>;// 压摆率
};
};
/* configure 'PB10' as analog input in low-power mode */
pwm2_sleep_pins_a: pwm2-sleep-0 {
pins {
pinmux = <STM32_PINMUX('B', 10, ANALOG)>;
};
};
};
};
fragment@2 {
target-path="/";
__overlay__{
pwm_test {
compatible = "pwm_test";
status = "okay";
front {
pwm-names = "test_tim2_ch3_pwm2";
/* 参数含义:<通道号 默认周期(ns)> */
/* 代表 Channel 3 (通常索引从0开始: 0=CH1, 1=CH2, 2=CH3) */
pwms = <&pwm2 2 1000000>;
};
};
};
};
};
html
Fragment@0:激活 PWM 控制器(Provider)
配置的是 PWM 提供者 (Provider)。它基于 timers2 硬件资源,暴露出了一个 pwm2 节点供其他设备使用。
Fragment@1:配置引脚复用 (Pinmux)
确立了物理世界的连接。必须查阅芯片手册确认 PB10 的 AF1 功能确实是 TIM2_CH3。
Fragment@2:创建测试设备(Consumer)
驱动程序将匹配这个 pwm_test 节点,并读取里面的 pwms 属性来获取硬件控制权。
驱动代码:
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/leds.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/leds_pwm.h>
#include <linux/slab.h>
struct pwm_device *pwm_test; //定义pwm设备结构体
/*精简版 prob函数*/
static int led_pwm_probe_new(struct platform_device *pdev)
{
int ret = 0;
struct device_node *child; // 保存子节点
struct device *dev = &pdev->dev;
printk("match success \n");
child = of_get_next_child(dev->of_node, NULL);
if (child)
{
pwm_test = devm_of_pwm_get(dev, child, NULL);
if (IS_ERR(pwm_test))
{
printk(KERN_ERR" pwm_test,get pwm error!!\n");
return -1;
}
}
else
{
printk(KERN_ERR" pwm_test of_get_next_child error!!\n");
return -1;
}
/*配置频率200KHz=1e9/5000 占空比80%=1000/5000*/
/*
duty_ns (高电平时间) = 1000 ns
period_ns (周期时间) = 5000 ns
*/
pwm_config(pwm_test, 1000, 5000);
/*反相 频率200KHz 占空比20%*/
pwm_set_polarity(pwm_test, PWM_POLARITY_INVERSED);
pwm_enable(pwm_test);
return ret;
}
static int led_pwm_remove(struct platform_device *pdev)
{
pwm_config(pwm_test, 0, 5000);
return 0;
}
static const struct of_device_id of_pwm_leds_match[] = {
{.compatible = "pwm_test"},
{},
};
static struct platform_driver led_pwm_driver = {
.probe = led_pwm_probe_new,
.remove = led_pwm_remove,
.driver = {
.name = "test_tim2_ch3_pwm2",
.of_match_table = of_pwm_leds_match,
},
};
/*
*驱动初始化函数
*/
static int __init pwm_leds_platform_driver_init(void)
{
return platform_driver_register(&led_pwm_driver);
}
/*
*驱动注销函数
*/
static void __exit pwm_leds_platform_driver_exit(void)
{
printk(KERN_ERR " pwm_leds_exit\n");
/*注销平台设备*/
platform_driver_unregister(&led_pwm_driver);
}
module_init(pwm_leds_platform_driver_init);
module_exit(pwm_leds_platform_driver_exit);
MODULE_LICENSE("GPL");
执行流程:
html
1.加载设备树 (DTS):内核解析 overlay,配置 STM32 的 TIM2,将 PB10 引脚设为 PWM 模式,并在系统设备列表中生成名为 pwm_test 的平台设备。
2.加载驱动 (Driver):module_init 注册 platform_driver。
3.匹配 (Probe):内核发现 pwm_test 设备和驱动的 compatible 字符串匹配,调用 led_pwm_probe_new。
4.获取资源:驱动通过 of_get_next_child 找到设备树里的 front 节点,并据此申请 PWM 控制权。
5.硬件配置:驱动向寄存器写入参数:周期设为 5000ns,比较值设为 1000ns。设置极性位翻转。开启 Timer 的 Enable 位。
6.物理现象:用示波器测量 PB10 引脚,你会看到一个频率为 200kHz,高电平占比 80% 的方波。
7.卸载 (Remove):当卸载模块时,led_pwm_remove 被调用,pwm_config(pwm_test, 0, 5000) 将占空比设为 0。