PWM
在本章中,我们将学习如何在应用层中使用 PWM。
示例程序:Code.zip
1.PWM
PWM,全称为脉冲宽度调制(Pulse Width Modulation),是一种通过控制信号的脉冲宽度来实现模拟信号输出的技术。它常用于嵌入式系统和电子设备中,用于控制电机速度、LED 亮度、音频信号生成等应用。
在 Linux 系统中,PWM 设备通常通过 sysfs 文件系统进行管理和配置,其设备目录通常位于 /sys/class/pwm/
目录下。
2.控制PWM(Shell)
2.1 引脚分布
LuckFox Pico Ultra/Ultra W 引脚图:
2.2 打开 PWM
使用 luckfox-config 打开相关配置:
- 键盘上的↑、↓键进行菜单项目的选择,enter键进入,←、→键或者Tab进行 OK和 cancel 按钮的选择,Esc 键取消返回,空格键为选择定选项,任何更改都将在重新启动后生效。注意:ADB登录无法使用 方向键和 Tab,只能使用数字选择选项,enter确认。
在终端打开 luckfox-config 工具:
luckfox-config
选择 Advanced Options:
选择 PWM:
选择想要打开的 PWM 接口,这里以 PWM7_M1 为例:
选择
enable
,当选择回车确认后,按esc
退出:重启开发板
reboot
2.3 设备目录
在 /sys/class/pwm/
目录中,每个 PWM 设备都有一个独立的子目录,其名称通常以 pwmchipX
的形式命名,其中 X 是 PWM 设备的编号。您可使用如下命令查看:
[root@luckfox root]$ ls -l /sys/class/pwm
total 0
lrwxrwxrwx 1 root root 0 Jan 1 1970 pwmchip1 -> ../../devices/platform/ff350010.pwm/pwm/pwmchip1
lrwxrwxrwx 1 root root 0 Jun 13 13:28 pwmchip7 -> ../../devices/platform/ff360030.pwm/pwm/pwmchip7
2.4 设备属性
在成功导出 PWM 的一个通道后,会在该 PWM 的设备目录下生成单个通道的目录,其中有这个通道对应的属性文件,用户可以通过修改这些文件的内容来配置和控制特定 PWM 通道的各个参数,以实现对 PWM 信号的精确控制。这提供了一个方便的接口,使用户能够与 PWM 硬件进行交互并调整 PWM 信号的特性。
查看PWM10单个通道的属性文件
# ls /sys/class/pwm/pwmchip10/pwm0/
uevent capture duty_cycle output_type
polarity enable power period属性文件
通过使用这些属性文件,您可以轻松地配置和控制 PWM 通道,使其适应不同的应用场景和需求。其中一些关键的属性如下:
enable
:用于启用或禁用PWM通道。period
:用于设置PWM信号的周期时间。duty_cycle
:用于设置PWM信号的占空比。polarity
:用于配置PWM信号的极性。power/control
:用于启用或禁用PWM通道的电源管理(通常用于省电模式)。
2.5 控制PWM
测试 PWM。将PWM10_M1(GPIO1_C6_d)导出到用户空间
echo 0 > /sys/class/pwm/pwmchip10/export
设置PWM周期单位为ns,比如 1KHz 频率的周期就是 1000000ns(注意,在任何的情况下都得保证 period 的值大于等于 duty_cycle 的值)
echo 1000000 > /sys/class/pwm/pwmchip10/pwm0/period
设置占空比(小于等于 period )
echo 100000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 200000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 300000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 400000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 500000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 600000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 700000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 800000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 900000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 1000000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 0 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle设置PWM极性正常或翻转
echo "normal" > /sys/class/pwm/pwmchip1/pwm0/polarity
echo "inversed" > /sys/class/pwm/pwmchip1/pwm0/polarity使能和关闭PWM
echo 1 > /sys/class/pwm/pwmchip1/pwm0/enable
echo 0 > /sys/class/pwm/pwmchip1/pwm0/enable取消导出PWM到用户空间
echo 0 > /sys/class/pwm/pwmchip10/unexport
3.控制PWM(Python程序)
在前文中,我们演示了使用 echo 命令修改设备文件,以达到控制设备的目的。接下来,我们将使用 Python 程序实现 PWM 的控制。
3.1 示例程序
通过以下程序,可以实现控制PWM10输出。
from periphery import PWM
import time
pwm = PWM(10, 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()
3.2 打开PWM设备
此代码段使用 periphery
库中的 PWM
类创建了一个 PWM 对象。在这个例子中,PWM 对象被初始化为 PWM10 。
pwm = PWM(10, 0)
3.3 配置PWM
在这个代码段中,配置了先前创建的 PWM 对象的一些属性。设置了 PWM 的频率为 1000 Hz,极性为正常("normal"),并将占空比初始化为 0。
pwm.frequency = 1000
pwm.duty_cycle = 0
pwm.polarity = "normal"
3.4 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)
3.5 运行程序
使用 nano 工具在终端创建 py 文件,粘贴并保存 python 程序
# nano pwm.py
运行程序
# python3 pwm.py
实验现象
控制PWM10输出,实现呼吸灯
4.控制PWM(C程序)
在前文中,我们演示了如何使用 echo 命令和 Python 程序控制 PWM。实际上也可以使用 vi 编辑器对文件进行修改,修改时需注意用户权限。此外,我们还可以使用C库函数或系统调用来读写设备文件,以达到控制设备的目的。请注意,为了在特定的嵌入式系统上运行程序,通常需要使用交叉编译工具来编译代码,以生成可在目标开发板上执行的可执行文件。接下来,让我们一起探讨具体的实施步骤。
4.1 示例程序
通过以下程序,可以实现控制PWM10输出。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PWM_PATH "/sys/class/pwm/pwmchip10"
#define PERIOD_NS 1000000
#define MIN_DUTY_CYCLE_NS 0
#define MAX_DUTY_CYCLE_NS 1000000
int main() {
FILE *pwm_export = fopen(PWM_PATH "/export", "w");
if (!pwm_export) {
perror("Failed to open PWM export");
return 1;
}
fprintf(pwm_export, "0");
fclose(pwm_export);
FILE *period_file = fopen(PWM_PATH "/pwm0/period", "w");
if (!period_file) {
perror("Failed to open PWM period");
return 1;
}
fprintf(period_file, "%d", PERIOD_NS);
fclose(period_file);
FILE *enable_file = fopen(PWM_PATH "/pwm0/enable", "w");
if (!enable_file) {
perror("Failed to open PWM enable");
return 1;
}
fprintf(enable_file, "1");
fclose(enable_file);
int direction = 1;
int duty_cycle_ns = 0;
while (1) {
duty_cycle_ns += 10000 * direction;
if(duty_cycle_ns == MAX_DUTY_CYCLE_NS)
direction = -1;
else if(duty_cycle_ns == MIN_DUTY_CYCLE_NS)
direction = 1;
FILE *duty_cycle_file = fopen(PWM_PATH "/pwm0/duty_cycle", "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);
usleep(50000);
}
FILE *pwm_unexport = fopen(PWM_PATH "/unexport", "w");
if (!pwm_unexport) {
perror("Failed to open PWM unexport");
return 1;
}
fprintf(pwm_unexport, "0");
fclose(pwm_unexport);
return 0;
}
4.2 导出PWM通道到用户空间
这段代码通过向 /sys/class/pwm/pwmchip10/export
写入设备索引(通常为0),以导出指定的PWM通道供用户空间控制。
FILE *pwm_export = fopen(PWM_PATH "/export", "w");
if (!pwm_export) {
perror("Failed to open PWM export");
return 1;
}
fprintf(pwm_export, "0");
fclose(pwm_export);
4.3 设置PWM周期
这段代码通过向 /sys/class/pwm/pwmchip10/pwm0/period
写入周期值 “1000000”(以纳秒为单位),以设置PWM的周期为1KHZ。
FILE *period_file = fopen(PWM_PATH "/pwm0/period", "w");
if (!period_file) {
perror("Failed to open PWM period");
return 1;
}
fprintf(period_file, "%d", PERIOD_NS);
fclose(period_file);
4.4 使能PWM
这段代码通过向 /sys/class/pwm/pwmchip10/pwm0/enable
文件写入值"1",以启用PWM信号的输出。
FILE *enable_file = fopen(PWM_PATH "/pwm0/enable", "w");
if (!enable_file) {
perror("Failed to open PWM enable");
return 1;
}
fprintf(enable_file, "1");
fclose(enable_file);
4.5 使用PWM实现呼吸灯
这段代码打开 /sys/class/pwm/pwmchip10/pwm0/duty_cycle
文件,并且通过不断改变 duty_cycle_ns
变量的值,实现了PWM占空比从0%逐渐增加到100%,然后再从100%逐渐减小到0%的呼吸灯效果。duty_cycle_ns
的变化在0到1000000之间,以控制占空比的变化。当占空比达到最小值或最大值时,它会反转方向,以实现往复运动。此外,usleep(50000)
函数用于控制呼吸灯的速度,您可以根据需要调整此值以改变呼吸速度。
int direction = 1;
int duty_cycle_ns = 0;
while (1) {
duty_cycle_ns += 10000 * direction;
if(duty_cycle_ns == MAX_DUTY_CYCLE_NS)
direction = -1;
else if(duty_cycle_ns == MIN_DUTY_CYCLE_NS)
direction = 1;
FILE *duty_cycle_file = fopen(PWM_PATH "/pwm0/duty_cycle", "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);
usleep(50000);
}
4.6 取消导出PWM通道到用户空间
这段代码通过向 /sys/class/pwm/pwmchip10/unexport
写入设备索引,取消导出指定的PWM通道到用户空间。
FILE *pwm_unexport = fopen(PWM_PATH "/unexport", "w");
if (!pwm_unexport) {
perror("Failed to open PWM unexport");
return 1;
}
fprintf(pwm_unexport, "0");
fclose(pwm_unexport);
4.7 交叉编译
指定交叉编译工具
首先,我们要将交叉编译工具的路径添加到系统的
PATH
环境变量中,以便可以在任何地方使用交叉编译工具,您可以在shell配置文件中添加以下行(通常是~/.bashrc
或~/.bash_profile
或~/.zshrc
,具体取决于您使用的shell),注意PATH=
后的路径为交叉编译工具所在的目录。gcc路径
<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
打开shell配置文件
vi ~/.bashrc
将交叉编译工具的路径添加到系统的PATH环境变量中,将 <SDK Directory> 修改为自己的 SDK 路径,如
/home/luckfox/luckfox-pico/
export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
重新加载shell配置文件,使更改生效
source ~/.bashrc
使用交叉编译工具编译程序
arm-rockchip830-linux-uclibcgnueabihf-gcc pwm.c -o pwm
交叉编译成功后,将在当前目录下生成可在开发板运行的可执行文件
#ls
pwm pwm.c
4.8 运行程序
文件传输
先将
pwm
从虚拟机传输到 Windows,再通过 TFTP 或 ADB 传输到开发板,将文件从 Windows 通过 ADB 将文件传输到开发板的步骤如下:adb push 文件所在路径 开发板存储路径
eg:(将当前目录下pwm文件传输到开发板的根目录)
adb push pwm /运行程序
修改
pwm
文件的操作权限后运行程序# chmod 777 pwm
# ./pwm实验现象
控制PWM10输出,实现呼吸灯