10. PWM 操作指南

本文档说明在 CV184x 平台上通过 sysfs 配置与使用 PWM、引脚复用方法以及命令行与用户态程序示例。

10.1. 操作准备

1. 内核要求

  • 使用 SDK 提供的内核,并确保内核配置中已开启 CONFIG_PWM (驱动随内核自动加载,无需 insmod)。

2. 引脚复用

PWM 信号需通过 pinmux 将对应 GPIO 引脚配置为 PWM 功能。在目标板上执行 cvi_pinmux 命令时,请以实际使用的 PWM 通道及原理图为准。

示例:将 PWM6 所用引脚从 JTAG 切到 PWM

cvi_pinmux -r JTAG_CPU_TCK
cvi_pinmux -w JTAG_CPU_TCK/PWM_6

示例:将 PWM14 所用引脚从 I2C 切到 PWM

cvi_pinmux -r IIC2_SCL
cvi_pinmux -w IIC2_SCL/PWM_14

10.2. 操作流程

使用方式

  • 在控制台通过 shell 命令操作 sysfs 接口;

  • 或在用户态/内核态程序中通过读写 sysfs 节点操作 PWM。

硬件与 sysfs 对应关系

  • 时钟频率:250 MHz。

  • 通道数量:16 路,每路可独立配置周期与占空比。

  • 控制器与 sysfs 路径:

    • 芯片内共有 3 个 PWM 控制器,对应 sysfs 中的 pwmchip0pwmchip6pwmchip12

    • 原理图上的 pwm0~pwm15 与控制器、sysfs 的对应关系如下:

      • pwm0~pwm5 → /sys/class/pwm/pwmchip0/pwm0~pwm5

      • pwm6~pwm11 → /sys/class/pwm/pwmchip6/pwm0~pwm5

      • pwm12~pwm15 → /sys/class/pwm/pwmchip12/pwm0~pwm3

    即 pwmchip6 的 pwm0~pwm5 对应原理图 pwm6~pwm11,pwmchip12 的 pwm0~pwm3 对应原理图 pwm12~pwm15。

10.3. 操作示例

10.3.1. 命令行操作示例

以下以 pwmchip0 的 pwm1、pwmchip6 的 pwm0、pwmchip12 的 pwm2 为例(对应原理图上的 pwm1、pwm6、pwm14)。实际使用时请按需替换控制器编号与通道号。

步骤 1:导出 PWM 通道

echo 1 > /sys/class/pwm/pwmchip0/export
echo 0 > /sys/class/pwm/pwmchip6/export
echo 2 > /sys/class/pwm/pwmchip12/export

步骤 2:设置周期(单位:纳秒)

echo 1000000 > /sys/class/pwm/pwmchip0/pwm1/period
echo 1000000 > /sys/class/pwm/pwmchip6/pwm0/period
echo 1000000 > /sys/class/pwm/pwmchip12/pwm2/period

步骤 3:设置占空比(单位:纳秒)

echo 500000 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle   # 500000/1000000 = 50%
echo 300000 > /sys/class/pwm/pwmchip6/pwm0/duty_cycle
echo 500000 > /sys/class/pwm/pwmchip12/pwm2/duty_cycle

步骤 4:使能 PWM 输出

使能后可在对应引脚用示波器或外设观察波形。

echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable
echo 1 > /sys/class/pwm/pwmchip6/pwm0/enable
echo 1 > /sys/class/pwm/pwmchip12/pwm2/enable

步骤 5:禁用 PWM 输出

echo 0 > /sys/class/pwm/pwmchip0/pwm1/enable
echo 0 > /sys/class/pwm/pwmchip6/pwm0/enable
echo 0 > /sys/class/pwm/pwmchip12/pwm2/enable

步骤 6:取消导出 PWM 通道

echo 1 > /sys/class/pwm/pwmchip0/unexport
echo 0 > /sys/class/pwm/pwmchip6/unexport
echo 2 > /sys/class/pwm/pwmchip12/unexport

步骤 7:查看 PWM 通道状态

以下以 pwmchip0/pwm1 为例,可查看周期、占空比、使能状态及控制器信息:

# 查看 pwmchip0/pwm1 的周期
cat /sys/class/pwm/pwmchip0/pwm1/period
# 查看 pwmchip0/pwm1 的占空比
cat /sys/class/pwm/pwmchip0/pwm1/duty_cycle
# 查看 pwmchip0/pwm1 是否使能(1=使能,0=禁用)
cat /sys/class/pwm/pwmchip0/pwm1/enable
# 查看 PWM 控制器的基本信息
cat /sys/class/pwm/pwmchip0/device/uevent

10.3.2. 最高与最低频率操作示例

以下说明如何配置 PWM 的最高频率与最低频率,并给出操作步骤与典型参数。

采用示波器观测波形时,需保证示波器采样率至少大于被测信号频率的两倍,否则无法正确还原波形。

最高频率

  • 本设备最高支持 50 MHz,对应周期为 20 ns(\(1/50\,\mathrm{MHz} = 20\,\mathrm{ns}\))。

  • 为得到有效波形,占空比时间(duty_ns)必须小于周期时间,通常采用 50% 占空比测试。

以 pwmchip0/pwm1 为例:

echo 1 > /sys/class/pwm/pwmchip0/export
echo 20 > /sys/class/pwm/pwmchip0/pwm1/period      # 周期 20 ns → 频率 50 MHz
echo 10 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle  # 50% 占空比:20 × 0.5 = 10 ns
echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable

结果:输出频率约 50 MHz,占空比 50%,可用示波器在对应引脚验证。

最低频率

  • 由于内核中周期与占空比使用有符号整数(ns)表示,周期最大可取 2147483646 ns(避免溢出)。

  • 为生成有效波形,占空比时间(duty_ns)必须小于周期时间,通常选择 50% 占空比测试: \(\mathrm{duty\_ns} = \mathrm{period\_ns} \times 50\% = 2147483646 \times 0.5 = 1073741823\,\mathrm{ns}\)

以 pwmchip0/pwm1 为例:

echo 1 > /sys/class/pwm/pwmchip0/export
echo 2147483646 > /sys/class/pwm/pwmchip0/pwm1/period     # 周期 2147483646 ns
echo 1073741823 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle # 50% 占空比
echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable

结果:输出周期约 2.147 s(频率约 0.466 Hz),占空比 50%。可用示波器观察长周期方波。

10.3.3. 用户态程序示例

以下以 pwmchip6 的 pwm0 为例(对应原理图 pwm6),演示在用户态通过 open/write 操作 sysfs 完成导出、设置周期与占空比、使能/禁用输出、取消导出。路径与通道号可通过宏 PWM_PATHPWM_NUM 修改。

辅助函数:向 sysfs 节点写入字符串

#define PWM_PATH "/sys/class/pwm/pwmchip6"
#define PWM_NUM  0

int pwm_write(const char *path, const char *value) {
    int fd = open(path, O_WRONLY);
    if (fd < 0) {
        perror("打开文件失败");
        return -1;
    }
    if (write(fd, value, strlen(value)) < 0) {
        perror("写入失败");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

步骤 1:导出 PWM 通道

char path[256];
snprintf(path, sizeof(path), "%s/export", PWM_PATH);
if (pwm_write(path, "1") < 0)   /* 导出 pwm0 */
    return -1;
usleep(100000);   /* 等待约 100ms 再操作该通道 */

步骤 2:配置周期(单位:纳秒,示例 1000000 ns)

snprintf(path, sizeof(path), "%s/pwm%d/period", PWM_PATH, PWM_NUM);
if (pwm_write(path, "1000000") < 0)
    return -1;

步骤 3:配置占空比(单位:纳秒,示例 300000 ns)

snprintf(path, sizeof(path), "%s/pwm%d/duty_cycle", PWM_PATH, PWM_NUM);
if (pwm_write(path, "300000") < 0)
    return -1;

步骤 4:使能 PWM 输出

snprintf(path, sizeof(path), "%s/pwm%d/enable", PWM_PATH, PWM_NUM);
if (pwm_write(path, "1") < 0)
    return -1;

步骤 5:运行一段时间后禁用 PWM 输出

sleep(10);   /* 输出 10 秒 */
if (pwm_write(path, "0") < 0)   /* path 仍为 enable 节点 */
    return -1;

步骤 6:取消导出 PWM 通道

snprintf(path, sizeof(path), "%s/unexport", PWM_PATH);
pwm_write(path, "1");   /* 取消导出 pwm0,返回值可按需检查 */

完整用户态程序(全部代码)

将上述各步骤串联,并加上头文件与 main,即可得到可直接编译运行的程序:

 1// pwm_control.c
 2#include <stdio.h>
 3#include <stdlib.h>
 4#include <string.h>
 5#include <fcntl.h>
 6#include <unistd.h>
 7#include <errno.h>
 8
 9#define PWM_PATH "/sys/class/pwm/pwmchip6"
10#define PWM_NUM  0
11
12int pwm_write(const char *path, const char *value) {
13    int fd, ret;
14
15    fd = open(path, O_WRONLY);
16    if (fd < 0) {
17        perror("打开文件失败");
18        return -1;
19    }
20
21    ret = write(fd, value, strlen(value));
22    if (ret < 0) {
23        perror("写入失败");
24        close(fd);
25        return -1;
26    }
27
28    close(fd);
29    return 0;
30}
31
32int main(void) {
33    char path[256];
34    int ret;
35
36    printf("=== PWM控制程序 ===\n");
37
38    /* 1. 导出 PWM 通道 */
39    printf("1. 导出PWM通道 pwm%d...\n", PWM_NUM);
40    snprintf(path, sizeof(path), "%s/export", PWM_PATH);
41    ret = pwm_write(path, "1");
42    if (ret < 0) return -1;
43    printf("导出成功\n");
44    usleep(100000);
45
46    /* 2. 设置周期(1 kHz) */
47    printf("2. 设置周期为1kHz...\n");
48    snprintf(path, sizeof(path), "%s/pwm%d/period", PWM_PATH, PWM_NUM);
49    ret = pwm_write(path, "1000000");
50    if (ret < 0) return -1;
51    printf("周期设置成功\n");
52
53    /* 3. 设置占空比(30%%) */
54    printf("3. 设置占空比为30%%...\n");
55    snprintf(path, sizeof(path), "%s/pwm%d/duty_cycle", PWM_PATH, PWM_NUM);
56    ret = pwm_write(path, "300000");
57    if (ret < 0) return -1;
58    printf("占空比设置成功\n");
59
60    /* 4. 启用 PWM */
61    printf("4. 启用PWM输出...\n");
62    snprintf(path, sizeof(path), "%s/pwm%d/enable", PWM_PATH, PWM_NUM);
63    ret = pwm_write(path, "1");
64    if (ret < 0) return -1;
65    printf("PWM已启用\n");
66
67    /* 5. 运行 10 秒 */
68    printf("PWM输出中,10秒后关闭...\n");
69    sleep(10);
70
71    /* 6. 禁用 PWM */
72    printf("5. 禁用PWM...\n");
73    ret = pwm_write(path, "0");
74    if (ret < 0) return -1;
75    printf("PWM已禁用\n");
76
77    /* 7. 取消导出(可选) */
78    printf("6. 取消导出PWM通道...\n");
79    snprintf(path, sizeof(path), "%s/unexport", PWM_PATH);
80    pwm_write(path, "1");
81    printf("PWM通道已取消导出\n");
82
83    return 0;
84}

10.3.4. 运行方式说明

  1. 保存源码:将上述完整用户态程序保存为文件,例如 pwm_control.c

  2. 编译:在宿主机或目标板上使用对应工具链编译。示例(目标板为 ARM 时请替换为实际交叉编译器):

    # 交叉编译示例(以实际工具链为准)
    arm-none-linux-uclibcgnueabihf-gcc -o pwm_control pwm_control.c -static
    
  3. 运行:将可执行文件拷贝到开发板后,在目标板上执行。需对 /sys/class/pwm 有写权限(通常需 root 或相应权限)。

    ./pwm_control
    

    程序将依次:导出 pwmchip6 的 pwm0、设置 1 kHz 周期与 30%% 占空比、启用 PWM 输出、运行 10 秒、禁用输出、取消导出。可在对应引脚用示波器观察波形。

  4. 修改通道与路径:若使用其他控制器或通道,请修改源码中的 PWM_PATH(如 /sys/class/pwm/pwmchip0)、PWM_NUM(如 1),以及 export/unexport 中写入的通道号,使与本文「硬件与 sysfs 对应关系」一致。