[S5PV210] PWM

概述

PWM:Pulse Width Modulation, 脉冲宽度调制。

通过PWM调整高低电平的占空比,我们可以实现调节如LCD的背光亮度、蜂鸣器的音调等,本文采用蜂鸣器作为实例。

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

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

内部流程

原理图

 

以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减一,即:109,、108、107......

3. 当TCNTn中的值从109减少到110时,即: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 / huangch28@sina.com");
MODULE_DESCRIPTION("PWM for beep driver");
View Code

 

ioctl代码如下:

#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;
}
View Code

 

posted on 2015-02-05 20:31  青梦吾源  阅读(1568)  评论(0编辑  收藏  举报

导航