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 中的
pwmchip0、pwmchip6、pwmchip12。原理图上的 pwm0~pwm15 与控制器、sysfs 的对应关系如下:
pwm0~pwm5 →
/sys/class/pwm/pwmchip0/pwm0~pwm5pwm6~pwm11 →
/sys/class/pwm/pwmchip6/pwm0~pwm5pwm12~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_PATH、PWM_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. 运行方式说明¶
保存源码:将上述完整用户态程序保存为文件,例如
pwm_control.c。编译:在宿主机或目标板上使用对应工具链编译。示例(目标板为 ARM 时请替换为实际交叉编译器):
# 交叉编译示例(以实际工具链为准) arm-none-linux-uclibcgnueabihf-gcc -o pwm_control pwm_control.c -static运行:将可执行文件拷贝到开发板后,在目标板上执行。需对
/sys/class/pwm有写权限(通常需 root 或相应权限)。./pwm_control
程序将依次:导出 pwmchip6 的 pwm0、设置 1 kHz 周期与 30%% 占空比、启用 PWM 输出、运行 10 秒、禁用输出、取消导出。可在对应引脚用示波器观察波形。
修改通道与路径:若使用其他控制器或通道,请修改源码中的 PWM_PATH(如 /sys/class/pwm/pwmchip0)、PWM_NUM(如 1),以及 export/unexport 中写入的通道号,使与本文「硬件与 sysfs 对应关系」一致。