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/pwmtotal 0lrwxrwxrwx 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/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 timepwm = PWM(1, 0)try:pwm.frequency = 1000pwm.duty_cycle = 0pwm.polarity = "normal"pwm.enable()direction = 1while True:pwm.duty_cycle += 0.01 * directionpwm.duty_cycle = round(pwm.duty_cycle, 2)if pwm.duty_cycle == 1.0:direction = -1elif pwm.duty_cycle == 0.0:direction = 1time.sleep(0.05)except KeyboardInterrupt:passfinally:pwm.close() -
程序解析。
pwm = PWM(1, 0) # 开启 PWM chip 0, channel 0pwm.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 * directionpwm.duty_cycle = round(pwm.duty_cycle, 2)if pwm.duty_cycle == 1.0:direction = -1elif pwm.duty_cycle == 0.0:direction = 1print(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;}// 启用/禁用 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;} -
交叉编译运行程序,搭建交叉编译环境请参考《程序编译》或《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>;};