Skip to main content

Linux PWM 驱动开发实践

1. PWM 驱动简析

PWM(脉宽调制,Pulse Width Modulation)是一种常用的信号调制方式,通过周期性地改变信号的高电平和低电平持续时间比例(即占空比),来调节输出的平均电压或功率。PWM 技术广泛应用于电机调速、LED 亮度调节、舵机控制、蜂鸣器音量调节等场景。

1.1. PWM 基本原理

PWM 信号是一种矩形脉冲波,其主要参数包括:

  • 周期(Period):一个完整高低电平循环所需的时间。

  • 频率(Frequency):单位时间内脉冲周期的个数,频率 = 1/周期。

  • 占空比(Duty Cycle):高电平持续时间占整个周期的比例,通常用百分比表示。


如上图,在确定周期的情况下,通过调节占空比,可以灵活控制负载的平均电压或功率。例如:

  • 占空比越高,LED 越亮,电机转速越快。

  • 占空比降低,LED 变暗,电机转速变慢。

PWM 的优点是控制精度高、能量损耗小、抗干扰能力强,因此在嵌入式系统和工业控制领域被广泛采用。

1.2. PWM 框架介绍


  • 用户空间:通过/sys/class/pwm接口访问PWM设备,与内核交互。

  • 内核空间

    pwm-core:管理PWM设备,提供API接口。

    pwm-driver:具体设备的驱动程序,与硬件交互。

    pwm-users:使用PWM核心功能的模块,如背光控制。

  • 硬件层:PWMn,具体的PWM硬件设备。

1.3. CONFIG 介绍

CONFIG_PWM 此为内核平台pwm框架提供支持,支持k1 pwm驱动情况下,默认为 Y

Symbol: PWM [=y]
Device Drivers
-> Pulse-Width Modulation (PWM) Support (PWM [=y])

在支持平台层pwm框架后,配置CONFIG_PWM_PXA为Y,默认支持k1 pwm驱动

Symbol: PWM_PXA [=y]
->PXA PWM support (PWM_PXA [=y])

接下来演示如何配置,我们可以通过 menuconfig 查看, 在 path/to/your/linux-6.6 目录下输入如下命令:

make menuconfig

移动键盘下键找到 Device Drivers 回车。


移动键盘下键找到 Pulse-Width Modulation 回车。


移动键盘下键看到 PXA PWM support 已配置。


2. 设备树下的PWM控制器节点

Spacemit K1-X 芯片有 20 路主域 PWM 输出(pwm0~pwm19)和 10 路 RCPU 域 PWM 输出(rpwm0~rpwm9),共 30 个 PWM 控制器节点。每个 PWM 控制器在设备树 path/to/your/linux-6.6/arch/riscv/boot/dts/spacemit/k1-x.dtsi 中都有独立节点,主域和 RCPU 域节点结构基本一致,仅 reg 地址、时钟、复位资源等参数不同。

以 PWM4 为例,主域 PWM 控制器节点如下:

pwm4: pwm@d401b000 {
compatible = "spacemit,k1x-pwm";
reg = <0x0 0xd401b000 0x0 0x10>;
#pwm-cells = <1>; // 1 个参数,通常为 PWM 通道号
clocks = <&ccu CLK_PWM4>;
resets = <&reset RESET_PWM4>;
k1x,pwm-disable-fd; // 关闭死区功能(如有)
status = "disabled"; // 默认禁用,实际使用时需使能
};

在具体板级设备树 path/to/your/linux-6.6/arch/riscv/boot/dts/spacemit/k1-x_MUSE-Pi-Pro.dts 中使能 PWM4 并配置引脚复用:

&pwm4 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm4_1>;
status = "okay";
};

引脚复用配置path/to/your/linux-6.6/arch/riscv/boot/dts/spacemit/k1-x_pinctrl.dtsi

pinctrl_pwm4_1: pwm4_1_grp {
pinctrl-single,pins = <
K1X_PADCONF(GPIO_34, MUX_MODE3, (EDGE_NONE | PULL_UP | PAD_1V8_DS2)) /* pwm4 */
>;
};

3. PWM 子系统

Linux 内核提供了个 PWM 子系统框架,编写 PWM 驱动的时候一定要符合这个框架。PWM子系统的核心是 pwm_chip 结构体,定义在文件 path/to/your/linux-6.6/include/linux/pwm.h 中,定义如下:

struct pwm_chip {
struct device *dev;
const struct pwm_ops *ops;
int base;
unsigned int npwm;

struct pwm_device * (*of_xlate)(struct pwm_chip *chip,
const struct of_phandle_args *args);
unsigned int of_pwm_n_cells;

/* only used internally by the PWM framework */
struct list_head list;
struct pwm_device *pwms;
};

pwm_ops 结构体就是 PWM 外设的各种操作函数集合,编写 PWM 外设驱动的时 候需要开发人员实现。pwm_ops 结构体也定义在 pwm.h 头文件中,定义如下:

struct pwm_ops {
int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_capture *result, unsigned long timeout);
int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state);
int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state);
struct module *owner;
};

apply 函数用于配置、启用和禁用 PWM 信号。apply 函数的参数 state 包含了所有需要的配置信息,包括占空比、周期、极性等。

struct pwm_state {
u64 period;
u64 duty_cycle;
enum pwm_polarity polarity;
bool enabled;
bool usage_power;
};

PWM 子系统驱动的核心就是初始化 pwm_chip 结构体各成员变量,然后向内核注册初始化完成以后的 pwm_chip 。这里就要用到 pwmchip_add 函数,此函数定义在 path/to/your/linux-6.6/drivers/pwm/core.c 文件中,函数原型如下:

static void of_pwmchip_add(struct pwm_chip *chip)

chip:要向内核注册的 pwm_chip。

卸载 PWM 驱动的时候需要将前面注册的 pwm_chip 从内核移除掉,这里要用到 pwmchip_remove 函数,函数原型如下:

static void of_pwmchip_remove(struct pwm_chip *chip)

chip:要移除的 pwm_chip。

4. PWM 驱动源码分析

4.1. 匹配关系

path/to/your/linux-6.6/drivers/pwm/pwm-pxa.c 中,我们找到驱动的匹配表 (pwm_of_match):

#ifdef CONFIG_SOC_SPACEMIT_K1X
{ .compatible = "spacemit,k1x-pwm", .data = &pwm_id_table[0]},
#endif

驱动注册:

static struct platform_driver pwm_driver = {
.driver = {
.name = "pxa25x-pwm",
.of_match_table = pwm_of_match,
...
},
.probe = pwm_probe,
.id_table = pwm_id_table,
};

注册到内核:

late_initcall(k1x_pwm_driver_init);

static int k1x_pwm_driver_init(void)
{
return platform_driver_register(&pwm_driver);
}

4.2. probe 函数

匹配到 compatible 后,内核会调用:

static int pwm_probe(struct platform_device *pdev)

probe 函数完成:

  • 资源申请(内存映射、时钟、reset 等)

  • 初始化 pwm_chip 结构体

  • 设置 of_xlate 和 of_pwm_n_cells

  • 调用 devm_pwmchip_add 注册 PWM 控制器

5. PWM 驱动开发实例与实验流程

说明:本次实验采用**GPIO34 (PWM4)**引脚为例。

PWM 驱动和设备树配置就不需要我们再编写了,MUSE Pi Pro 已经写好了,前面我们也已经进行过分析。

将 MUSE Pi Pro 开发板排针上的 GPIO34 (右排倒数第5个) 引脚连接到示波器上,如下图:


通过示波器来查看 PWM 波形图。我们可以直接在用户层来配置 PWM,进入目录 /sys/class/pwm 中,如下图所示:


已配置好的 pwmchip0 对应 PWM4,pwmchip1 对应 R_PWM7。

5.1. 调出 pwmchip0 的 pwm0 子目录

首先需要调出 pwmchip0 下的 pwm0 目录,否则后续就没法操作了,输入如下命令:

echo 0 | sudo tee /sys/class/pwm/pwmchip0/export > /dev/null

执行完成会在 pwmchip0 目录下生成一个名为 pwm0 的子目录,如下图所示:


5.2. 设置 PWM4 的频率

注意,这里设置的是周期值,单位为 ns,比如 20KHz 频率的周期就是 50000ns,输入如下命令:

echo 50000 | sudo tee /sys/class/pwm/pwmchip0/pwm0/period > /dev/null

5.3. 设置 PWM4 的占空比

这里不能直接设置占空比,而是设置的一个周期的 ON 时间,也就是高电平时间,比如 20KHz 频率下 50% 占空比的 ON 时间就是 25000,输入如下命令:

echo 25000 | sudo tee /sys/class/pwm/pwmchip0/pwm0/duty_cycle > /dev/null

5.4. 使能 PWM4

输入如下命令使能 PWM4:

echo 1 | sudo tee /sys/class/pwm/pwmchip0/pwm0/enable > /dev/null

设置完成使用示波器查看波形是否正确,正确的话如下图所示,可以看到一个周期内高低电平所占时间几乎一致。


或者使用万用表测量该引脚的电压,对应约为1.64V (约为3.3V的二分之一) ,如下图所示。