13. pwm(脉宽调制)

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

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

13.1. 脉宽调制

13.1.1. 什么是PWM

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

13.1.2. PWM的频率

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

13.1.3. PWM的周期

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

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

13.1.4. 占空比

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

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

未找到图片

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

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

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

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

13.1.5. PWM原理

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

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

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

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

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

未找到图片

13.2. pwm引脚

LubanCat-P1板卡22pin上有两个具有pwm功能的GPIO

未找到图片

13.2.1. PWM控制器与PWM接口

算能sg200x有4个PWM控制器,每个控制器有4路PWM接口,4个控制器总共有16路pwm接口,对应关系如下:

可以通过debug接口查看控制器情况

1
2
#查看pwm控制器情况
sudo cat /sys/kernel/debug/pwm
未找到图片

可以从上图看到默认没有程序或者驱动使用PWM。

13.2.2. 使能PWM接口功能

PWM接口在默认情况是关闭状态的,需要使能才能使用,可以通过 cvi_pinmux 工具进行切换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#查看CLK25M引脚复用功能,默认复用为PWR_GPIO11
sudo cvi_pinmux -l | grep -A 5 'CLK25M function'

#将CLK25M引脚复用为PWM_3
sudo cvi_pinmux -w CLK25M/PWM_3



#查看PWR_GPIO0引脚复用功能,默认复用为PWR_GPIO0
sudo cvi_pinmux -l | grep -A 5 'PWR_GPIO0 function'

#将PWR_GPIO0引脚复用为PWM_8
sudo cvi_pinmux -w PWR_GPIO0/PWM_8

13.3. pwm控制方式(shell)

确认切换了引脚复用后可进行以下操作。

以pwm3为例,对应pwmchip0控制器的第4路接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#切换root用户
sudo -s

#将pwm3导出到用户空间
echo 3 > /sys/class/pwm/pwmchip0/export

#设置pwm周期 单位为ns
echo 1000000 > /sys/class/pwm/pwmchip0/pwm3/period

#设置占空比
echo 500000 > /sys/class/pwm/pwmchip0/pwm3/duty_cycle

#设置pwm极性
echo "normal" > /sys/class/pwm/pwmchip0/pwm3/polarity

#使能pwm
echo 1 > /sys/class/pwm/pwmchip0/pwm3/enable

#取消将pwm3导出到用户空间
echo 3 > /sys/class/pwm/pwmchip0/unexport

以pwm8为例,对应pwmchip8控制器的第1路接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#切换root用户
sudo -s

#将pwm8导出到用户空间
echo 0 > /sys/class/pwm/pwmchip8/export

#设置pwm周期 单位为ns
echo 1000000 > /sys/class/pwm/pwmchip8/pwm0/period

#设置占空比
echo 500000 > /sys/class/pwm/pwmchip8/pwm0/duty_cycle

#设置pwm极性
echo "normal" > /sys/class/pwm/pwmchip8/pwm0/polarity

#使能pwm
echo 1 > /sys/class/pwm/pwmchip8/pwm0/enable

#取消将pwm8导出到用户空间
echo 0 > /sys/class/pwm/pwmchip8/unexport

提示

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

使能后可以debug接口查看pwm情况

未找到图片

上图sysfs名字的pwm设备就是通过以上shell命令配置的pwm,处于激活状态。

13.4. 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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#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]);

    /* 将 argv[1] 从字符串转换为整数 */
    int number = atoi(argv[1]);

    /* 确定 pwmchip 控制器编号 */
    int controller;
    if (number >= 0 && number <= 3) {
        controller = 0;
    } else if (number >= 4 && number <= 7) {
        controller = 4;
    } else if (number >= 8 && number <= 11) {
        controller = 8;
    } else if (number >= 12 && number <= 15) {
        controller = 12;
    } else {
        fprintf(stderr, "number is out of the expected range.\n");
        return 1;
    }

    /* 求余数,确定 pwm 编号 */
    int remainder = number % 4;
    char buf[10];
    sprintf(buf, "%d", remainder);

    /* 导出 pwm */
    sprintf(pwm_path, "/sys/class/pwm/pwmchip%d/pwm%d", controller, remainder);

    // 如果 pwm0 目录不存在,则导出
    if (access(pwm_path, F_OK)) {
        char temp[100];
        int fd;
        sprintf(temp, "/sys/class/pwm/pwmchip%d/export", controller);
        if (0 > (fd = open(temp, O_WRONLY))) {
            perror("open error");
            exit(-1);
        }
        // 导出 pwm
        if (1 != write(fd, buf, strlen(buf))) {
            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
5
6
7
#设置pwm周期 , pwm占空比

#设置pwm8
sudo ./pwm_test 8 1000000 500000

#查看pwm情况
sudo cat /sys/kernel/debug/pwm
未找到图片