欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

PWM脉冲宽度调制(一)

程序员文章站 2022-06-08 20:50:33
...

一、PWM模式

脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。

PWM脉冲宽度调制(一)

PWM本质是一个定时器,ARR寄存器中填充一个最大值,计数器从0计数至ARR,然后再跳变至0开始重新计数。

CCRx寄存器中存放一个阈值,当计数器中的值小于CCRx时,PWM输出低电平,当大于CCRx时输出高电平。


二、用途

可以实现调节LCD的背光度、蜂鸣器的音调、LED的亮度等。

在S5PV210中,PWM定时器的特点如下:

  • 拥有5个PWM定时器,其中,Timer0~Timer3各拥有一个PWM输出Pin。
  • 时钟输入源为APB-PCLK,我在时钟初始化时将PCLK配为66.7MHz,通过一系列的分频,实现预期的tick周期。
  • PWM的每次tick结束都会产生一个内部中断,我们可以在中断处理函数中添加需要处理的功能。

三、内部流程

PWM脉冲宽度调制(一)

以Timer0为例(XpwmTOUT0):

输入时钟为PCLK,先经过一个8Bit的预分频,随后在进行一次分频(1/1, 1/2, 1/4, 1/8, 1/16选1个),随后进入核心部分的控制单元(Control Logic0),最后输出PWM波形xPWMTOUT0。其中,核心的部分就在图中的Control Logic的部分。

PWM Cycle

先看一个简单的PWM周期,如下图:

首先,上图中的位置2到位置5的部分,成为一个PWM周期。依次列出每个时刻做了什么事情。

1. 设置寄存器TCNTBn=159(50+109), TCMPBn=109, 设置manual-update on,此时,TCNTBn中的值被拷贝到TCNTn中,TCMPBn中的值被拷贝到TCMPn中。

2. 设置manual-update off, 设置start位,表示开始定时,此时,TCNTn开始倒计时,每隔1个tick减一,即:159,、158、157......

3. 当TCNTn中的值从159减少到109时,即:TCNTn的值 == TCMPn的值时,电平跳转,由0变为1。

4. 到TCNTn继续递减为0时,产生内部中断。

5. 又过了1个tick,若此时auto-reload被置位,则TCMPBn和TCNTBn被重新加载到TCNTn和TCMPn中,开始一个新的PWM周期。

上面涉及到几个关键点再次提出

1. 当我们设定了auto-reload时,只有当TCNTn的值递减为0时,TCMPBn和TCNTBn才会被重新加载到TCNTn和TCMPn中。

2. 一开始,当我们设定完TCMPBn和TCNTBn后,只有将manual-update打开,才能手动将TCMPBn和TCNTBn加载到TCNTn和TCMPn中,随后我们又将manual-update关闭

3. PWM周期开始时,默认的初始电平为低电平,但这是可以通过寄存器修改的。

4. 当TCNTn  == TCMPn时,电平跳转,所以,我们可以通过修改TCMPBn的值,来修改高电平的占空比。


再看一下下面这张图,描述了PWM的double buffer机制:

流程和原理与上面类似,重点看一下开头连续两次的设定:

1. 第一次设定TCNTBn=3,TCMPBn=1,将manual update置1,这样,就会使TCNTn=TCNTBn=3,TCMPn=TCMPBn=1

2. 第二次设定TCNTBn=2,TCMPBn=0,将manual update置0,这样,虽然PWM cycle还没开始,但是第二个PWM周期的值已经被设定好,这就是所谓的double buffer机制。

3. 在第二个PWM周期开始时,auto-reload被置0,因此在第二个PWM结束后,就不会继续reload,就结束了。

关键寄存器

以下均以Timer1为例。

两次分频

TCFG0,0xE250_0000

可以进行1~255的预分频。

TCFG1,0xE250_0004

可以进行1/1, 1/2, 1/4, 1/8, 1/16 分频,或者直接选择SCLK_PWM。

TCNTBn和TCMPBn

TCNTB1, 0xE250_0018

TCMPB1, 0xE250_001C

控制寄存器

TCON, 0xE250_0008

 

实例

下面的例子是通过Timer1输出的PWM波形来控制蜂鸣器发声,控制的方式有两种,通过用户空间的ioctl操作和文件系统的echo操作,可调节的参数有蜂鸣器的频率、响度、持续时间。

注:蜂鸣器的频率即PCLK分频后的输出频率,响度即PWM的占空比。

/*
 * 蜂鸣器的字符设备驱动
 * 两种方式打开蜂鸣器
 * 方法1: 通过ioctl操作/dev/my_beep,代码实例:test/beep_test.c
 * 方法2: 通过文件系统操作:
 *             step1:向/sys/devices/virtual/my_beep/my_beep路径下的frequency、volume、sec文件写入参数
 *             step2:echo 1 > /sys/devices/virtual/my_beep/my_beep/do_beep,来启动蜂鸣器
 */

#include <linux/delay.h>
#include <linux/input.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>

#include <mach/map.h>
#include <mach/gpio.h>
#include <mach/map.h>
#include <mach/irqs.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-core.h>
#include <plat/gpio-cfg.h>
#include <plat/gpio-cfg-helpers.h>
#include <plat/regs-timer.h>
#include <plat/clock.h>

#define SOUND_FREQENCY_BASE  667

volatile int *CLK_GATE_IP3 = NULL;
static dev_t devno;
static struct cdev *beep_dev;
static struct class *beep_class;
static struct device *beep_class_dev;
static unsigned long pclk;
static int beep_irq;
static unsigned long beep_cnt = 0;

static int fs_beep_freq;
static int fs_beep_volume;
static long fs_beep_sec;

static int beep_on(unsigned int snd_volume, unsigned int snd_freq, unsigned long beep_sec)
{
    unsigned long tcon;
    unsigned long tcfg0;
    unsigned long tcfg1;
    unsigned long tcntb1;
    unsigned long tcmpb1;
    unsigned long freq;

    /*clear relate bits*/
    tcon = __raw_readl(S3C2410_TCON);
    tcfg0 =__raw_readl(S3C2410_TCFG0);
    tcfg1 =__raw_readl(S3C2410_TCFG1);

    tcfg0 &= ~0xFF;
    __raw_writel(tcon, S3C2410_TCFG0);
    tcfg1 &= ~(0xF << 4);
    __raw_writel(tcon, S3C2410_TCFG1);
    tcon &= ~(0xF << 8);
    __raw_writel(tcon, S3C2410_TCON);


    /*Timer Input Clock Frequency = PCLK / ( {prescaler value + 1} ) / {divider value}
     *set prescaler    = 1
     *set divider = 2
     *so , input clock frequency = 66.7MHz / 2 / 2 = 16.675MHz
     */
    tcfg0 |= 0x01;
    __raw_writel(tcfg0, S3C2410_TCFG0);
    tcfg1 |= 1 << 4;
    __raw_writel(tcfg1, S3C2410_TCFG1);

    /*enable auto-reload, set TCNTB1 & TCMPB1, then disable manual-update*/
    tcon |= 1 << 11;
    tcntb1 = 101 * SOUND_FREQENCY_BASE * (10 - snd_freq);
    tcmpb1 = snd_volume * SOUND_FREQENCY_BASE * (10 - snd_freq);
    freq = pclk/4/(tcntb1+1);
    beep_cnt = freq * beep_sec;
    printk("beep frequency = %ldHz \n" , freq);
    __raw_writel(tcon, S3C2410_TCON);
    __raw_writel(tcntb1, S3C2410_TCNTB(1));
    __raw_writel(tcmpb1, S3C2410_TCMPB(1));

    /*enable manual-update and then disable manual-update, let TCNTB1 and TCMPB1 load to TCNT1 and TCMP1*/
    tcon |= 1 << 9;
    __raw_writel(tcon, S3C2410_TCON);
    tcon &= ~(1 << 9);
    __raw_writel(tcon, S3C2410_TCON);

    /*start timer*/
    tcon |= 1 << 8;
    __raw_writel(tcon, S3C2410_TCON);

    return 0;
}

static int beep_off(void)
{
    unsigned long tcon;
    tcon = __raw_readl(S3C2410_TCON);
    tcon &= ~(1 << 8);
    tcon &= ~(1 << 9);
    __raw_writel(tcon, S3C2410_TCON);

    return 0;
}

static int beep_dev_open(struct inode *inode, struct file *file)
{
    /*open函数中没什么要做的,在此我们打印PCLK(pwm输入时钟)的频率*/
    struct clk *clk_p;
    clk_p = clk_get(NULL, "pclk");
    pclk = clk_get_rate(clk_p);
    printk("pclk rate : %ldHz\n", pclk);

    return 0;
}

static int beep_dev_release(struct inode *inode, struct file *file)
{
    /*stop beep*/
//    beep_off();
    return 0;
}

/*
 * snd_lvl: 1 ~ 100
 * snd_freq: 0 ~ 9
 */
static int beep_dev_ioctl(struct file *file, unsigned int beep_on_off, unsigned int (*beep_args)[])
{
    unsigned int snd_lvl = (*beep_args)[0];
    unsigned int snd_freq = (*beep_args)[1];
    unsigned long beep_sec = (*beep_args)[2];

    if(beep_on_off > 0)
    {
        if(snd_lvl < 1 || snd_lvl > 100)
        {
            printk("error: sound volume should from 1 ~ 100 !\n");
            return -1;
        }
        if(snd_freq < 0 || snd_freq > 9)
        {
            printk("error: sound frequency level should from 0 ~ 9 !\n");
            return -1;
        }
        if(beep_sec <= 0)
        {
            printk("error: beep seconds should > 0 !\n");
            return -1;
        }
        printk("snd_lvl = %d, snd_freq= %d\n", snd_lvl, snd_freq);
        beep_on(snd_lvl, snd_freq, beep_sec);
    }
    else
        beep_off();

    return 0;
}

static irqreturn_t my_beeq_irq_handler(int irq,void *dev_id,struct pt_regs *regs)
{
    beep_cnt--;
    int tint_cstat = __raw_readl(S3C64XX_TINT_CSTAT);
    tint_cstat |= 1 << 6;
    __raw_writel(tint_cstat, S3C64XX_TINT_CSTAT);
    if(beep_cnt == 0)
        beep_off();
    return (IRQ_HANDLED);
}

static size_t show_beep_frequency(struct device *dev, struct device_attribute *attr, char *buf)
{
    ssize_t ret = 0;
    sprintf(buf, "%d (value:0~9)\n", fs_beep_freq);
    ret = strlen(buf) + 1;
    return ret;
}

static size_t store_beep_frequency(struct device *dev, struct device_attribute *attr, char *buf, size_t len)
{
    int freq = (unsigned int)simple_strtoull(buf, NULL, 10);
    if(freq < 0 || freq > 9)
    {
        printk("error: sound frequency level should from 0 ~ 9 !\n");
        return -1;
    }
    fs_beep_freq = freq;

    return len;
}
static DEVICE_ATTR(frequency, 0666, show_beep_frequency, store_beep_frequency);

static size_t show_beep_volume(struct device *dev, struct device_attribute *attr, char *buf)
{
    ssize_t ret = 0;
    sprintf(buf, "%d (value:1~100)\n", fs_beep_volume);
    ret = strlen(buf) + 1;
    return ret;
}

static size_t store_beep_volume(struct device *dev, struct device_attribute *attr, char *buf, size_t len)
{
    int volume = (unsigned int)simple_strtoull(buf, NULL, 10);
    if(volume < 1 || volume > 100)
    {
        printk("error: sound volume should from 1 ~ 100 !\n");
        return -1;
    }
    fs_beep_volume = volume;

    return len;
}
static DEVICE_ATTR(volume, 0666, show_beep_volume, store_beep_volume);

static size_t show_beep_sec(struct device *dev, struct device_attribute *attr, char *buf)
{
    ssize_t ret = 0;
    sprintf(buf, "%d (value: > 0)\n", fs_beep_sec);
    ret = strlen(buf) + 1;
    return ret;
}

static size_t store_beep_sec(struct device *dev, struct device_attribute *attr, char *buf, size_t len)
{
    unsigned long sec = simple_strtoull(buf, NULL, 10);
    if(sec <= 0)
    {
        printk("error: beep seconds should > 0 !\n");
        return -1;
    }
    fs_beep_sec = sec;

    return len;
}
static DEVICE_ATTR(sec, 0666, show_beep_sec, store_beep_sec);

static size_t fs_do_beep(struct device *dev, struct device_attribute *attr, char *buf, size_t len)
{
    int is_beep = (unsigned int)simple_strtoull(buf, NULL, 10);
    if(is_beep > 0)
    {
        if(fs_beep_freq < 0 || fs_beep_freq > 9
            || fs_beep_volume < 1 || fs_beep_volume > 100
            || fs_beep_sec < 1)
        {
            printk("error: condition not match! \n");
            return len;
        }
        struct clk *clk_p;
        clk_p = clk_get(NULL, "pclk");
        pclk = clk_get_rate(clk_p);

        beep_on(fs_beep_volume, fs_beep_freq, fs_beep_sec);
    }
    return len;
}
static DEVICE_ATTR(do_beep, 0222, NULL, fs_do_beep);

static struct file_operations beep_ops =
{
    .owner = THIS_MODULE,
    .open = beep_dev_open,
    .release = beep_dev_release,
    .unlocked_ioctl = beep_dev_ioctl
};

static int tq210_beep_init(void)
{
    int major;
    unsigned long tint_cstat;

    /*确保PWM的clock gating打开*/
    CLK_GATE_IP3 = (int *)ioremap(0xE010046C, 4);

    *CLK_GATE_IP3 |= 1 << 23;

    /*设置gpio控制寄存器*/
    s3c_gpio_cfgpin(S5PV210_GPD0(1), S3C_GPIO_SFN(2));
    /*不使用上拉下拉电阻*/
    s3c_gpio_setpull(S5PV210_GPD0(1), S3C_GPIO_PULL_NONE);

    /*下面是创建、添加一个字符设备*/
    if(alloc_chrdev_region(&devno, 0, 1, "my_tq210_beep") < 0)
    {
        printk("alloc_chrdev_region 'my_beep' fail ! \n");
        return -1;
    }

    major = MAJOR(devno);
    beep_dev = kmalloc(sizeof(struct cdev), GFP_KERNEL);
    cdev_init(beep_dev, &beep_ops);
    beep_dev->owner = THIS_MODULE;

    if(cdev_add(beep_dev, devno, 1) < 0)
    {
        printk("cdev_add 'beep_dev' fail ! \n");
        return -1;
    }

    /*创建设备节点*/
    beep_class = class_create(THIS_MODULE, "my_beep");
    beep_class_dev = device_create(beep_class, NULL, MKDEV(major, 0), NULL, "my_beep", 0);
    if (device_create_file(beep_class_dev, &dev_attr_frequency) < 0)
    {
        printk("error: create attr frequency error!\n");
        return -1;
    }
    if (device_create_file(beep_class_dev, &dev_attr_volume) < 0)
    {
        printk("error: create attr volume error!\n");
        return -1;
    }
    if (device_create_file(beep_class_dev, &dev_attr_sec) < 0)
    {
        printk("error: create attr seconds error!\n");
        return -1;
    }
    if (device_create_file(beep_class_dev, &dev_attr_do_beep) < 0)
    {
        printk("error: create attr do_beep error!\n");
        return -1;
    }

    /*打开中断,请求中断*/
    tint_cstat = __raw_readl(S3C64XX_TINT_CSTAT);
    tint_cstat |= 1 << 1;
    tint_cstat |= 1 << 6;
    __raw_writel(tint_cstat, S3C64XX_TINT_CSTAT);

    beep_irq = request_irq(IRQ_TIMER1, my_beeq_irq_handler, IRQF_DISABLED, "my_beep", NULL);
    if(beep_irq < 0)
    {
        printk("request timer 1 irq (no = %d) fail!\n", IRQ_TIMER1);
        return -1;
    }
    printk("my tq210 beep init finish! \n");

     return 0;
}

static void tq210_beep_exit(void)
{
    cdev_del(beep_dev);
    kfree(beep_dev);
    unregister_chrdev_region(devno, 1);
    device_unregister(beep_class_dev);
    class_destroy(beep_class);
    free_irq(beep_irq, NULL);
}

module_init(tq210_beep_init);
module_exit(tq210_beep_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("chuck_huang / aaa@qq.com");
MODULE_DESCRIPTION("PWM for beep driver");

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int main(int argc, char *argv[])
{
    if (argc != 5)
    {
        printf("arg format:\n\
            argv[1]: 0-> sound off / 1-> sound on\n\
            argv[2]: sound volume, from 1 to 100\n\
            argv[3]: sound frequency level, from 0 to 9\n\
            argv[4]: beep secounds\n\
            e.g.: beep_test 1 50 5 2\n");
        return -1;
    }
    int fd;

    int beep_args[3];
    beep_args[0] = atoi(argv[2]);
    beep_args[1] = atoi(argv[3]);
    beep_args[2] = atoi(argv[4]);
    fd = open("/dev/my_beep", O_RDWR);

    if(fd < 0)
    {
        printf("open /dev/my_beep error ! \n");
        return -1;
    }
    
    /*
    * ioctl (*file, int cmd, int (*beep_args)[2])
    * cmd: 0-> sound off / 1-> sound on
    * (*beep_args)[0] : snd_lvl : from 1 to 100
    * (*beep_args)[1] : snd_freq : from 1 to 10
    * (*beep_args)[2] : beep_sec : from 1 to 10
    */
    ioctl(fd, atoi(argv[1]), &beep_args);
    close(fd);

    return 0;
}


















































































相关标签: PWM