15-Linux驱动开发-PWM子系统

底层的寄存器操作(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。
相关推荐
好奇的菜鸟42 分钟前
在WSL Ubuntu 24中设置root密码并默认使用root登录
linux·运维·ubuntu
礼拜天没时间.43 分钟前
《Grafana 企业级可视化监控实战指南:从安装、配置到智能告警》:Grafana 简介
linux·运维·信息可视化·zabbix·grafana·监控
礼拜天没时间.1 小时前
《Grafana 企业级可视化监控实战指南:从安装、配置到智能告警》:Grafana 环境搭建
linux·运维·信息可视化·zabbix·grafana·监控
边疆.1 小时前
【Linux】基础IO
linux·运维·服务器·io·文件操作·重定向
e***58231 小时前
Linux部署Redis集群
linux·运维·redis
n***4431 小时前
Linux下MySQL的简单使用
linux·mysql·adb
e***71671 小时前
Linux下安装Nginx服务及systemctl方式管理nginx详情
linux·运维·nginx
nukix1 小时前
Linux 查看应用端口情况
linux·运维·服务器
s***87271 小时前
linux centos8 安装redis 卸载redis
linux·运维·redis