PWM脉冲宽度调制(一)
一、PWM模式
脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。
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结束都会产生一个内部中断,我们可以在中断处理函数中添加需要处理的功能。
三、内部流程
以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;
}
下一篇: 使用其他定时器作为HAL的基础时钟