PWM
在本章中,我们将学习如何在应用层中使用 PWM。
示例程序:Code.zip
1.PWM
PWM,全称为脉冲宽度调制(Pulse Width Modulation),是一种通过控制信号的脉冲宽度来实现模拟信号输出的技术。它常用于嵌入式系统和电子设备中,用于控制电机速度、LED 亮度、音频信号生成等应用。
在 Linux 系统中,PWM 设备通常通过 sysfs 文件系统进行管理和配置,其设备目录通常位于 /sys/class/pwm/
目录下。
2.控制PWM(Shell)
2.1 引脚分布
由于在 LuckFox Pico 、LuckFox Pico Mini A/B 和 LuckFox Pico Plus/Pro/Max 引脚复用,会有引脚功能冲突,所以各型号开发板默认开启的PWM如下:
- LuckFox Pico:
PWM0_0
- 34 号引脚PWM1_0
- 4 号引脚PWM10_1
- 54 号引脚PWM11_1
- 55 号引脚
- LuckFox Pico Mini A/B:
PWM10_1
- 54 号引脚PWM11_1
- 55 号引脚
- LuckFox Pico Plus:
PWM0_0
- 34 号引脚PWM1_0
- 4 号引脚PWM10_1
- 54 号引脚PWM11_1
- 55 号引脚
- LuckFox Pico Pro/Max:
PWM5_1
- 72 号引脚PWM6_1
- 73 号引脚PWM10_1
- 54 号引脚PWM11_1
- 55 号引脚
LuckFox Pico 引脚图:
LuckFox Pico Mini A/B 引脚图:
LuckFox Pico Plus 引脚图:
LuckFox Pico Pro/Max 引脚图:
LuckFox Pico Ultra/Ultra W 引脚图:
2.2 设备目录
在 /sys/class/pwm/
目录中,每个 PWM 设备都有一个独立的子目录,其名称通常以 pwmchipX
的形式命名,其中 X 是 PWM 设备的编号。您可使用如下命令查看:
# ls -l /sys/class/pwm
lrwxrwxrwx 1 root root 0 pwmchip10 -> ../../devices/platform/ff490020.pwm/pwm/pwmchip10
lrwxrwxrwx 1 root root 0 pwmchip1 -> ../../devices/platform/ff350010.pwm/pwm/pwmchip1
lrwxrwxrwx 1 root root 0 pwmchip11 -> ../../devices/platform/ff490030.pwm/pwm/pwmchip11
lrwxrwxrwx 1 root root 0 pwmchip0 -> ../../devices/platform/ff350000.pwm/pwm/pwmchip0
2.3 设备属性
在成功导出 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.4 控制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输出,实现呼吸灯
5.修改设备树
程序中的 PWM10_M1 在所有型号的开发板都是默认开启的,可以直接使用。接下来,我们一起看看如果需要开启其他的 PWM 该如何操作。
5.1 修改设备树文件
设备树文件路径
开发板配置文件
在
<SDK目录>/project/cfg/BoardConfig_IPC/
目录下有不同型号开发板的配置文件,这些文件主要包含了针对 Luckfox Pico 不同型号开发板的配置参数,涵盖了目标架构、启动介质、Uboot和内核配置、分区设置等多方面的内容。SDK目录结构如下:├── build.sh -> project/build.sh ---- SDK编译脚本
├── media --------------------------- 多媒体编解码、ISP等算法相关(可独立SDK编译)
├── sysdrv -------------------------- U-Boot、kernel、rootfs目录(可独立SDK编译)
├── project ------------------------- 参考应用、编译配置以及脚本目录
│ ├── cfg
│ ├── BoardConfig_IPC
│ ├── BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
│ ├── BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico_Mini_A-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Mini_B-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Plus-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1106_Luckfox_Pico_Pro_Max-IPC.mk
│ └── ...
├── output -------------------------- SDK编译后镜像文件存放目录
├── docs ---------------------------- SDK文档目录
└── tools --------------------------- 烧录镜像打包工具以及烧录工具其中,
RK_KERNEL_DTS
指定了内核的设备树文件。我们以 Luckfox Pico 为例,打开BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
文件,可以看到RK_KERNEL_DTS
指向的文件为rv1103g-luckfox-pico.dts
设备树文件路径
根据RK_KERNEL_DTS,可以确定 Luckfox Pico 的设备树文件路径如下:
<SDK目录>/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico.dts
开启PWM0_M0
在设备树中,开启PWM0_M0的代码片段如下所示
开启PWM0_M1
如果我们想要使用 PWM0_M1 进行输出,首先我们需要取消注释 PWM0_M1 的配置语句(请查看第201行代码)
根据接口图可知,PWM0_M1(GPIO1_D2_d) 默认功能为 I2C3 功能,所以我们需要在设备树中注释 I2C3 的代码片段
5.2 编译内核
编译选择分支,分别是指定 LuckFox Pico 、LuckFox Pico Mini A 、LuckFox Pico Mini B、LuckFox Pico Plus 和 LuckFox Pico Pro/Max
luckfox@luckfox:~/luckfox-pico$ ./build.sh lunch
ls: cannot access 'BoardConfig*.mk': No such file or directory
You're building on Linux
Lunch menu...pick a combo:
BoardConfig-*.mk naming rules:
BoardConfig-"启动介质"-"电源方案"-"硬件版本"-"应用场景".mk
BoardConfig-"boot medium"-"power solution"-"hardware version"-"applicaton".mk
----------------------------------------------------------------
0. BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
boot medium(启动介质): EMMC
power solution(电源方案): NONE
hardware version(硬件版本): RV1103_Luckfox_Pico
applicaton(应用场景): IPC
----------------------------------------------------------------
----------------------------------------------------------------
1. BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico_Mini_A-IPC.mk
boot medium(启动介质): EMMC
power solution(电源方案): NONE
hardware version(硬件版本): RV1103_Luckfox_Pico_Mini_A
applicaton(应用场景): IPC
----------------------------------------------------------------
----------------------------------------------------------------
2. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Mini_B-IPC.mk
boot medium(启动介质): SPI_NAND
power solution(电源方案): NONE
hardware version(硬件版本): RV1103_Luckfox_Pico_Mini_B
applicaton(应用场景): IPC
----------------------------------------------------------------
----------------------------------------------------------------
3. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Plus-IPC.mk
boot medium(启动介质): SPI_NAND
power solution(电源方案): NONE
hardware version(硬件版本): RV1103_Luckfox_Pico_Plus
applicaton(应用场景): IPC
----------------------------------------------------------------
----------------------------------------------------------------
4. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1106_Luckfox_Pico_Pro_Max-IPC.mk
boot medium(启动介质): SPI_NAND
power solution(电源方案): NONE
hardware version(硬件版本): RV1106_Luckfox_Pico_Pro_Max
applicaton(应用场景): IPC
----------------------------------------------------------------
Which would you like? [0]: 0
[build.sh:info] switching to board: /home/luckfox/luckfox-pico/project/cfg/BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
[build.sh:info] Running build_select_board succeeded.重新编译内核
luckfox@luckfox:~/Luckfox-Pico/luckfox-pico$ ./build.sh kernel
5.3 重新烧录固件
- 内核编译成功后生成的文件在
<SDK目录>output/image
目录下 - 替换原固件中的
boot.image
与env.txt
文件 - 重新创建SD(Luckfox Pico Plus 可只修改相应的分区)