[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");
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; }