PWM
1. PWM 简介
PWM,全称为脉冲宽度调制(Pulse Width Modulation),是一种通过控制信号的脉冲宽度来实现模拟信号输出的技术。它常用于嵌入式系统和电子设备中,用于控制电机速度、LED 亮度、音频信号生成等应用。在 Linux 系统中,PWM 设备通常通过 sysfs 文件系统进行管理和配置,其设备目录通常位于 /sys/class/pwm/ 目录下。
2. 控制PWM(Shell)
2.1 引脚分布
Luckfox Omni3576 引脚图:

2.2 设备目录和设备属性
-
查看 PWM 接口,pwmchip0(注意:此时的 pwmchip0 是 2ade2000) 为屏幕的背光,系统默认开启:
root@luckfox:/# ls -l /sys/class/pwmtotal 0lrwxrwxrwx 1 root root 0 Aug 25 23:07 pwmchip0 -> ../../devices/platform/2ade0000.pwm/pwm/pwmchip0 -
当开启多个 PWM 设备树插件时,PWM 控制器值越小,系统分配的 pwmchip 越小。(此时默认的屏幕背光变成了 pwmchip1)。
root@luckfox:/# ls -l /sys/class/pwmtotal 0lrwxrwxrwx 1 root root 0 Aug 26 00:59 pwmchip0 -> ../../devices/platform/2ade0000.pwm/pwm/pwmchip0lrwxrwxrwx 1 root root 0 Aug 26 00:59 pwmchip1 -> ../../devices/platform/2ade2000.pwm/pwm/pwmchip1名称 设备树定义名称 地址 pwm2m0_ch0 pwm2_8ch_0 2ade0000 pwm2m0_ch2 pwm2_8ch_2 2ade2000 -
在 /sys/class/pwm/pwmchipN/pwm0/ 设备目录下有多种属性,可以通过这些属性控制,从而实现对 PWM 的控制。
root@luckfox:/# ls /sys/class/pwm/pwmchip0/pwm0/capture duty_cycle enable period polarity power ueventenable:用于启用或禁用PWM通道period:用于设置PWM信号的周期时间duty_cycle:用于设置PWM信号的占空比polarity:用于配置PWM信号的极性power/control:用于启用或禁用PWM通道的电源管理(通常用于省电模式)
2.3 控制 PWM
-
导出到用户空间或者取消用户空间的导出(以 GPIO0_D3_d 为例)。
echo 0 > /sys/class/pwm/pwmchip0/exportecho 0 > /sys/class/pwm/pwmchip0/unexport -
设置 PWM 周期单位为ns,比如 1KHz 频率的周期就是 1000000ns(注意,在任何的情况下都得保证 period 的值大于等于 duty_cycle 的值)。
echo 1000000 > /sys/class/pwm/pwmchip10/pwm0/period -
设置PWM极性正常或翻转。
echo "normal" > /sys/class/pwm/pwmchip0/pwm0/polarityecho "inversed" > /sys/class/pwm/pwmchip0/pwm0/polarity -
使能和关闭PWM。
echo 1 > /sys/class/pwm/pwmchip1/pwm0/enableecho 0 > /sys/class/pwm/pwmchip1/pwm0/enable -
设置占空比(小于等于 period )
echo 100000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 200000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 300000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 400000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 600000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 700000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 800000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 900000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 0 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
3. 使用python-periphery控制 PWM
-
使用 python-periphery库进行PWM输出的完整示例代码如下:
#!/usr/bin/python3from periphery import PWMimport timeimport numpy as nppwm = PWM(0, 0)pwm.frequency = 100pwm.polarity = "normal"pwm.duty_cycle = 0pwm.enable()try:while True:for i in np.linspace(0, 2 * np.pi, 100):duty_cycle = (np.sin(i) + 1) / 2pwm.duty_cycle = duty_cycletime.sleep(0.02)except KeyboardInterrupt:pwm.disable()pwm.close() -
程序解析。
pwm = PWM(0, 0) # 开启 PWM chip 0, channel 0pwm.frequency = 100 # 设置100Hz频率pwm.polarity = "normal" # 设置 PWM 极性pwm.duty_cycle = 0 # 初始占空比pwm.enable() # 使能 PWM- 创建一个占空比循环,逐步增加占空比到最大值,然后再逐步减少到最小值。这个循环可以使用
np.sin()函数来模拟类似的曲线,从而产生平滑的亮度变化。
while True:# 生成一个从 0 到 2π 的平滑变化的数组,这个范围用于控制 sin() 函数的输入。for i in np.linspace(0, 2 * np.pi, 100):# (duty_cycle)在 0 和 1 之间平滑变化duty_cycle = (np.sin(i) + 1) / 2pwm.duty_cycle = duty_cycletime.sleep(0.02)- y = sinx 在
0和2Π区间输出值会在-1和1之间变化,由于我们希望占空比(duty_cycle)在0和1之间平滑变化,(np.sin(i) + 1) / 2的输出范围:0~1(通过除以 2,将结果限制在 0 到 1 之间)。
- 创建一个占空比循环,逐步增加占空比到最大值,然后再逐步减少到最小值。这个循环可以使用
-
运行程序:
chmod +x pwm.py./pwm.py -
可以看到对应 Pin12 号管脚的数据(呼吸灯)。

4. 控制PWM(C程序)
除了可以通过 shell 脚本和 Python 库操作 sysfs 控制 PWM 引脚外,还可以使用 C 语言操作 sysfs 来实现同样的功能。
-
导出用户空间。
int pwm_export(int channel) {FILE *pwm_export = fopen(PWM_PATH "/export", "w");if (!pwm_export) {perror("Failed to open PWM export");return 1;}fprintf(pwm_export, "%d", channel);fclose(pwm_export);return 0;} -
取消用户空间导出。
int pwm_unexport(int channel) {FILE *pwm_unexport = fopen(PWM_PATH "/unexport", "w");if (!pwm_unexport) {perror("Failed to open PWM unexport");return 1;}fprintf(pwm_unexport, "%d", channel);fclose(pwm_unexport);return 0;} -
设置 PWM 周期。
int pwm_set_period(int channel, int period_ns) {char period_path[128];snprintf(period_path, sizeof(period_path), PWM_PATH "/pwm%d/period", channel);FILE *period_file = fopen(period_path, "w");if (!period_file) {perror("Failed to open PWM period");return 1;}fprintf(period_file, "%d", period_ns);fclose(period_file);return 0;} -
设置占空比。
int pwm_set_duty_cycle(int channel, int duty_cycle_ns) {char duty_cycle_path[128];snprintf(duty_cycle_path, sizeof(duty_cycle_path), PWM_PATH "/pwm%d/duty_cycle", channel);FILE *duty_cycle_file = fopen(duty_cycle_path, "w");if (!duty_cycle_file) {perror("Failed to open PWM duty cycle");return 1;}fprintf(duty_cycle_file, "%d", duty_cycle_ns);fclose(duty_cycle_file);return 0;} -
使能 PWM 。
int pwm_enable(int channel, int enable) {char enable_path[128];snprintf(enable_path, sizeof(enable_path), PWM_PATH "/pwm%d/enable", channel);FILE *enable_file = fopen(enable_path, "w");if (!enable_file) {perror("Failed to open PWM enable");return 1;}fprintf(enable_file, "%d", enable);fclose(enable_file);return 0;} -
完整示例程序。
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <math.h> // 用于 sin() 函数#define PWM_PATH "/sys/class/pwm/pwmchip10"#define PERIOD_NS 1000000#define MAX_DUTY_CYCLE_NS 1000000#define MIN_DUTY_CYCLE_NS 0#define PI 3.141592653589793// 导出 PWM 通道int pwm_export(int channel) {FILE *pwm_export = fopen(PWM_PATH "/export", "w");if (!pwm_export) {perror("Failed to open PWM export");return 1;}fprintf(pwm_export, "%d", channel);fclose(pwm_export);return 0;}// 取消导出 PWM 通道int pwm_unexport(int channel) {FILE *pwm_unexport = fopen(PWM_PATH "/unexport", "w");if (!pwm_unexport) {perror("Failed to open PWM unexport");return 1;}fprintf(pwm_unexport, "%d", channel);fclose(pwm_unexport);return 0;}// 设置 PWM 周期int pwm_set_period(int channel, int period_ns) {char period_path[128];snprintf(period_path, sizeof(period_path), PWM_PATH "/pwm%d/period", channel);FILE *period_file = fopen(period_path, "w");if (!period_file) {perror("Failed to open PWM period");return 1;}fprintf(period_file, "%d", period_ns);fclose(period_file);return 0;}// 设置占空比int pwm_set_duty_cycle(int channel, int duty_cycle_ns) {char duty_cycle_path[128];snprintf(duty_cycle_path, sizeof(duty_cycle_path), PWM_PATH "/pwm%d/duty_cycle", channel);FILE *duty_cycle_file = fopen(duty_cycle_path, "w");if (!duty_cycle_file) {perror("Failed to open PWM duty cycle");return 1;}fprintf(duty_cycle_file, "%d", duty_cycle_ns);fclose(duty_cycle_file);return 0;}// 启用/禁用 PWMint pwm_enable(int channel, int enable) {char enable_path[128];snprintf(enable_path, sizeof(enable_path), PWM_PATH "/pwm%d/enable", channel);FILE *enable_file = fopen(enable_path, "w");if (!enable_file) {perror("Failed to open PWM enable");return 1;}fprintf(enable_file, "%d", enable);fclose(enable_file);return 0;}int main() {int channel = 0; // 使用通道 0// 导出 PWMif (pwm_export(channel)) {return 1;}// 设置周期if (pwm_set_period(channel, PERIOD_NS)) {return 1;}// 启用 PWMif (pwm_enable(channel, 1)) {return 1;}// 使用 sin(x) 函数生成占空比的平滑变化double step_size = (2 * PI) / 100.0;while (1) {for (double i = 0; i <= 2 * PI; i += step_size) {// 使用 sin(x) 生成从 0 到 1 的占空比double duty_cycle_ratio = (sin(i) + 1) / 2;int duty_cycle_ns = (int)(duty_cycle_ratio * MAX_DUTY_CYCLE_NS);// 设置占空比if (pwm_set_duty_cycle(channel, duty_cycle_ns)) {return 1;}usleep(20000); // 控制呼吸灯速度,20毫秒更新一次}}// 禁用 PWMif (pwm_enable(channel, 0)) {return 1;}// 取消导出 PWMif (pwm_unexport(channel)) {return 1;}return 0;} -
编译运行程序。
gcc pwm.c -o pwm -lm
5. 设备树简介
-
PWM DTS 源文件在
kernel-6.10/arch/arm64/boot/dts/rockchip/rk3576.dtsi已经定义,我们可以直接调用。pwm0_2ch_0: pwm@27330000 {compatible = "rockchip,rk3576-pwm";reg = <0x0 0x27330000 0x0 0x1000>;interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;#pwm-cells = <3>;pinctrl-names = "active";pinctrl-0 = <&pwm0m0_ch0>;clocks = <&cru CLK_PMU1PWM>, <&cru PCLK_PMU1PWM>;clock-names = "pwm", "pclk";status = "disabled";}; -
设备文件路径位于
kernel-6.1/arch/arm64/boot/dts/rockchip/luckfox-omni3576.dts,开启pwm2m0_ch0的代码片段如下:&pwm2_8ch_0 {status = "okay";pinctrl-0 = <&pwm2m0_ch0>;};