15. pwm(脉宽调制)

本章讲解Linux pwm相关应用层程序控制。

本章的示例代码目录为:base_linux/pwm

15.1. 脉宽调制

15.1.1. 什么是PWM

PWM(Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术, 广泛应用在测量、通信、工控等方面。

15.1.2. PWM的频率

是指在1秒钟内,信号从高电平到低电平再回到高电平的次数,也就是说一秒钟PWM有多少个周期,单位Hz。

15.1.3. PWM的周期

T=1/f,T是周期,f是频率。

如果频率为50Hz ,也就是说一个周期是20ms,那么一秒钟就有 50次PWM周期。

15.1.4. 占空比

是一个脉冲周期内,高电平的时间与整个周期时间的比例,单位是% (0%-100%)

一个周期的长度,如下图所示

未找到图片

其中,周期是一个脉冲信号的时间,1s内的周期T次数等于频率f,脉宽时间是指高电平时间。

上图中,脉宽时间占总周期时间的比例,就是占空比。

比方说,周期的时间是10ms,脉宽时间是8ms,那么占空比是8/10= 80%,这就是占空比为80%的脉冲信号。

PWM就是脉冲宽度调制,通过调节占空比就可以调节脉冲宽度。

15.1.5. PWM原理

假设高电平为5V、低电平则为0V,那么要输出不同的模拟电压就要用到PWM。

通过改变IO口输出的方波的占空比,从而获得使用数字信号模拟成的模拟电压信号。

电压是以一种脉冲序列被加到模拟负载上去的,接通时是高电平1,断开时是低电平0。

接通时直流供电输出,断开时直流供电断开。通过对接通和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值5V的模拟电压。

比方说,占空比为50%那就是高电平时间一半,低电平时间一半。在一定的频率下,就可以得到模拟的2.5V输出电压。那么75%的占空比,得到的电压就是3.75V,如下图所示。

未找到图片

15.2. pwm引脚

imx8板卡40pin上有4个pwm,如下图所示:

未找到图片

同一时间下,一个pwm控制器只能给一个gpio复用,野火提供的pwm插件对应以下引脚:

../../_images/pwm1.jpg

如果需要使用其他引脚作为pwm复用,需要修改对应的设备树插件。

15.2.1. 使能PWM接口功能

PWM接口在默认情况是关闭状态的,需要使能才能使用,

修改/boot/uEnv.txt配置文件,将需要打开的pwm接口对应pwm插件取消注释,然后重启板卡,以下以开启pwm2为例:

../../_images/pwm3.jpg

15.3. 检查PWM设备

使能pwm通信接口后,可以用以下命令查看pwm是否开启

1
2
3
ls /sys/class/pwm/

cat /sys/kernel/debug/pwm
未找到图片

注意

如果开启了mipi屏幕的插件,那么pwm1也会开启,因为mipi屏幕使用pwm1控制背光,但是使用的不是40pin上的引脚,使用的是GPIO1_IO01引脚。

15.4. pwm控制方式(shell)

下面操作以pwm2为例

注意

从前面检查PWM设备步骤可知,pwm2为pwmchip1,需根据实际确定。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#将pwm2导出到用户空间
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则将pwm3导出到用户空间
echo 0 > /sys/class/pwm/pwmchip1/unexport
../../_images/pwm5.jpg

提示

当设置period与duty_cycle值的时候需要注意在任何的情况下都得保证period的值大于等于duty_cycle的值。

15.5. pwm控制方式(系统调用)

base_linux/pwm/pwm_test.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#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)    //配置PWM
{
    char file_path[100];
    int len;
    int fd;
    sprintf(file_path, "%s/%s", pwm_path, attr);
    if ((fd = open(file_path, O_WRONLY))< 0) {
        perror("open error");
        return fd;
    }
    len = strlen(val);
    if (len != write(fd, val, len)) {
        perror("write error");
        close(fd);
        return -1;
    }
    close(fd); //关闭文件
    return 0;
}
 
 
int main(int argc, char *argv[])
{
    /* 校验传参 */
    if (4 != argc) {
        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]);
    //如果 pwm0 目录不存在, 则导出
    if (access(pwm_path, F_OK)) {
        char temp[100];
        int fd;
        sprintf(temp, "/sys/class/pwm/pwmchip%s/export", argv[1]);
        if (0 > (fd = open(temp, O_WRONLY))) {
            perror("open error");
            exit(-1);
        }
        //导出 pwm
        if (1 != write(fd, "0", 1)) {
            perror("write error");
            close(fd);
            exit(-1);
        }
        close(fd); //关闭文件
    }
    
    /* 配置 PWM 周期 */
    if (pwm_config("period", argv[2]))
        exit(-1);
    /* 配置占空比 */
    if (pwm_config("duty_cycle", argv[3]))
        exit(-1);
    /* 使能 pwm */
    pwm_config("enable", "1");
    getchar();
    /* 退出程序 */
    exit(0);
}

方法1:

1
make

方法2:

1
gcc pwm_test.c -o pwm_test
1
2
3
4
#设置pwm周期 , pwm占空比

#./pwm_test pwmchip1 周期 占空比
sudo ./pwm_test 1 1000000 500000