Linux下的蜂鸣器驱动程序设计

 

嵌入式系统开发技术

 

1.绪论____________________________________________________ 3

1.1概要______________________________________________________ 3

1.2设计内容__________________________________________________ 4

2.开发环境的搭建__________________________________________ 4

2.1Redhat的安装______________________________________________ 4

2.2安装arm-linux-gcc交叉编译器_______________________________ 9

2.3安装及编译linux-2.6.29-mini2440-20090708内核________________ 9

3.字符设备驱动相关知识____________________________________ 9

3.1模块机制__________________________________________________ 9

3.2字符设备开发基本步骤_____________________________________ 10

3.3主设备号和次设备号_______________________________________ 11

3.4实现字符驱动程序_________________________________________ 12

4.蜂鸣器原理_____________________________________________ 14

4.1蜂鸣器的种类和工作原理___________________________________ 14

4.2开发板上蜂鸣器原理图分析_________________________________ 15

4.3GPB0参数________________________________________________ 15

5.总体设计_______________________________________________ 16

5.1设计思路_________________________________________________ 16

5.2设计步骤_________________________________________________ 16

6. 驱动及测试程序________________________________________ 17

6.1beep.c____________________________________________________ 17

6.2beep_tset.c________________________________________________ 21

7.运行结果及截图_________________________________________ 22

综合设计总结与思考______________________________________ 25

 

 

 

                     1.绪论

1.1概要

linux 驱动在本质上就是一种软件程序,上层软件可以在不用了解硬件特性的情况下,通过驱动提供的接口,和计算机硬件进行通信。

系统调用是内核和应用程序之间的接口,而驱动程序是内核和硬件之间的接口,也就是内核和硬件之间的桥梁。它为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。

linux 驱动程序是内核的一部分,管理着系统中的设备控制器和相应的设备。它主要完成这么几个功能:对设备初始化和释放;传送数据到硬件和从硬件读取数据;检测和处理设备出现的错误。

一般来说,一个驱动可以管理一种类型的设备。例如不同的 U 盘都属于 mass storage 设备,我们不需要为每一个 U 盘编写驱动,而只需要一个驱动就可以管理所有这些 mass storage 设备。

为方便我们加入各种驱动来支持不同的硬件,内核抽象出了很多层次结构,这些层次结构是 linux 设备驱动的上层。它们抽象出各种的驱动接口,驱动只需要填写相应的回调函数,就能很容易把新的驱动添加到内核。

一般来说, linux 驱动可以分为三类,就是块设备驱动,字符设备驱动和网络设备驱动。块设备的读写都有缓存来支持,并且块设备必须能够随机存取。块设备驱动主要用于磁盘驱动器。

而字符设备的 I/O 操作没有通过缓存。字符设备操作以字节为基础,但不是说一次只能执行一个字节操作。例如对于字符设备我们可以通过 mmap 一次进行大量数据交换。字符设备实现比较简单和灵活。

1.2设计内容

本次设计是简单的字符设备驱动设计,基于mini2440的蜂鸣器的驱动设计。

                    2.开发环境的搭建

 

2.1Redhat的安装

创建一个虚拟机:点击菜单栏File->New->Virtual machine。点击下一步。

选择Typical选项。

选择Linux下的Red Hat Linux

填写虚拟机的命名和存储地址。

 

选择磁盘大小

2.2安装arm-linux-gcc交叉编译器

     将  arm-linux-gcc-4.3.2.tgz复制到虚拟机的root目录下

解压文件:tar zxvf arm-linux-gcc-4.3.2.tgz

在bash_profile里添加路径:gedit ~/.bash_profiel

路径 /root/usr/local/arm/4.3.2/bin

source ~/.bash_profile 使更改生效

 

2.3安装及编译linux-2.6.29-mini2440-20090708内核

复制内核到root目录下

解压内核文件 tar zxvf linux-2.6.29-mini2440-20090708.tgz

使内核文件生效:cp config_mini2440_n35 .config

使用make命令完成编译

                  3.字符设备驱动相关知识

3.1模块机制

Linux提供了机制被称为模块(Module)的机制

 

提供了对许多模块支持, 包括但不限于, 设备驱动

 

每个模块由目标代码组成( 没有连接成一个完整可执行程序 )

insmod 将模块动态加载到正在运行内核

rmmod 程序移除模块

 

Linux内核模块的程序结构

 

  1. module_init()---模块加载函数(必须)

通过insmod或modprobe命令加载内核模块时,模块的加载函数  会自动被内核执行,完成模块的相关初始化工作

 

  1. module_exit()---模块卸载函数(必须)

当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块装载函数相反的功能

  1. MODULE_LICENSE()---模块许可证声明(必须)

模块许可证(LICENSE)声明描述内核模块的许可权限

如果不声明LICENSE,模块被加载时,将收到内核被污染(kernel tainted)的警告

 

 

  1. module_param()---模块参数(可选)

模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。

  1. EXPORT_SYMBOL()---模块导出符号(可选)

内核模块可以导出符号(symbol,对应于函数或变量)到内核

其他模块可以使用本模块中的变量或函数

 

  1. 其他一些声明MODULE_XXXXX()---模块声明(可选)

模块加载函数

static int   __init     initialization_function(void)

{

  /* 初始化代码 */

}

module_init(initialization_function);

模块卸载函数

  static void     __exit   cleanup_function(void)

  {

        /* 释放资源 */

  }

  module_exit(cleanup_function);

 

 

3.2字符设备开发基本步骤

 

  1. 确定主设备号和次设备号

 

  1. 实现字符驱动程序

实现file_operations结构体

实现初始化函数,注册字符设备

实现销毁函数,释放字符设备

 

  1. 创建设备文件节点

 

3.3主设备号和次设备号

 

  • 主设备号是内核识别一个设备的标识。

整数(占12bits),范围从0到4095,通常使用1到255

 

  • 次设备号由内核使用,用于正确确定设备文件所指的设备。

整数(占20bits),范围从0到1048575,一般使用0到255

 

  • 设备编号的内部表达

dev_t类型(32位):

用来保存设备编号(包括主设备号(12位)和次设备号(20位))

从dev_t获得主设备号和次设备号:

MAJOR(dev_t);

MINOR(dev_t);

将主设备号和次设备号转换成dev_t类型:

MKDEV(int major,int minor);

  • 分配主设备号

手工分配主设备号:找一个内核没有使用的主设备号来使用。

#include <linux/fs.h>

int register_chrdev_region( dev_t first,     unsigned int count,    char *name );

  • 动态分配主设备号:

#include <linux/fs.h>

int  alloc_chrdev_resion(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);

  • 释放设备号

void  unregister_chrdev_region(dev_t first, unsigned int count);

3.4实现字符驱动程序

  1. cdev 结构体

struct cdev

 {

   struct kobject kobj;              /* 内嵌的kobject 对象 */

   struct module *owner;         /*所属模块*/

   struct file_operations *ops;  /*文件操作结构体*/

   struct list_head list;

   dev_t dev;                           /*设备号*/

   unsigned int count;

 };

  1. file_operations 结构体

 

字符驱动和内核的接口:

在include/linux/fs.h定义

 

字符驱动只要实现一个file_operations结构体

 

并注册到内核中,内核就有了操作此设备的能力。

  1. file_operations的主要成员:

struct module *owner: 指向模块自身

open:打开设备

release:关闭设备

read:从设备上读数据

write:向设备上写数据

ioctl:I/O控制函数

llseek:定位读写指针

mmap:映射设备空间到进程的地址空间

  1. ioctl函数

为设备驱动程序执行“命令”提供了一个特有的入口点

用来设置或者读取设备的属性信息。

int    ioctl (struct inode *inode, struct file *filp,

             unsigned int cmd, unsigned long arg);

  1. cmd 参数的定义

不推荐用0x1,0x2,0x3之类的值

Linux对ioctl()的cmd参数有特殊的定义

 

构造命令编号的宏:

_IO(type,nr)用于构造无参数的命令编号;

_IOR(type,nr,datatype)用于构造从驱动程序中读取数据的命令编号;

_IOW(type,nr,datatype)用于写入数据的命令;

_IOWR(type,nr,datatype)用于双向传输。

type和number位字段通过参数传入,而size位字段通过对datatype参数取sizeof获得。

  1. Ioctl函数模板

int xxx_ioctl( struct inode *inode, struct f ile *filp, unsigned int cmd,

                      unsigned long arg)

 {

   ...

   switch (cmd)

   {

      case XXX_CMD1:

      ...

      break;

      case XXX_CMD2:

      ...

      break;

      default:  ///*不能支持的命令 */

      return - ENOTTY;

   }

   return 0;

 }

 

3.5字符设备驱动结构

 

                       4.蜂鸣器原理                   

4.1蜂鸣器的种类和工作原理

   蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。

   压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。有的压电式蜂鸣器外壳上还装有发光二极管。多谐振荡器由晶体管或集成电路构成。当接通电源后(1.5~15V直流工作电压),多谐振荡器起振,输出1.5~2.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声。

   电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场。振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。

   有源蜂鸣器和无源蜂鸣器的区别:这个“源”字是不是指电源,而是指震荡源,即有源蜂鸣器内有振荡源而无源蜂鸣器内部没有振荡源。有振荡源的通电就可以发声,没有振荡源的需要脉冲信号驱动才能发声。

4.2开发板上蜂鸣器原理图分析

 

由原理图可以得知,蜂鸣器是通过GPB0 IO口使用PWM信号驱动工作的,而GPB0口是一个复用的IO口,要使用它得先把他设置成TOUT0 PWM输出模式。

4.3GPB0参数

 

 

 

                        5.总体设计

5.1设计思路

 Linux设备驱动属于内核的一部分,Linux内核的一个模块可以以两种方式被编译和加载:

(1)直接编译进Linux内核,随同Linux启动时加载;

(2)编译成一个可加载和删除的模块,使用insmod加载(modprobe和insmod命令类似,但依赖于相关的配置文件),rmmod删除。这种方式控制了内核的大小,而模块一旦被插入内核,它就和内核其他部分一样。

  这次的蜂鸣器驱动就采用动态模块加载的方式

5.2设计步骤

  • 编写简单的字符设别驱动程序框架
  • 编写控制蜂鸣器控制开关函数
  • 编译模块,生成.ko

  

  • 编写用户层测试程序
  • 编译用户层测试程序,生成可执行程序beep_test
  • 将生成的.ko 模块和应用层测试程序 beep_test 下载到目标板
  • 用 insmod 装载模块
  • 创建设备节点

   mknod /dev/beep c 253 0

  • 运行用户层测试程序 beep_test

#./beep_test

如果你的 beep_test 的属性不是可执行的,可以用 chmod 777 beep_test  将其设置成可执行

程序。

  1. 驱动及测试程序

6.1beep.c

#include<linux/module.h>

#include<linux/types.h>

#include<linux/fs.h>

#include<linux/errno.h>

#include<linux/mm.h>

#include<linux/sched.h>

#include<linux/init.h>

#include<linux/cdev.h>

#include<linux/io.h>

#include<asm/system.h>

#include<asm/uaccess.h>

#include<asm/io.h>

#include <asm/irq.h>

#include <linux/slab.h>

#include <linux/ioport.h>

#include <mach/hardware.h>

#include <mach/gpio-fns.h>

#include <asm/irq.h>

#include <linux/init.h>

#include <linux/gpio.h>

#include <plat/devs.h>

#include <linux/types.h>

#include <linux/interrupt.h>

#include <linux/list.h>

#include <linux/timer.h>

#include <linux/init.h>

#include <linux/gpio.h>

#include <linux/sysdev.h>

#include <linux/platform_device.h>

 

 

#include <asm/mach/arch.h>

#include <asm/mach/map.h>

#include <asm/mach/irq.h>

 

#include <asm/mach-types.h>

#include <mach/hardware.h>

#include <asm/irq.h>

#include <mach/regs-gpio.h>

#include <plat/pm.h>

 

 

#define BEEP_MAJOR 201

#define BEEP_START_CMD 0x0

#define BEEP_STOP_CMD 0x1

 

static int beep_major=BEEP_MAJOR;

 

 

static struct cdev BeepDevs;

 

static int beep_open(struct inode *inode,struct file *filp)

{

      return 0;

}

 

static int beep_relesae(struct inode *inode,struct file *filp)

{

      return 0;

}

 

static void beep_stop(void)

{

     

s3c2410_gpio_cfgpin(S3C2410_GPB(0),S3C2410_GPIO_OUTPUT);

      s3c2410_gpio_setpin(S3C2410_GPB(0),0);

      printk("stop\n");

}

 

static void beep_start(void)

{

     

s3c2410_gpio_cfgpin(S3C2410_GPB(0),S3C2410_GPIO_OUTPUT);

      s3c2410_gpio_setpin(S3C2410_GPB(0),1);

      printk("start\n");

}

 

static int beep_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)

{

      switch(cmd)

      {

            case BEEP_START_CMD:

                  printk("beep_strat\n");

                  beep_start();

                  break;

            case BEEP_STOP_CMD:

                  printk("beep_stop\n");

                  beep_stop();

                  break;

            default:

                  printk("default\n");

                  break;

      }

 

      return 0;   

}

 

static struct file_operations beep_remap_ops={

      .owner = THIS_MODULE,

      .open =beep_open,

      .release = beep_relesae,

      .ioctl = beep_ioctl,

};

 

static void beep_setup_cdev(struct cdev *dev, int minor,struct file_operations *fops)

{

      int err,devno = MKDEV(beep_major,minor);

 

      cdev_init(dev,fops);

      dev->owner = THIS_MODULE;

      dev->ops = fops;

      err = cdev_add(dev,devno,1);

      if(err)

      {

            printk("error %d adding beep %d \n",err,minor);

      }

}

 

static int __init  beep_init(void)

{

      int result;

      dev_t dev=MKDEV(beep_major,0);

 

      if(beep_major)

      {

            result= register_chrdev_region(dev,1,"beep");

      }

      else

      {

            result=alloc_chrdev_region(&dev,0,1,"beep");

            beep_major=MAJOR(dev);

      }

 

      if(result<0)

      {

            printk("beep: unable to get major %d \n",beep_major);

            return result;

      }

 

      if(beep_major==0)

      {

            beep_major=result;

      }

     

 

      beep_setup_cdev(&BeepDevs,0,&beep_remap_ops);

      printk("beep devices installed,with major %d",beep_major);

      return 0;

}

 

static void __exit beep_exit()

{

      cdev_del(&BeepDevs);

      unregister_chrdev_region(MKDEV(beep_major,0),1);

      printk("beep device uninstalled\n");

}

 

 

MODULE_AUTHOR("xdq");

MODULE_LICENSE("GPL");

 

module_init(beep_init);

module_exit(beep_exit);

 

6.2beep_tset.c

#include<stdio.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdlib.h>

 

int main()

{

      int dev_fd;

      char c;

      dev_fd=open("/dev/beep",O_WRONLY|O_NONBLOCK);

      if(dev_fd == -1)

      {

            printf("con not flie\n");

            exit(0);

      }

 

      ioctl(dev_fd,0x0,0);

 

      getchar();

      getchar();

 

      ioctl(dev_fd,0x1,0);

     

      getchar();

      getchar();

 

      close(dev_fd);

      return 0;

}

 

                   7.运行结果及截图

 

 

传输beep.ko,ls命令查看文件。

 

传输beep_test可执行文件。使用./beep_test命令运行测试程序

beep_stat,蜂鸣器响了,按下键盘一键,beep_stop,蜂鸣器停止

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cingular_0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值