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

2.2 设备目录和设备属性
查看 PWM 接口,pwmchip0(注意:此时的 pwmchip0 是 ff932000) 为屏幕的背光,系统默认开启:
root@luckfox:~# ls -l /sys/class/pwm
total 0
lrwxrwxrwx 1 root root 0 Jan 1 00:00 pwmchip0 -> ../../devices/platform/ff932000.pwm/pwm/pwmchip0当开启多个 PWM 设备树插件时,PWM 控制器值越小,系统分配的 pwmchip 越小。
在 /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
导出到用户空间或者取消用户空间的导出。
echo 0 > /sys/class/pwm/pwmchip0/export
echo 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/polarity
echo "inversed" > /sys/class/pwm/pwmchip0/pwm0/polarity使能和关闭PWM。
echo 1 > /sys/class/pwm/pwmchip1/pwm0/enable
echo 0 > /sys/class/pwm/pwmchip1/pwm0/enable设置占空比(小于等于 period )
echo 100000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 200000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 300000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 400000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 600000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 700000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 800000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 900000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 0 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
3. 使用python-periphery控制 PWM
使用 python-periphery库进行PWM输出的完整示例代码如下:
#!/usr/bin/python3
from periphery import PWM
import time
pwm = PWM(1, 0)
try:
pwm.frequency = 1000
pwm.duty_cycle = 0
pwm.polarity = "normal"
pwm.enable()
direction = 1
while True:
pwm.duty_cycle += 0.01 * direction
pwm.duty_cycle = round(pwm.duty_cycle, 2)
if pwm.duty_cycle == 1.0:
direction = -1
elif pwm.duty_cycle == 0.0:
direction = 1
time.sleep(0.05)
except KeyboardInterrupt:
pass
finally:
pwm.close()程序解析。
pwm = PWM(1, 0) # 开启 PWM chip 0, channel 0
pwm.frequency = 1000 # 设置1000Hz频率
pwm.polarity = "normal" # 设置 PWM 极性
pwm.duty_cycle = 0 # 初始占空比
pwm.enable() # 使能 PWM- 这个代码段实现了一个简单的 PWM 输出。通过循环不断调整 PWM 的占空比,以实现呼吸灯效果。
pwm.duty_cycle每次增加 0.01(可以根据需要调整),并使用round函数将其保留两位小数。在达到最大或最小值时,反转方向。并在每次更新 PWM 值后,通过time.sleep控制呼吸灯的速度。
while True:
pwm.duty_cycle += 0.01 * direction
pwm.duty_cycle = round(pwm.duty_cycle, 2)
if pwm.duty_cycle == 1.0:
direction = -1
elif pwm.duty_cycle == 0.0:
direction = 1
print(pwm.duty_cycle)
time.sleep(0.05)- 这个代码段实现了一个简单的 PWM 输出。通过循环不断调整 PWM 的占空比,以实现呼吸灯效果。
运行程序:
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/pwmchip1"
#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;
}
// 启用/禁用 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;
}
int main() {
int channel = 0; // 使用通道 0
// 导出 PWM
if (pwm_export(channel)) {
return 1;
}
// 设置周期
if (pwm_set_period(channel, PERIOD_NS)) {
return 1;
}
// 启用 PWM
if (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毫秒更新一次
}
}
// 禁用 PWM
if (pwm_enable(channel, 0)) {
return 1;
}
// 取消导出 PWM
if (pwm_unexport(channel)) {
return 1;
}
return 0;
}交叉编译运行程序,搭建交叉编译环境请参考《程序编译》或《GPIO》部分。
arm-none-linux-gnueabihf-gcc pwm.c -o pwm -lm
5. 设备树简介
设备文件路径位于
kernel-6.1/arch/arm/boot/dts/rk3506g-luckfox-lyra.dts,开启pwm的代码片段如下:/{
pwm_rockchip_test: pwm-rockchip-test {
compatible = "pwm-rockchip-test";
pwms = <&pwm0_4ch_0 0 25000 0>,
<&pwm0_4ch_1 0 25000 0>,
<&pwm0_4ch_2 0 25000 0>,
<&pwm0_4ch_3 0 25000 0>,
<&pwm1_8ch_0 0 25000 0>,
<&pwm1_8ch_1 0 25000 0>,
<&pwm1_8ch_2 0 25000 0>,
<&pwm1_8ch_3 0 25000 0>,
<&pwm1_8ch_4 0 25000 0>,
<&pwm1_8ch_5 0 25000 0>;
pwm-names = "pwm0_0",
"pwm0_1",
"pwm0_2",
"pwm0_3",
"pwm1_0",
"pwm1_1",
"pwm1_2",
"pwm1_3",
"pwm1_4",
"pwm1_5";
};
};
/***********************PWM********************/
&pwm0_4ch_0 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io12_pwm0_ch0>;
assigned-clocks = <&cru CLK_PWM0>;
assigned-clock-rates = <100000000>;
};
&pwm0_4ch_1 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io13_pwm0_ch1>;
assigned-clocks = <&cru CLK_PWM0>;
assigned-clock-rates = <100000000>;
};
&pwm0_4ch_2 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io9_pwm0_ch2>;
assigned-clocks = <&cru CLK_PWM0>;
assigned-clock-rates = <100000000>;
};
&pwm0_4ch_3 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io8_pwm0_ch3>;
assigned-clocks = <&cru CLK_PWM0>;
assigned-clock-rates = <100000000>;
};
&pwm1_8ch_0 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io4_pwm1_ch0>;
assigned-clocks = <&cru CLK_PWM1>;
assigned-clock-rates = <100000000>;
};
&pwm1_8ch_1 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io3_pwm1_ch1>;
assigned-clocks = <&cru CLK_PWM1>;
assigned-clock-rates = <100000000>;
};
&pwm1_8ch_2 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io2_pwm1_ch2>;
assigned-clocks = <&cru CLK_PWM1>;
assigned-clock-rates = <100000000>;
};
&pwm1_8ch_3 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io27_pwm1_ch3>;
assigned-clocks = <&cru CLK_PWM1>;
assigned-clock-rates = <100000000>;
};
&pwm1_8ch_4 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io26_pwm1_ch4>;
assigned-clocks = <&cru CLK_PWM1>;
assigned-clock-rates = <100000000>;
};
&pwm1_8ch_5 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&rm_io25_pwm1_ch5>;
assigned-clocks = <&cru CLK_PWM1>;
assigned-clock-rates = <100000000>;
};