Linux嵌入式系统开发之蜂鸣器---驱动篇
时间:2010-12-22 来源:☆&寒 烟☆
“啊!”我愣愣的揉着熟睡的双眼。“切,你是惦记着你的课程吧”。我一看小王红着脸,羞的不好意思了,赶快打圆场说(学计算机的脑袋就是转的快,好使,哈哈)。
“对对,我就是惦记着你的课程呢,你都没讲完…你去哪儿了,十天半月都没人,你有事不要紧,你不能耽误我的学习热情,知道不!”小王得着理就不放过我。
“唉,其实也没去哪儿,就是有点事,忙去了,所以…”残酷的现实告诉我,以后再不能给小王得理的机会啦…
“哦?是感情的事不?嘿嘿…”
“切..真是人小鬼大..”我用朦胧的眼勇敢的白了小王一眼。“不想跟你瞎说了,好了,开始今天的课程---Linux嵌入式系统开发之蜂鸣器---驱动篇”
说起蜂鸣器的驱动开发,就不能不说说硬件原理。PWM(脉冲宽度调制)简单的讲是一种变频技术之一,是靠改变脉冲宽度来控制输出电压,通过改变周期来控制其输出频率。根据S3C2440的手册介绍,S3C2440A内部有5个16位的定时器,定时器0、1、2、3都带有脉冲宽度调制功能(PWM),定时器4是一个没有输出引脚的内部定时器,定时器0有一个用于大电流设备的死区生成器。
我说的对不对,不相信,那好,咱有图为证:
根据上面的图,我们来总结一下2440内部定时器模块的特性吧:
1)共5个16位的定时器,定时器0、1、2、3都带有脉冲宽度调制功能(PWM);
2)每个定时器都有一个比较缓存寄存器(TCMPB)和一个计数缓存寄存器(TCNTB);
3)定时器0、1共享一个8位的预分频器(预定标器),定时器2、3、4共享另一个8位的预分频器(预定标器),其值范围是0~255;
4)定时器0、1共享一个时钟分频器,定时器2、3、4共享另一个时钟分频器,这两个时钟分频器都能产生5种不同的分频信号值(即:1/2、1/4、1/8、1/16和TCLK);
5)两个8位的预分频器是可编程的且根据装载的值来对PCLK进行分频,预分频器和钟分频器的值分别存储在定时器配置寄存器TCFG0和TCFG1中;
6)有一个TCON控制寄存器控制着所有定时器的属性和状态,TCON的第0~7位控制着定时器0、第8~11位控制着定时器1、第12~15位控制着定时器2、第16~19位控制着定时器3、第20~22位控制着定时器4。
还是根据S3C2440手册的描述和上图的结构,要开始一个PWM定时器功能的步骤如下(假设使用的是第一个定时器):
1)分别设置定时器0的预分频器值和时钟分频值,以供定时器0的比较缓存寄存器和计数缓存寄存器用;
2)设置比较缓存寄存器TCMPB0和计数缓存寄存器TCNTB0的初始值(即定时器0的输出时钟频率);
3)关闭定时器0的死区生成器(设置TCON的第4位);
4)开启定时器0的自动重载(设置TCON的第3位);
5)关闭定时器0的反相器(设置TCON的第2位);
6)开启定时器0的手动更新TCNTB0&TCMPB0功能(设置TCON的第1位);
7)启动定时器0(设置TCON的第0位);
8)清除定时器0的手动更新TCNTB0&TCMPB0功能(设置TCON的第1位)。
由此可以看到,PWM的输出频率跟比较缓存寄存器和计数缓存寄存器的取值有关,而比较缓存寄存器和计数缓存寄存器的值又跟预分频器和时钟分频器的值有关;要使用PWM功能其实也就是对定时器的相关寄存器进行操作。手册上也有一个公式:定时器输出频率 = PCLK / {预分频器值 + 1} / 时钟分频值。
开发板上蜂鸣器原理图分析:
s3c2440芯片接口 蜂鸣器电路
由原理图可以得知,蜂鸣器是通过GPB0 IO口使用PWM信号驱动工作的,而GPB0口是一个复用的IO口,要使用它得先把他设置成TOUT0 PWM输出模式。
下面就开始激动人心的源码分析的时刻:
必要的头文件
#define DEVICE_NAME "pwm"
#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0
static struct semaphore lock;
/* 设置pwm频率函数,通过传递不同的参数,改变频率,实现对声音的控制
* 函数在ioctl中,需要发出声音时调用
* 定时器输入时钟频率= PCLK/(预分频值 + 1)/(除值)
*/
static void PWM_Set_Freq( unsigned long freq ) { unsigned long tcon; //定时器控制寄存器,设置timer0-4的状态
unsigned long tcnt; //定时器缓冲区寄存器,通过TCNTB0\TCMPB0共同现实频率改变,发出不同的声音
unsigned long tcfg1; //定时器配置寄存器1,设置各个pwm定时器的除值各DMA应答通道
unsigned long tcfg0; //定时器配置寄存器0,设置定时器0-4的预分频值
struct clk *clk_p;
unsigned long pclk;
////设置复用端口:GPB0端口为OUTPUT
3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPB0_TOUT0);
tcon = __raw_readl(S3C2410_TCON);//读取定时器控制寄存器
tcfg1 = __raw_readl(S3C2410_TCFG1);//读取配置寄存器1
tcfg0 = __raw_readl(S3C2410_TCFG0);//读取配置寄存器2
//设置预分频率为50
tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK;
tcfg0 |= (50 - 1);
//设置PWM定时器除值为1/16
tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK;
tcfg1 |= S3C2410_TCFG1_MUX0_DIV16;//设置tcfg1的值为0x0011即:1/16
__raw_writel(tcfg1, S3C2410_TCFG1);
__raw_writel(tcfg0, S3C2410_TCFG0);
//获取系统时钟频率
clk_p = clk_get(NULL, "pclk");
pclk = clk_get_rate(clk_p);
tcnt = (pclk/50/16)/freq; // 定时器输入时钟频率=PCLK/(预分频值+1)/(除值)
//向TCNT\TCMPB写入相应的频率,实现不同的声音
__raw_writel(tcnt, S3C2410_TCNTB(0));
__raw_writel(tcnt/2, S3C2410_TCMPB(0));
//发声结束后,关闭死区、自动重载、关反相器、更新TCNTB0&TCMPB0、启动定时器0 tcon &= ~0x1f;
tcon |= 0xb;
__raw_writel(tcon, S3C2410_TCON);
tcon &= ~2;//清除定时器0的手动更新位
__raw_writel(tcon, S3C2410_TCON);
}
//关闭发出声音时调用
static void PWM_Stop(void)
{ //由硬件图可知BEEP通过S3C2440的GPB0端口相连,设置GPB0为输出端口
s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);
s3c2410_gpio_setpin(S3C2410_GPB(0), 0);
}
//打开设备函数
static int s3c24xx_pwm_open(struct inode *inode, struct file *file)
{
if (!down_trylock(&lock))
return 0;
else
return -EBUSY;
}
//释放设备
static int s3c24xx_pwm_close(struct inode *inode, struct file *file)
{
PWM_Stop();
up(&lock);
return 0;
}
//ioctl函数,实现对用户传递的命令进行处理
static int s3c24xx_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case PWM_IOCTL_SET_FREQ://发声
if (arg == 0)
return -EINVAL;
PWM_Set_Freq(arg);
break;
case PWM_IOCTL_STOP://不发声
PWM_Stop();
break;
}
return 0;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = s3c24xx_pwm_open,
.release = s3c24xx_pwm_close,
.ioctl = s3c24xx_pwm_ioctl,
};
/*
* 定义misc设备结构体,其中包括fops,需调用进行配置,当然也可直接在misc中进行配置
* 因为misc设备的主设备号都是10,所以不需要再进行定义,而次设备号此处通过MISC_DYNAMIC_MINOR
* 进行自动分配
*/
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
//初始函数
static int __init dev_init(void)
{
int ret;
init_MUTEX(&lock);//初始化信号量
ret = misc_register(&misc);//注册为misc设备类型的驱动
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
//退出函数
static void __exit dev_exit(void)
{
misc_deregister(&misc);//注册为misc设备类型的驱动
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hanyan225");
“这次讲的还行吧,离开了半个月,也不知道我讲课的技术下降了没有..”惭愧的心情也让我少了往日的活泼。
“嗯,挺好的,详细了不好,还给了很多的原理…”一会儿阴霾,一会儿阳关,这就是可爱的小王。“对了,我说过你笨没有,今天有个女生说我老是说她笨,气的要跟我绝交啊,我..”我无奈的补充到。
“没有啊, 我就是觉得你对我太好了,让我都有点那个了…”小王有趣的笑道。
“唉,我是一直给她玩笑的,伤心而令人头痛的90后啊…”
“切,90后的小女生,你还欺负,不想理你啦..”小王气愤的说."等等,你先给我说说这个驱动怎么和上节的应用篇联系起来使用,等我明白了再不理你"。
啊!这啥世道,那也太没面子了吧,喜欢…我也就只剩下甩手的份了。
通过下边的两条命令:
insmod ./leds_control.ko
mknod /dev/leds_control c 150 0
然后运行上节应用篇的知识,这下听到了你所谓的蚊子叫了吧…嘿嘿