一、前言
嵌入式Linux学习交流群:1005210698
欢迎各位大佬和萌新进来一起学习交流
本文基于泰山派RK3566开发板为例
二. 脉宽调制
1. 什么是PWM
- PWM(Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术, 广泛应用在测量、通信、工控等方面。
2. PWM的频率
- 是指在1秒钟内,信号从高电平到低电平再回到高电平的次数,也就是说一秒钟PWM有多少个周期,单位Hz。
3. PWM的周期
- T=1/f,T是周期,f 是频率。
- 如果频率为50Hz ,也就是说一个周期是20ms,那么一秒钟就有50个PWM周期。
4. 占空比
-
是一个脉冲周期内,高电平的时间与整个周期时间的比例,单位是% (0%-100%)
-
一个周期的长度,如下图所示
-
其中,周期是一个脉冲信号的时间,1s内的周期T次数等于频率f,脉宽时间是指高电平时间。
-
上图中,脉宽时间占总周期时间的比例,就是占空比。
-
比方说,周期的时间是10ms,脉宽时间是8ms,那么占空比是8/10= 80%,这就是占空比为80%的脉冲信号。
-
PWM就是脉冲宽度调制,通过调节占空比就可以调节脉冲宽度。
5. PWM原理
- 假设高电平为5V、低电平则为0V,那么要输出不同的模拟电压就要用到PWM。
- 通过改变IO口输出的方波的占空比,从而获得使用数字信号模拟成的模拟电压信号。
- 电压是以一种脉冲序列被加到模拟负载上去的,接通时是高电平1,断开时是低电平0。
- 接通时直流供电输出,断开时直流供电断开。通过对接通和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值5V的模拟电压。
- 比方说,占空比为50%那就是高电平时间一半,低电平时间一半。在一定的频率下,就可以得到模拟的2.5V输出电压。那么75%的占空比,得到的电压就是3.75V,如下图所示。
三. pwm引脚
- 泰山派RK3566板卡上集成了四个具有pwm功能的GPIO
- 对应设备树(/kernel/arch/arm64/boot/dts/rockchip/tspi-rk3566-user-v10-linux.dts),默认全部开启
&pwm8 {
status = "okay";
};
&pwm9 {
status = "okay";
};
&pwm14 {
status = "okay";
};
//pwd 15遥控器
&pwm15 {
status = "okay";
compatible = "rockchip,remotectl-pwm";
remote_pwm_id = <3>;
handle_cpu_id = <1>;
remote_support_psci = <0>;
pinctrl-names = "default";
pinctrl-0 = <&pwm15m0_pins>;
//用户自定方法:adb设置输出日志并通过dmesg确定usercode=address与key_table=command
//echo 1 > sys/module/rockchip_pwm_remotectl/parameters/code_print
//键值可在 include/dt-bindings/input/linux-event-codes.h 中查找
ir_key1 {
rockchip,usercode = <0xff00>;
rockchip,key_table =
<0xf2 KEY_MENU>,
<0xe9 KEY_BACK>,
<0xe3 KEY_ENTER>,
<0xe7 KEY_UP>,
<0xad KEY_DOWN>,
<0xf7 KEY_LEFT>,
<0xa5 KEY_RIGHT>,
<0xba KEY_1>,
<0xb9 KEY_2>,
<0xb8 KEY_3>,
<0xbb KEY_4>,
<0xbf KEY_5>,
<0xbc KEY_6>,
<0xf8 KEY_7>,
<0xea KEY_8>,
<0xf6 KEY_9>,
<0xe6 KEY_0>;
};
};
四、检查PWM设备
- 在给板子上点后,可以用以下命令查看pwm信息
ls /sys/class/pwm/
cat /sys/kernel/debug/pwm
// kernel/arch/arm64/boot/dts/rockchip/rk3568.dtsi
pwm5: pwm@fe6e0010 {
compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";
reg = <0x0 0xfe6e0010 0x0 0x10>;
#pwm-cells = <3>;
pinctrl-names = "active";
pinctrl-0 = <&pwm5_pins>;
clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>;
clock-names = "pwm", "pclk";
status = "disabled";
};
pwm8: pwm@fe6f0000 {
compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";
reg = <0x0 0xfe6f0000 0x0 0x10>;
#pwm-cells = <3>;
pinctrl-names = "active";
pinctrl-0 = <&pwm8m0_pins>;
clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>;
clock-names = "pwm", "pclk";
status = "disabled";
};
pwm9: pwm@fe6f0010 {
compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";
reg = <0x0 0xfe6f0010 0x0 0x10>;
#pwm-cells = <3>;
pinctrl-names = "active";
pinctrl-0 = <&pwm9m0_pins>;
clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>;
clock-names = "pwm", "pclk";
status = "disabled";
};
pwm14: pwm@fe700020 {
compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";
reg = <0x0 0xfe700020 0x0 0x10>;
#pwm-cells = <3>;
pinctrl-names = "active";
pinctrl-0 = <&pwm14m0_pins>;
clocks = <&cru CLK_PWM3>, <&cru PCLK_PWM3>;
clock-names = "pwm", "pclk";
status = "disabled";
};
- pwmchip0为屏幕的背光对应pwm5,可以在
kernel/arch/arm64/boot/dts/rockchip/tspi-rk3566-dsi-v10.dtsi
查看 - 当开启多个pwm设备树插件时,pwm控制器值越小,系统分配的pwmchip越小。
五、pwm控制方式
1. 在板卡上通过shell控制
- 下面操作以pwm8为例
#将pwm3导出到用户空间
echo 0 > /sys/class/pwm/pwmchip1/export
#设置pwm周期 单位为ns
echo 1000000 > /sys/class/pwm/pwmchip1/pwm0/period
#设置占空比
echo 500000 > /sys/class/pwm/pwmchip1/pwm0/duty_cycle
#设置pwm极性
echo "normal" > /sys/class/pwm/pwmchip1/pwm0/polarity
#使能pwm
echo 1 > /sys/class/pwm/pwmchip1/pwm0/enable
#失能pwm
echo 0 > /sys/class/pwm/pwmchip1/pwm0/enable
#如果需要关闭PWM则将pwm3导出到用户空间
echo 0 > /sys/class/pwm/pwmchip1/unexport
2. 通过代码系统调用
- 下面操作以pwm8为例
/******************************************************************
* 个人博客:https://blog.csdn.net/2302_80277720?type=blog
* 嵌入式Linux学习交流群:1005210698
* 欢迎各位大佬和萌新来加入交流学习
* Change Logs:
* Date Author Notes
* 2025-03-01 喝呜昂黄 first version
******************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
static char pwm_path[75];
static int pwm_config(const char *attr, const char *val){
char file_path[100];
int len;
int fd;
int res;
sprintf(file_path, "%s/%s", pwm_path, attr);
fd = open(file_path, O_WRONLY);
if(0 > fd){
perror("open error");
return fd;
}
len = strlen(val);
res = write(fd, val, len);
if(len != res){
perror("write error");
close(fd);
return -1;
}
close(fd);
return 0;
}
static void pwm_export(int fd, char *id){
char temp[100];
int res;
// 如果 pwm0 目录不存在, 则导出
if(access(pwm_path, F_OK)){
sprintf(temp, "/sys/class/pwm/pwmchip%s/export", id);
fd = open(temp, O_WRONLY);
if(fd < 0){
perror("open error");
exit(-1);
}
// 导出 pwm
res = write(fd, "0", 1);
if(1 != res){
perror("write error");
close(fd);
exit(-1);
}
}
}
static void pwm_unexport(int fd, char *id){
char temp[100];
int res;
sprintf(temp, "/sys/class/pwm/pwmchip%s/unexport", id);
fd = open(temp, O_WRONLY);
if(fd < 0){
perror("open error");
exit(-1);
}
// 导出 pwm
res = write(fd, "0", 1);
if(1 != res){
perror("write error");
close(fd);
exit(-1);
}
}
// ./pwm_test 1 1000000 500000
int main(int argc, char **argv){
int fd;
int res;
if(argc != 4){
fprintf(stderr, "Usage: %s <id> <period> <duty>\n", argv[0]);
exit(-1);
}
printf("PWM config: id<%s>, period<%s>, duty<%s>\n",argv[1], argv[2],argv[3]);
// pwm 路径
sprintf(pwm_path, "/sys/class/pwm/pwmchip%s/pwm0", argv[1]);
// 导出 pwm
pwm_export(fd, argv[1]);
// 配置 PWM 周期
res = pwm_config("period", argv[2]);
if(res)
exit(-1);
// 配置占空比
res = pwm_config("duty_cycle", argv[3]);
if(res)
exit(-1);
// 使能 pwm
pwm_config("enable", "1");
printf("按任意键终止程序\n");
getchar();
// 失能 pwm
pwm_config("enable", "0");
// 放回 pwm
pwm_unexport(fd, argv[1]);
return 0;
}
六、参考
本片文章参考野火的《pwm(脉宽调制)》