跳到主要内容

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 设备目录和设备属性

  1. 查看 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
  2. 当开启多个 PWM 设备树插件时,PWM 控制器值越小,系统分配的 pwmchip 越小。

  3. 在 /sys/class/pwm/pwmchipN/pwm0/ 设备目录下有多种属性,可以通过这些属性控制,从而实现对 PWM 的控制。

    root@luckfox:/# ls /sys/class/pwm/pwmchip0/pwm0/
    capture duty_cycle enable period polarity power uevent
    • enable:用于启用或禁用PWM通道
    • period:用于设置PWM信号的周期时间
    • duty_cycle:用于设置PWM信号的占空比
    • polarity:用于配置PWM信号的极性
    • power/control:用于启用或禁用PWM通道的电源管理(通常用于省电模式)

2.3 控制 PWM

  1. 导出到用户空间或者取消用户空间的导出。

    echo 0 > /sys/class/pwm/pwmchip0/export
    echo 0 > /sys/class/pwm/pwmchip0/unexport
  2. 设置 PWM 周期单位为ns,比如 1KHz 频率的周期就是 1000000ns(注意,在任何的情况下都得保证 period 的值大于等于 duty_cycle 的值)。

    echo 1000000 > /sys/class/pwm/pwmchip10/pwm0/period
  3. 设置PWM极性正常或翻转。

    echo "normal" > /sys/class/pwm/pwmchip0/pwm0/polarity
    echo "inversed" > /sys/class/pwm/pwmchip0/pwm0/polarity
  4. 使能和关闭PWM。

    echo 1 > /sys/class/pwm/pwmchip1/pwm0/enable
    echo 0 > /sys/class/pwm/pwmchip1/pwm0/enable
  5. 设置占空比(小于等于 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

  1. 使用 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()
  2. 程序解析。

    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)
  1. 运行程序:

    chmod +x pwm.py
    ./pwm.py
  2. 可以看到对应 Pin12 号管脚的数据(呼吸灯)。

4. 控制PWM(C程序)

除了可以通过 shell 脚本和 Python 库操作 sysfs 控制 PWM 引脚外,还可以使用 C 语言操作 sysfs 来实现同样的功能。

  1. 导出用户空间。

    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;
    }
  2. 取消用户空间导出。

    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;
    }
  3. 设置 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;
    }
  4. 设置占空比。

    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;
    }
  5. 使能 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;
    }
  6. 完整示例程序。

    #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;
    }
  7. 交叉编译运行程序,搭建交叉编译环境请参考《程序编译》或《GPIO》部分。

    arm-none-linux-gnueabihf-gcc pwm.c -o pwm -lm

5. 设备树简介

  1. 设备文件路径位于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>;
    };