1、蜂鸣器驱动设计项目文档项目设计报告 项目名称:基于ARM-Linux的BUZZER驱动设计 项目负责人: 姚 雷 项目时间 :2012.12.3-2012.12.7 第一章 绪论1.1驱动背景Linux设备驱动分类:1.1.1 字符设备驱动:字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现 open, close,read和 write 系统调用。1.1.2块设备驱动:在大部分的 Unix 系统, 块设备不能按字节处理数据,只能一次传送一个或多个长度是512字节( 或一个更大的 2 次幂的数 )的整块数据。而Linux则允许块设备传送任意数目的字节。因此, 块和
2、字符设备的区别仅仅是驱动的与内核的接口不同。1.1.3 网络接口驱动:任何网络事务都通过一个接口来进行, 一个接口通常是一个硬件设备(eth0), 但是它也可以是一个纯粹的软件设备, 比如回环接口(lo)。一个网络接口负责发送和接收数据报文。1.2 嵌入式系统以应用为中心、以计算机技术为基础,软硬件可裁剪,应用系统对功能、可靠性、成本、体积、功耗和应用环境有特殊要求的与用计算机系统。是将应用程序、操作系统和计算机硬件集成在一起的系统(技术角度);嵌入式系统是设计完成复杂功能的硬件和软件,幵使其紧密耦合在一起的计算机系统(系统角度)。第二章 驱动应用技术2.1 驱动程序使用Linux用户程序通过
3、设备文件(又名:设备节点)来使用驱动程序操作字符设备和块设备。2.2 设备创建字符设备通过字符设备文件来存取。字符设备文件由使用 ls -l 的输出的第一列的“c”标识。如果使用 ls -l 命令, 会看到在设备文件项中有 2 个数(由一个逗号分隔) 这些数字就是设备文件的主次设备编号。主设备号用来标识与设备文件相连的驱动程序。次编号被驱动程序用来辨别操作的是哪个设备。* 主设备号用来反映设备类型 *次设备号用来区分同类型的设备*Linux内核如何给设备分配主设备号?可以采用静态申请,动态分配两种方法。2.2.1 静态申请根据Documentation/devices.txt,确定一个没有使用
4、的主设备号,使用 register_chrdev_region 函数注册设备号,int register_chrdev_region(dev_t from, unsigned count, const char *name)。参数:from:希望申请使用的设备号count:希望申请使用设备号数目name:设备名(体现在/proc/devices) 2.2.2 动态申请使用 alloc_chrdev_region 分配设备号,int alloc_chrdev_region(dev_t *dev, unsignedbaseminor, unsigned count,const char *name
5、)。参数:dev:分配到的设备号baseminor:起始次设备号count:需要分配的设备号数目name:设备名(体现在/proc/devices)注销设备号:void unregister_chrdev_region(dev_t from, unsigned count)。2.3 设备Ioctl控制在用户空间,使用ioctl 系统调用来控制设备,原型如下:int ioctl(int fd,unsigned long cmd,.) 原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第 2 个参数 )是否涉及到与设备的数据交互。ioctl 驱动方法有和用户空间版本不同的原型:int (*
6、ioctl)(struct inode *inode,struct file*filp,unsigned int cmd,unsigned long arg)cmd参数从用户空间传下来,可选的参数 arg 以一个unsigned long 的形式传递,不管它是一个整数或一个指针。如果cmd命令不涉及数据传输,则第 3 个参数arg的值无任何意义。在编写ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。ioctl 命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:类型(幻数),序号,传送方向,参数的大小。内
7、核提供了下列宏来帮助定义命令:_IO(type,nr) 没有参数的命令_IOR(type,nr,datatype) 从驱动中读数据_IOW(type,nr,datatype) 写数据到驱动_IOWR(type,nr,datatype)双向传送,type 和 number 成员作为参数被传递。 定义好了命令,下一步就是要实现Ioctl函数了,Ioctl函数的实现包括如下3个技术环节:(1).返回值 (2).参数使用 (3).命令操作Ioctl函数的实现通常是根据命令执行的一个switch语句。但是,当命令号不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)。第三章 项目任
8、务及目标实现1.目标编写驱动程序对ARM开发板的BUZZER进行控制,通过测试程序来测试该驱动程序能否正常工作。2.环境Linux,arm-linux-gcc,mini2440开发板 加载蜂鸣器驱动,insmod pwn.ko,初始化硬件设备。 安装嵌入式交叉编译器(arm-linux-gcc),使得在ARM开发板上运行测试驱动程序,调试程序。3.项目需求在开发板上运行测试程序。可以看到根据你输入参数的大小,蜂鸣器也会发生不同频率的叫声,根据延时的时间实现蜂鸣器响与停止的间隔时间。对Linux内核进行裁剪,模块化加载驱动。第四章 蜂鸣器工作原理蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。
9、压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。有的压电式蜂鸣器外壳上还装有发光二极管。多谐振荡器由晶体管或集成电路构成。当接通电源后(1.515V直流工作电压),多谐振荡器起振,输出1.52.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声。电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场。振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。根据S3C2440的手册介绍,S3C2440A内部有5个16位的定时器,定时器0、1、2、3都带有脉冲宽度调制功能(PWM),定时器4是一个没有
10、输出引脚的内部定时器,定时器0有一个用于大电流设备的死区生成器。总结一下2440内部定时器模块的特性吧:1)共5个16位的定时器,定时器0、1、2、3都带有脉冲宽度调制功能(PWM);2)每个定时器都有一个比较缓存寄存器(TCMPB)和一个计数缓存寄存器(TCNTB);3)定时器0、1共享一个8位的预分频器(预定标器),定时器2、3、4共享另一个8位的预分频器(预定标器),其值范围是0255;4)定时器0、1共享一个时钟分频器,定时器2、3、4共享另一个时钟分频器,这两个时钟分频器都能产生5种不同的分频信号值(即:1/2、1/4、1/8、1/16和TCLK);5)两个8位的预分频器是可编程的且
11、根据装载的值来对PCLK进行分频,预分频器和钟分频器的值分别存储在定时器配置寄存器TCFG0和TCFG1中;6)有一个TCON控制寄存器控制着所有定时器的属性和状态,TCON的第07位控制着定时器0、第811位控制着定时器1、第1215位控制着定时器2、第1619位控制着定时器3、第2022位控制着定时器4。还是根据S3C2440手册的描述和上图的结构,要开始一个PWM定时器功能的步骤如下(假设使用的是第一个定时器):1)分别设置定时器0的预分频器值和时钟分频值,以供定时器0的比较缓存寄存器和计数缓存寄存器用;2)设置比较缓存寄存器TCMPB0和计数缓存寄存器TCNTB0的初始值(即定时器0的
12、输出时钟频率);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功能其实也就是对定时器的相关寄存器进行操作。手册上也有一个公式:定
13、时器输出频率 = PCLK / 预分频器值 + 1 / 时钟分频值。第五章 总体设计1.处理流程(1).编写驱动程序代码和测试程序代码,(2).裁剪并编译内核,生成内核映像,(3).将内核映像下载到开发板上,并将文件系统也下载到开发板上。(4).将驱动文件和可执行的测试程序下载到开发板上(5).加载驱动,然后运行测试程序进行调试。注意:蜂鸣器是通过GPB0 IO口使用PWM信号驱动工作的,而GPB0口是一个复用的IO口,要使用它得先把他设置成TOUT0 PWM输出模式。2 各模块设计2.1 打开设备模块 static int BUZZER_open(struct inode *inode, s
14、truct file *file) if (!down_trylock(&lock) return 0; else return -EBUSY;此函数实现了怎么去打开设备,在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项为NULL,设备的打开操作永远成功。Open方法是驱动程序用来为以后的操作完成初始化准备工作的。在大部分驱动程序中,open完成如下工作:初始化设备,标明次设备号。2.2 关闭设备模块static int BUZZER_close(struct inode *inode, struct file *file) BUZZER_Stop(); up(&lock
15、); return 0;当设备文件被关闭时调用这个操作。与open相仿,release也可以没有,此处关闭函数为BUZZER_close。Release方法的作用正好与open相反。这个设备方法有时也称为close,它应该:关闭设备。注意:本驱动程序要进行读和写。2.3 开启蜂鸣器并设置蜂鸣器频率/* freq: pclk/50/16/65536 pclk/50/16 * if pclk = 50MHz, freq is 1Hz to 62500Hz * human ear : 20Hz 20000Hz */static void BUZZER_Start( unsigned long fre
16、q ) unsigned long tcon; unsigned long tcnt; unsigned long tcfg1; unsigned long tcfg0; struct clk *clk_p; unsigned long pclk; /set GPB0 as tout0, pwm output s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPB0_TOUT0);/设置gpio的工作模式,是输入,输出还是其他的, 就是设置GPB0为输出模式 tcon = _raw_readl(S3C2410_TCON); tcfg1 = _raw_re
17、adl(S3C2410_TCFG1); /读取定时器配置寄存器1的值 tcfg0 = _raw_readl(S3C2410_TCFG0); /读取定时器配置寄存器0的值 /prescaler = 50 tcfg0 &= S3C2410_TCFG_PRESCALER0_MASK; tcfg0 |= (50 - 1); /fg0的值为49 /mux = 1/16 tcfg1 &= S3C2410_TCFG1_MUX0_MASK; tcfg1 |= S3C2410_TCFG1_MUX0_DIV16; /设置tcfg1的值为0x0011即:1/16 _raw_writel(tcfg1, S3C2410
18、_TCFG1); /将值tcfg1写入定时器配置寄存器1中 _raw_writel(tcfg0, S3C2410_TCFG0); /将值tcfg0写入定时器配置寄存器0中 clk_p = clk_get(NULL, pclk); pclk = clk_get_rate(clk_p); /从系统平台时钟队列中获取pclk的时钟频率,在include/linux/clk.h中定义 tcnt = (pclk/50/16)/freq; /计算定时器0的输出时钟频率(pclk/prescaler0 + 1/divider value) _raw_writel(tcnt, S3C2410_TCNTB(0)
19、; /设置定时器0计数缓存寄存器的值 _raw_writel(tcnt/2, S3C2410_TCMPB(0); /设置定时器0比较缓存寄存器的值 tcon &= 0x1f; tcon |= 0xb; /disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0 _raw_writel(tcon, S3C2410_TCON); /设置定时器控制寄存器的0-4位,即对定时器0进行控制 tcon &= 2; /clear manual update bit _raw_writel(tcon, S3C2410
20、_TCON); /清除定时器0的手动更新位对GPB0复用口进行复用功能设置,设置为TOUT0 PWM输出,置GPIO口为输出功能,往相应的控制寄存器写值,并向相应的数据寄存器写值实现输出低电平还是高电平,控制蜂鸣器的开启与关闭。蜂鸣器原理图:2.4 关闭蜂鸣器static void BUZZER_Stop(void) s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT); s3c2410_gpio_setpin(S3C2410_GPB(0), 0);恢复GPB0口为IO口输出功能,由原理图可知直接给低电平可让蜂鸣器停止工作。2.5 i
21、octl控制模块static int BUZZER_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) /printk(ioctl buzzer: %x %lxn, cmd, arg); switch (cmd) case BUZZER_IOCTL_START: if (arg = 0) return -EINVAL; BUZZER_Set_Freq(arg); break; case BUZZER_IOCTL_STOP: BUZZER_Stop(); break; return
22、0;应用程序向设备发送命令,设备接受到命令并进行解析,并做相应的设置并启动设备工作或停止工作。如果输入的参数大于0,就让蜂鸣器开始工作,不同的参数,蜂鸣器的频率也不一样。2.6 重要数据结构模块在Linux字符设备驱动程序设计中,有3种非常重要的数据结构:Struct fileStruct inodeStruct file_operationsStruct File代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的 struct file。它由内核在打开文件时创建, 在文件关闭后释放。重要成员:loff_t f_pos /*文件读写位置*/struct file_operatio
23、ns *f_op Struct Inode用来记录文件的物理上的信息。因此, 它和代表打开文件的file结构是不同的。一个文件可以对应多个file结构, 但只有一个inode 结构。 重要成员:dev_t i_rdev:设备号 Struct file_operations一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动中的函数, 这些函数实现一个特别的操作, 对于不支持的操作保留为NULL。static struct file_operations dev_fops = .owner = THIS_MODULE, .open = BUZZER_open, .release =
24、 BUZZER_close, .ioctl = BUZZER_ioctl,;2.7 设备注册模块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); printk (DEVICE_NAMEtinitializedn); return ret;在lin
25、ux 2.6内核中,字符设备使用 struct cdev 来描述。 字符设备的注册可分为如下3个步骤:分配cdev初始化cdev添加cdevStruct cdev的分配可使用cdev_alloc函数来完成。Struct cdev的初始化使用cdev_init函数来完成。struct cdev的注册使用cdev_add函数来完成。2.8 设备注销模块static void _exit dev_exit(void) misc_deregister(&misc);此函数实现该混杂设备的注销。3.驱动测试驱动测试程序如下:/*buzzer_test.c*/#include #include #incl
26、ude #include void delay(int times) int i; for(;times0;times-) for(i=0;i400;i+); int main(void) int fd; fd = open(/dev/pwm, 0); if (fd 0) perror(open device pwm); exit(1); while (1) ioctl(fd,1,2000); sleep(1); ioctl(fd,0,2000); sleep(1); close(fd); return 0;此测试程序先执行打开设备文件,往设备中写入命令和参数来实现对蜂鸣器的控制。蜂鸣器的开启
27、与关闭间隔和时间以及蜂鸣器的频率取决与往设备文件里写的命令与参数。第六章 项目总结与体会经过了一个星期的学习驱动,本次项目是编写蜂鸣器驱动,毕竟自己学过驱动,感觉蛮多知识遗忘掉了,在编写蜂鸣器驱动和测试程序的过程中,发现了自己在驱动方面的不足之处,自己还有很多要学习的地方,在编写驱动时,设备的注册,模块的加载,蜂鸣器的频率等都是必须要考虑的问题,感谢老师们给我这次实训经历,能让自己进一步学习驱动的知识,对自己以后的择业与就业有了很大的帮助。 第七章 参考文献Linux设备驱动程序(第三版) (美)科波特,2006.1 中国电力出版社Linux驱动开发入门与实战 郑强编 2011.11 清华大学出版社Linux驱动开发详解 宋宝华编 2010.11 人民邮电出版社