- 论坛徽章:
- 0
|
一、开发环境 主 机:fedora 14 (2.6.33.7) 开发板:FL2440(nandflash:K9F1G08 128m) 编译器:arm-linux-gcc-4.3.2 二、原理分析 1. 什么是PWM
PWM(脉冲宽度调制)简单的讲是一种变频技术之一,是靠改变脉冲宽度来控制输出电压,通过改变周期来控制其输出频率。如果还不是很清楚,好吧,来看看我们实际生活中的例子,我们的电风扇为什么扭一下按扭,风扇的转速就会发生变化;调一下收音机的声音按钮,声音的大小就会发生变化;还有待会儿我们要讲的蜂鸣器也会根据不同的输入值而发出不同频率的叫声等等!!这些都是PWM的应用,都是通过PWM输出的频率信号进行控制的。 2. ARM Linux中的PWM 根据S3C2440的手册介绍,S3C2440A内部有5个16位的定时器,定时器0、1、2、3都带有脉冲宽度调制功能(PWM),定时器4是一个没有输出引脚的内部定时器,定时器0有一个用于大电流设备的死区生成器。看下图解释吧!! ![](http://blog.chinaunix.net/attachment/201102/21/21714580_1298271063csFu.jpg)
由S3C2440的技术手册和上面这幅结构图,我们来总结一下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} / 时钟分频值。下面我们来通过一个蜂鸣器的实例来说明PWM功能的使用。 3. 关于是蜂鸣器 1. 蜂鸣器的种类和工作原理 蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。
压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。有的压电式蜂鸣器外壳上还装有发光二极管。多谐振荡器由晶体管或集成电路构成。当接通电源后(1.5~15V直流工作电压),多谐振荡器起振,输出1.5~2.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声。
电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场。振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
有源蜂鸣器和无源蜂鸣器的区别:这个“源”字是不是指电源,而是指震荡源,即有源蜂鸣器内有振荡源而无源蜂鸣器内部没有振荡源。有振荡源的通电就可以发声,没有振荡源的需要脉冲信号驱动才能发声。
额外知识:简单蜂鸣器的制作方法
1)制备电磁铁M:在长约6厘米的铁螺栓上绕100圈导线,线端留下5厘米作引线,用透明胶布把线圈粘好,以免线圈松开,再用胶布把它粘在一个盒子上,电磁铁就做好了;
2)制备弹片P:从铁罐头盒上剪下一条宽约2厘米的长铁片,弯成直角,把电磁铁的一条引线接在弹片上,再用胶布把弹片紧贴在木板上;
3)用曲别针做触头Q,用书把曲别针垫高,用胶布粘牢,引出一条导线,如图连接好电路;
4)调节M与P之间的距离(通过移动盒子),使电磁铁能吸引弹片,调节触点与弹片之间的距离,使它们能恰好接触,通电后就可以听到蜂鸣声。
4. FL2440上的蜂鸣器接口 由原理图可以得知,蜂鸣器是通过GPB0 IO口使用PWM信号驱动工作的,而GPB0口试一个复用的IO口,要使用它的县把它设置成TOUT0 PWM输出模式。 ![](http://blog.chinaunix.net/attachment/201102/21/21714580_1298271414fH7x.jpg) 三、实现步骤 1. 编写适合FL2440开发板的蜂鸣器驱动程序。文件名为fl2440_pwm.c - /*
-
*==============================================
-
*Name : fl2440_pwm.c
-
*Author : y.q.yang
-
*Date : 21/2/2011
-
*Copyright : GPL
-
*Description : fl2440 pwm driver
-
*==============================================
-
*/
-
-
#include <linux/kernel.h>
-
#include <linux/module.h>
-
#include <linux/init.h>
-
#include <linux/errno.h>
-
#include <linux/cdev.h>
-
#include <linux/fs.h>
-
#include <linux/device.h>
-
#include <linux/clk.h>
-
//#include <linux/ioctl.h>
-
-
#include <mach/hardware.h>
-
#include <mach/regs-gpio.h>
-
#include <mach/gpio-fns.h>
-
#include <mach/gpio-nrs.h>
-
-
//#include <asm/clock.h>
-
#include <asm/io.h>
-
-
#include <plat/regs-timer.h>
-
-
-
#define S3C2410_GPIONO(bank,offset) ((bank) + (offset))
-
#define S3C2410_GPIO_BANKB (32*1)
-
#define S3C2410_GPB0 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 0)
-
#define S3C2410_GPB0_OUTP (0x1 << 0)
-
#define S3C2410_GPB0_TOUT (0x2 << 0)
-
-
-
#define PWM_MAJOR 0
-
#define PWM_NAME "fl2440_pwm"
-
-
static int device_major = PWM_MAJOR; //系统动态生成的主设备号
-
-
static int pwm_open(struct inode *inode, struct file *filp)
-
{
-
//对GPB0复用扣进行复用功能的设置,设置为TOUT0 PWM输出
-
s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_TOUT0);
-
-
return 0;
-
}
-
-
static int pwm_close(struct inode *inode, struct file *filp)
-
{
-
return 0;
-
}
-
-
static int pwm_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
-
{
-
//如果cmd等于0,则让蜂鸣器停止工作
-
if(cmd == 0)
-
{
-
//这里又恢复GPB0口为IO口输出功能,由原理图可知直接给低电平可让蜂鸣器停止工作
-
s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);
-
s3c2410_gpio_setpin (S3C2410_GPB0, 0);
-
}
-
else //输入的参数大于0,就让蜂鸣器开始工作,不同的参数,蜂鸣器的频率不一样
-
{
-
unsigned long tcon;
-
unsigned long tcnt;
-
unsigned long tcfg1;
-
unsigned long tcfg0;
-
-
struct clk *clk_p;
-
unsigned long pclk;
-
-
//以下对各寄存器的操作结合上面讲的开始一个pwm定时器的步骤和2440手册PWM寄存器操作部分来看就比较容易理解
-
tcfg1 = __raw_readl(S3C2410_TCFG1); //读取定时器0和1的配置的值
-
tcfg0 = __raw_readl(S3C2410_TCFG0);
-
-
tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; //S3C2410_TCFG_PRESCALER0_MASK = 255,把tcfg0设置全部清零
-
tcfg0 |= (50 - 1); //设置定时器0的预分频值为49
-
-
tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK; //S3C2410_TCFG1_MUX0_MASK = 15 << 0,把TCFG1[3:0]清零
-
tcfg1 |= S3C2410_TCFG1_MUX0_DIV16; //S3C2410_TCFG1_MUX0_DIV16 = 3 << 0,TCFG1[3:0]=0011即分频因子为16
-
-
__raw_writel(tcfg1, S3C2410_TCFG1); //把配置信息写入TCFG0和1配置定时器
-
__raw_writel(tcfg0, S3C2410_TCFG0);
-
-
clk_p = clk_get(NULL, "pclk"); //获取PCLK时钟频率
-
pclk = clk_get_rate(clk_p); //从系统平台时钟队列中获取pclk的时钟频率,在include/linux/clk.h中定义
-
tcnt = (pclk/50/16)/cmd; //计算定时器0的输出时钟频率(pclk/{prescaler0 + 1}/divider value)
-
-
__raw_writel(tcnt, S3C2410_TCNTB(0)); //设置定时器0计数缓冲寄存器的值
-
__raw_writel(tcnt, S3C2410_TCMPB(0)); //设置定时器0比较缓冲寄存器的值
-
-
tcon = __raw_readl(S3C2410_TCON); //读取定时器控制器的值
-
-
tcon &= ~0x1f; //把定时器0的配置全部清零
-
tcon |= 0xb; //关闭死区、自动重载、关反相器、更新TCNTB0&TCMTB0,启动定时器0
-
__raw_writel(tcon, S3C2410_TCON); //设置TCON的0-4位,即对定时器0进行配置
-
tcon &= ~2;
-
__raw_writel(tcon, S3C2410_TCON); //清除定时器0的手动更新位
-
}
-
-
return 0;
-
}
-
-
static struct file_operations pwm_fops =
-
{
-
.owner = THIS_MODULE,
-
.open = pwm_open,
-
.release = pwm_close,
-
.ioctl = pwm_ioctl,
-
};
-
-
//定义一个设备类
-
static struct class *pwm_class;
-
-
static int __init pwm_init(void)
-
{
-
//注册为字符设备,住设备号为0,让系统自动分配,设备名为fl2440_pwm,注册成功动态生成的主设备号
-
device_major = register_chrdev(PWM_MAJOR, PWM_NAME, &pwm_fops);
-
-
if(device_major < 0)
-
{
-
printk(PWM_NAME "register failed!\n");
-
return device_major;
-
}
-
-
//注册一个设备类,使mdev可以在/dev/目录下自动建立设备节点
-
pwm_class = class_create(THIS_MODULE, PWM_NAME);
-
-
if(IS_ERR(pwm_class))
-
{
-
printk("register class failed!\n");
-
return -1;
-
}
-
-
//创建一个设备节点,设备名为PWM_NAME,即fl2440_pwm
-
device_create(pwm_class, NULL, MKDEV(device_major, 0), NULL, PWM_NAME);
-
-
return 0;
-
}
-
-
static void __exit pwm_exit(void)
-
{
-
//注销设备
-
unregister_chrdev(device_major, PWM_NAME);
-
-
//删除设备节点
-
device_destroy(pwm_class, MKDEV(device_major, 0));
-
-
//注销设备类
-
class_destroy(pwm_class);
-
}
-
-
module_init(pwm_init);
-
module_exit(pwm_exit);
-
-
MODULE_LICENSE("GPL");
-
MODULE_AUTHOR("y.q.yang");
-
MODULE_DESCRIPTION("fl2440 pwm driver");
2. 将PWM蜂鸣器驱动代码部署到内核中。 - #cp /mnt/hgfs/share/fl2440_pwm.c linux-2.6.33.7/drivers/char/
-
-
#vim linux-2.6.33.7/drivers/char/Kconfig
-
config FL2440_PWM_BEEP
-
tristate "FL2440 PWM Device"
-
depends on ARCH_S3C2440
-
default y
-
---help---
-
FL2440 PWM Beep
-
-
#vim linux-2.6.33.7/drivers/char/Makefile
-
obj-$(CONFIG_FL2440_PWM_BEEP) += fl2440_pwm.o
3. 配置内核,选择PWM蜂鸣器设备选项 - #make menuconfig
-
-
Device Drivers --->
-
Character devices --->
-
<*> FL2440 PWM Beep Device (NEW)
4. 编译内核并下载到开发板上。这里要注意,现在我们不需要手动的在开发板上创建设备的节点了,因为我们现在使用了mdev进行管理了,在驱动程序中也添加了对类设备接口的支持。之前讲的一些驱动都没有,以后我们都使用这种方法。现在可以查看到/dev目录下自动创建好的fl2440_pwm设备节点,就直接可以使用它了。
四、测试驱动 1. 编写应用程序测试LED驱动,文件名:pwm_test.c - /*
-
*==============================================
-
*Name : pwm_test.c
-
*Author : y.q.yang
-
*Date : 21/2/2011
-
*Copyright : GPL
-
*Description : fl2440 pwm driver test
-
*==============================================
-
*/
-
-
#include <stdlib.h>
-
#include <fcntl.h>
-
#include <sys/ioctl.h>
-
-
int main(int argc, char **argv)
-
{
-
int tmp;
-
int fd;
-
int i;
-
-
//打开蜂鸣器设备
-
fd = open("/dev/fl2440_pwm", O_RDWR);
-
-
if(fd < 0)
-
{
-
printf("Open PWM Device Faild!\n");
-
exit(1);
-
}
-
-
//提示用户输入一个参数来对蜂鸣器进行调频,0表示停止工作
-
printf("please enter the times number(0 is stop):\n");
-
-
while(1)
-
{
-
//输入参数
-
scanf("%d", &tmp);
-
printf("times = %d\n", tmp);
-
-
//IO控制
-
ioctl(fd, tmp);
-
-
if(tmp == 0)
-
{
-
break;
-
}
-
}
-
-
//关闭设备
-
close(fd);
-
-
return 0;
-
}
2. 在开发主机上交叉编译测试应用程序,并复制到文件系统的/usr/sbin目录下,然后重新编译文件系统下载到开发板上。 - #arm-linux-gcc -o pwm_test pwm_test.c
3. 在开发板上运行测试程序。可以看到根据你输入参数的大小,蜂鸣器也会发生不同频率的叫声,输入0蜂鸣器停止鸣叫。
五、补充问题 仍然无法自动建立,/dev/fl2440_pwm节点,原因还未找到。
2011-02-21 |
|