IGKBoard(imx6ull)-PWM编程蜂鸣器编程控制


1- PWM介绍

PWM(Pulse Width Modulation),是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通过调节占空比的变化来调节信号、能量等的变化。
高电平占多一点,也是占空比大一点亮度就亮一点,占空比小一点亮度就没有那么亮,前提是PWM的频率要大于我们人眼识别频率(大约80Hz以上最好)。在电机驱动、无源蜂鸣器驱动、LCD屏幕背光调节、逆变电路等都会有应用。
我们来介绍一下PWM中涉及到的参数:

  • PWM的频率:
    是指1秒钟内信号从高电平到低电平再回到高电平的次数(如下图一个周期),即一秒钟PWM有多少个周期。50H表示1s有50个周期。

  • PWm周期:
    周期=1/频率,50Hz = 1/50 s = 0.02s = 20ms ,如果频率为50Hz ,也就是说一个周期是20ms那么一秒钟就有 50次PWM周期。

  • 占空比:
    是一个脉冲周期内,高电平(如下图的脉宽时间)的时间与整个周期时间的比例(百分数表示),如下图脉宽时间占总周期时间的比例,即占空比。

在这里插入图片描述


2- PWM使能

在开发板原理图我们可以看见40pin扩展口的PWM7和PWM8,实际上开发板上有4路PWM,如下:

PWM1 ---> backlight //LCD背光
PWM2 ---> beep //蜂鸣器
PWM7,PWM8 ---> 40pin扩展 //需要使能开启

在这里插入图片描述

(1)添加配置

我们需要修改相关配置文件才能使用40pin扩展口的PWM7和PWM8,如下修改(添加)配置文件,添加两个管脚的PWM支持,然后关机重启即可。

root@igkboard:~# vi /run/media/mmcblk1p1/config.txt
# Enable PWM overlays, PWM8 conflict with UART8(NB-IoT/4G module)
dtoverlay_pwm=7 8

(2)export、unexport与npwm属性文件

PWM 同样也是通过 sysfs 方式进行操控,进入到/sys/class/pwm 目录下,可以看到四个pwmchip?命名的文件夹,这4个文件夹其实就对应了IMX6ULL的4个PWM控制器(分别对应pwm1,pwm2,pwm7,pwm8):

root@igkboard:/sys/class/pwm# ls
pwmchip0 pwmchip1 pwmchip2 pwmchip3

进入pwmchip1查看文件我们发现三个属性文件。

  • npwm:这是一个只读属性,读取该文件可以得知该PWM控制器下共有几路PWM输出,如下所示:
root@igkboard:/sys/class/pwm/pwmchip1# cat npwm
1
  • export:与GPIO控制一样,在使用PWM之前,也需要将其导出,通过export属性进行导出,以下所示(可以看见多出来了pwm0,注意导出的编号(echo 0)必须小于npwm(1)的值)
root@igkboard:/sys/class/pwm/pwmchip1# echo 0 > export
root@igkboard:/sys/class/pwm/pwmchip1# ls
consumers device export npwm power pwm0 subsystem suppliers uevent
unexport
  • uexport:unexport:将导出的PWM删除。当使用完PWM之后,我们需要将导出的PWM删除,如下所示(可以看见pwm0不见了)
root@igkboard:/sys/class/pwm/pwmchip1# echo 0 > unexport
root@igkboard:/sys/class/pwm/pwmchip1# ls
consumers device export npwm power subsystem suppliers uevent unexport

(3)duty_cycle、enable和period属性文件

我们进入pwm0可以看见duty_cycle、enable和period三个文件属性:

  • enable:可读可写,写入"0"表示禁止PWM;写入"1"表示使能PWM。读取该文件获取PWM当前是禁止还是使能状态。
echo 0 > enable #禁止PWM输出
echo 1 > enable #使能PWM输出
  • period:用于配置PWM周期,可读可写;写入一个字符串数字值,以ns(纳秒)为单位,譬如配
    置PWM周期为10us(微秒)。
echo 10000 > period #PWM周期设置为10us(10 * 1000ns)
  • duty_cycle:用于配置PWM的占空比,可读可写;写入一个字符串数字值,同样也是以ns为单位。
echo 5000 > duty_cycle #PWM占空比设置为5us

3- PWM测试编程

(1)PWM8管脚连接

在这里插入图片描述
在这里插入图片描述

(2)流程介绍及源码

我们先来梳理一下流程:

  1. 获取终端输入的字符串,拼接字符串/sys/class/pwm/pwmchip/pwm0(?就是终端需要输入的访问哪一个PWM控制器)
  2. 判断上述路径文件是否存在,存在则修改其中的duty_cycle、enable和period文件属性,不存在就需要打开export属性文件写入0,再打开pwm0修改文件属性
  3. 修改文件属性自定义函数,就是打开文件写入即可

源码:
一些不常见的函数都有注释帮助了解。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

char pwm_path[100];
static int pwm_config(const char *attr, const char *val);

int main(int argc, char *argv[])
{
    char temp[100];
    int fd = -1;//文件描述符

    /*运行的时候需要输入四个参数:执行文件,pwmchip?,周期,占空比*/
    if(4 != argc)
    {
        printf("Please enter four parameters: %s <id> <period> <duty>\n", argv[0]);
        exit(-1);//进程正常退出是exit(0),异常退出是exit(非0)
    }
    
    printf("Pwm config: id<%s> period<%s> duty<%s>\n", argv[1],argv[2],argv[3]);

    /*接下来就是打开文件执行相关命令*/
    memset(pwm_path, 0, sizeof(pwm_path));//有数组使用之前都需要memset一下
    snprintf(pwm_path, sizeof(pwm_path), "/sys/class/pwm/pwmchip%s/pwm0", argv[1]);

    /*如果pwm_path不能打开,export:与GPIO控制一样,在使用PWM之前,也需要将其导出,通过export属性进行导出*/
    /*
         int access(const char *pathname,int mode)参数:
         pathname:表示要测试的文件的路径
         mode:表示测试的模式可能的值有:
         R_OK:是否具有读权限
         W_OK:是否具有可写权限
         X_OK:是否具有可执行权限
         F_OK:文件是否存在
         返回值:若测试成功则返回0,否则返回-1 
    */
    if(access(pwm_path, F_OK))
    {
        memset(temp, 0, sizeof(temp));
        snprintf(temp, sizeof(temp), "/sys/class/pwm/pwmchip%s/export", argv[1]);
        fd = open(temp, O_WRONLY);
        if(fd < 0)
        {
            printf("open pwmchip%s error: %s\n", argv[1], strerror(errno));
            exit(-1);
        }
        /*写入文件0,1个字节*/
        if(1 != write(fd, "0", 1))
        {
            printf("write '0' to pwmchip%s/export error\n", argv[1]);
            close(fd);
            exit(-1);
        }
        printf("export Failed to export\n");
        close(fd);
    }
    /*配置pwm周期*/
    if(pwm_config("period", argv[2]))
    {
        //printf("Configuration cycle failure.\n");
        exit(-1);    
    }

    /*配置占空比*/
    if(pwm_config("duty_cycle", argv[3]))
    {
        //printf("Failed to configure the duty cycle.\n");
        exit(-1);    
    }

    /*使能pwm*/
    pwm_config("enable", "1");

    return 0;
}

/*pwm配置函数,attr:属性文件名字,val:属性的值*/
static int pwm_config(const char *attr, const char *val)
{
    char file_path[100];
    int fd = -1;
    int len;

    /*判断参数*/
    if(attr == NULL || val == NULL)
    {
        printf("[%s] argument error\n", __FUNCTION__);
        return -1;
    }
    memset(file_path, 0, sizeof(file_path));
    snprintf(file_path, sizeof(file_path), "%s/%s", pwm_path, attr);
    fd = open(file_path, O_WRONLY);
    if(fd < 0)
    {   
        printf("[%s] open %s error\n", __FUNCTION__, file_path);
        return fd;
    }
    len = strlen(val);
    /*如果顺利write()会返回实际写入的字节数*/
    if(len != write(fd, val, len))
    {
            printf("[%s] write %s to %s error\n", __FUNCTION__, val, file_path);
            close(fd);
            return -2;
    }
    close(fd);
    return 0;
}

Makefile文件:

CC=arm-linux-gnueabihf-gcc
APP_NAME=pwm_test

all:clean
	@${CC} ${APP_NAME}.c -o ${APP_NAME}

clean:
	@rm -f ${APP_NAME}

make之后生成了ARM架构上运行的文件了。

wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/tftpboot$ make
wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/tftpboot$ file pwm_test
pwm_test: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=c8161112e18f74d738f5e089f49ad0594037e73f, for GNU/Linux 3.2.0, not stripped

(3)运行结果

tftp服务器不会搭建的可以参考这篇文章:wpa_supplicant无线网络配置imx6ull以及搭建tftp服务器
tftp服务器下载文件到开发板:

root@igkboard:~# tftp -gr pwm_test 192.168.0.134
root@igkboard:~# chmod a+x pwm_test 

运行(pwmchip3(PWM8)):

root@igkboard:~# ./pwm_test 3 10000 1000 
PWM config: id<3>, period<10000>, duty<1000>
root@igkboard:~# ./pwm_test 3 10000 3000 
PWM config: id<3>, period<10000>, duty<3000>
root@igkboard:~# ./pwm_test 3 10000 7000 
PWM config: id<3>, period<10000>, duty<7000>
root@igkboard:~# ./pwm_test 3 10000 9000  
PWM config: id<3>, period<10000>, duty<9000> 

可以发现我们的LED灯越来越亮。

运行(pwmchip1( PWM1蜂鸣器)):

root@igkboard:~# ./pwm_test 1 10000 5000
PWM config: id<1>, period<10000>, duty<5000>
root@igkboard:~# ./pwm_test 1 10000 0
PWM config: id<1>, period<10000>, duty<0>

可以听到蜂鸣器一直在叫,占空比为0的时候就不会叫了,人多的时候还是别放了,挺吵的。


4- pwm编程实现音乐播放

我们先来熟悉一下流程:

  1. 在打开pwm0的基础上我们继续实现。
  2. 首先我们需要了解乐谱的相关信息,将低、中、高音频率分组方便调节,相当于不同的音调。如果是高音音调,只需要将上面的频率乘以 2,如果是低音音调,需要将上面的音频除以2。
    在这里插入图片描述
  3. 我们想要连续播放,就需要在一个循环中不断改变频率,于是需要数组,保存我们需要演奏的曲子乐谱,乐谱数组中就是我们的音调。当然还需要注意休眠的时间,ms级函数的实现。

源码(在第一个代码的基础上添加修改)

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>

typedef struct pwm_one_s
{
    unsigned int msec;//持续的时间,单位毫秒
    unsigned int freq;//频率
    unsigned char duty;//占空比,百分数*100
}pwm_one_t;

#define CX CM
static const unsigned short CL[8]  = {0, 131, 147, 165, 175, 196, 211, 248};
static const unsigned short CM[8]  = {0, 262, 294, 330, 350, 393, 441, 495};
static const unsigned short CH[8]  = {0, 525, 589, 661, 700, 786, 882, 990};

static unsigned short songP[] = 
{
    CX[1], CX[5], CX[5], CX[5], CX[6], CX[6], CX[5], CX[0],
    CX[4], CX[4], CX[3], CX[3], CX[2], CX[2], CX[1], CX[0],
    CX[5], CX[5], CX[4], CX[4], CX[3], CX[3], CX[2], CX[0],
};

static char pwm_path[100];
void mssleep(unsigned long ms);//毫秒级函数的实现
static int pwm_config(const char *attr, const char *val);//pwm配置函数
static int pwm_ring_one(pwm_one_t *pwm_ring);

int main(int argc, char *argv[])
{
    char temp[100];
    int fd = -1;//文件描述符
    pwm_one_t pwm_one_test;

    /*运行的时候需要输入四个参数:执行文件,pwmchip?,周期,占空比*/
    if(2 != argc)
    {
        printf("Please enter two parameters: %s <id>\n", argv[0]);
        exit(-1);//进程正常退出是exit(0),异常退出是exit(非0)
    }
    
    //printf("Pwm config: id<%s> period<%s> duty<%s>\n", argv[1],argv[2],argv[3]);

    /*接下来就是打开文件执行相关命令*/
    memset(pwm_path, 0, sizeof(pwm_path));//有数组使用之前都需要memset一下
    snprintf(pwm_path, sizeof(pwm_path), "/sys/class/pwm/pwmchip%s/pwm0", argv[1]);

    /*如果pwm_path不能打开,export:与GPIO控制一样,在使用PWM之前,也需要将其导出,通过export属性进行导出*/
    /*
         int access(const char *pathname,int mode)参数:
         pathname:表示要测试的文件的路径
         mode:表示测试的模式可能的值有:
         R_OK:是否具有读权限
         W_OK:是否具有可写权限
         X_OK:是否具有可执行权限
         F_OK:文件是否存在
         返回值:若测试成功则返回0,否则返回-1 
    */
    if(access(pwm_path, F_OK))
    {
        memset(temp, 0, sizeof(temp));
        snprintf(temp, sizeof(temp), "/sys/class/pwm/pwmchip%s/export", argv[1]);
        fd = open(temp, O_WRONLY);
        if(fd < 0)
        {
            printf("open pwmchip%s error: %s\n", argv[1], strerror(errno));
            exit(-1);
        }
        /*写入文件0,1个字节*/
        if(1 != write(fd, "0", 1))
        {
            printf("write '0' to pwmchip%s/export error\n", argv[1]);
            close(fd);
            exit(-1);
        }
        close(fd);
    }
    pwm_one_test.freq = 1000;
    pwm_one_test.duty = 50;
    pwm_one_test.msec = 100;
    pwm_config("enable","1");

    for(int i=0; i<(sizeof(songP)/sizeof(songP[0])); i++)
    {
        if(songP[i] == 0)
        {
            pwm_one_test.duty = 0;
        }
        else
        {
            pwm_one_test.duty = 15;
            pwm_one_test.freq = songP[i];
        }
        pwm_one_test.msec = 300;
        pwm_ring_one(&pwm_one_test);//这才是关键的函数
    }
    pwm_config("enable", "0");

    return 0;
}
static int pwm_ring_one(pwm_one_t *pwm_ring)
{
    unsigned long period = 0;
    unsigned long duty_cycle = 0;
    char period_str[20] = {};
    char duty_cycle_str[20] = {};

    /*占空比不可能大于100(0.1*100)*/
    if(!pwm_ring || pwm_ring->duty > 100)
    {
        printf("[INFO] %s argument error.\n", __FUNCTION__);
        return -1;
    }
    period = (unsigned long)((1.f / (double)pwm_ring -> freq)*1e9);//周期=1/频率(s)*1000 000 000ns
    duty_cycle = (unsigned long) (((double)pwm_ring->duty / 100.f)*(double)period);//15/100=15%,15%*一个周期 = 占空比(ns)

    snprintf(period_str, sizeof(period_str), "%ld", period);
    snprintf(duty_cycle_str, sizeof(duty_cycle_str), "%ld", duty_cycle);

    printf("period: %sns, duty_cycle_str: %sns\n", period_str, duty_cycle_str);

    if(pwm_config("period", period_str))
    {
        printf("pwm_config period failure.\n");
        return -1;
    }
    if(pwm_config("duty_cycle", duty_cycle_str))
    {
        printf("pwm_config duty_cycle failure.\n");
        return -2;
    }

    mssleep(pwm_ring->msec);

    if(pwm_config("duty_cycle", "0"))
    {
        printf("pwm_config duty_cycle failure.\n");
    }
    mssleep(20);
}

/*毫秒级函数的实现*/
void mssleep(unsigned long ms)
{
	struct timespec ts = { 
		  .tv_sec  = (long int) (ms / 1000), 
		  .tv_nsec = (long int) (ms % 1000) * 1000000ul 
	  };
	nanosleep(&ts, 0);
}

/*pwm配置函数,attr:属性文件名字,val:属性的值*/
static int pwm_config(const char *attr, const char *val)
{
    char file_path[100];
    int fd = -1;
    int len;

    /*判断参数*/
    if(attr == NULL || val == NULL)
    {
        printf("[%s] argument error\n", __FUNCTION__);
        return -1;
    }
    memset(file_path, 0, sizeof(file_path));
    snprintf(file_path, sizeof(file_path), "%s/%s", pwm_path, attr);
    fd = open(file_path, O_WRONLY);
    if(fd < 0)
    {   
        printf("[%s] open %s error\n", __FUNCTION__, file_path);
        return fd;
    }
    len = strlen(val);
    /*如果顺利write()会返回实际写入的字节数*/
    if(len != write(fd, val, len))
    {
            printf("[%s] write %s to %s error\n", __FUNCTION__, val, file_path);
            close(fd);
            return -2;
    }
    close(fd);
    return 0;
}
root@igkboard:~# tftp -gr pwm_music 192.168.0.134
root@igkboard:~# chmod a+x pwm_music 
root@igkboard:~# ./pwm_music 1
period: 3816793ns, duty_cycle_str: 572518ns
period: 2544529ns, duty_cycle_str: 381679ns
period: 2544529ns, duty_cycle_str: 381679ns
period: 2544529ns, duty_cycle_str: 381679ns
period: 2267573ns, duty_cycle_str: 340135ns
period: 2267573ns, duty_cycle_str: 340135ns
period: 2544529ns, duty_cycle_str: 381679ns
period: 2544529ns, duty_cycle_str: 0ns
period: 2857142ns, duty_cycle_str: 428571ns
period: 2857142ns, duty_cycle_str: 428571ns
period: 3030303ns, duty_cycle_str: 454545ns
period: 3030303ns, duty_cycle_str: 454545ns
period: 3401360ns, duty_cycle_str: 510204ns
period: 3401360ns, duty_cycle_str: 510204ns
period: 3816793ns, duty_cycle_str: 572518ns
period: 3816793ns, duty_cycle_str: 0ns
period: 2544529ns, duty_cycle_str: 381679ns
period: 2544529ns, duty_cycle_str: 381679ns
period: 2857142ns, duty_cycle_str: 428571ns
period: 2857142ns, duty_cycle_str: 428571ns
period: 3030303ns, duty_cycle_str: 454545ns
period: 3030303ns, duty_cycle_str: 454545ns
period: 3401360ns, duty_cycle_str: 510204ns
period: 3401360ns, duty_cycle_str: 0ns

还是Makefile编写之后编译成ARM架构上的文件,然后tftp服务器下载到开发板,给予可执行权限,运行就可以听见小星星的曲子了。


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值