Chinaunix首页 | 论坛 | 博客
  • 博客访问: 53731
  • 博文数量: 65
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 14
  • 用 户 组: 普通用户
  • 注册时间: 2015-11-08 16:56
文章分类
文章存档

2015年(65)

我的朋友

分类: LINUX

2015-11-08 21:09:26

   这里主要是仿照《嵌入式Linux开发完全手册》上的例子写的,只是增加了别外两个按按键。在我的mini2440开发板上有6个按键。在上两篇文章中,主要分析了驱动中的整体的流程,现在来看一个具体的例子,是如何使用中断的。
1. 模块的初始化函数和卸载函数

/* 执行"insmod mini2440_buttons.ko"命令时就会调用这个函数*/
static int __init mini2440_buttons_init (void)
{
  int ret;
/*这里主要是注册设备驱动程序,参数为主设备号,如果BUTTON_MAJOR设为0,表示由内核自动分配主设备号,设备的名字,file_operations结构,操作主调和号为BUTTON_MAJOR的设备文件时,就会调用mini2440_buttons_fops中的相关成员函数*/
  ret = register_chrdev(BUTTON_MAJOR,DEVICE_NAME,&mini2440_buttons_fops);
  if(ret < 0)
  {
    printk(DEVICE_NAME "can't register major number\n");
    return ret ;
  }
  printk(DEVICE_NAME"initialized\n");
  return 0;
}
/* 执行 rmmod mini2440_buttons.ko0 命令时就会调用这个函数 */
static void __exit mini2440_buttons_exit(void)
{//卸载驱动程序

  unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME);
}
//指定驱动程序的初始化函数和卸载函数

module_init(mini2440_buttons_init);
module_exit(mini2440_buttons_exit);

   下面这个结构体是每一个字符驱动程序都是要用到的。这里定义了应用程序可以使用的设备操作函数,只有在这个结构体中的函数,在应用程序中才可以使用,在下面的驱动程序中要实现下面的函数。

/* 这个结构是字符设备驱动程序的核心,当应用程序操作设备文件时所调用的open,read,write等函数,最终会调用这个结构中的对应函数*/
static struct file_operations mini2440_buttons_fops =
  {
    .owner = THIS_MODULE, //这是 个宏,指向编译模块时自动创建的_this_module变量

    .open = mini2440_buttons_open,
    .release = mini2440_buttons_close,
    .read = mini2440_buttons_read,
  };

2. mini2440_buttons_open函数
   在应用程序执行“open("/dev/buttons",..)"系统调用时,mini2440_buttons_open函数将被调用。这用来注册6个按键的中断处理程序

static int mini2440_buttons_open(struct inode *inode,struct file *file)
{
  int i;
  int err;
  for (i=0;i<sizeof(button_irqs)/sizeof(button_irqs[0]);i++)
  { //注册中断处理函数 一共六个
    err = request_irq(button_irqs[i].irq,buttons_interrupt,button_irqs[i].flags,button_irqs[i].name,(void *)&press_cnt[i]);
    if (err)
      break;
  }
  if(err) //出错处理函数,如果出错释放已经注册的中断
  {
    i--;
    for(;i>=0;i--)
      free_irq(button_irqs[i].irq,(void *)&press_cnt[i]);
    return -EBUSY;
  }
  return 0;
}

requst_irq函数执行成功后,这6个按键所用的GPIO引脚的功能被设为外部中断,触发方式为下降沿触发,中断处理函数为buttons_interrupt.最后一个参数“(void *)&press_cnt[i]”将在buttons_interrupt函数中用到,它用来 存储按键被按下的次数。参数button_irqs的定义如下:

struct button_irq_desc
{
  int irq;//中断号
  unsigned long flags; //中断标志,用来定义中断的触发方式
  char *name; //中断名称
};

static struct button_irq_desc button_irqs[] =
  {  //下面是按键对应的外部的中断号,触发方式,名称
    {IRQ_EINT8,IRQF_TRIGGER_FALLING,"KEY0"},
    {IRQ_EINT11,IRQF_TRIGGER_FALLING,"KEY1"},
    {IRQ_EINT13,IRQF_TRIGGER_FALLING,"KEY2"},
    {IRQ_EINT14,IRQF_TRIGGER_FALLING,"KEY3"},
    {IRQ_EINT15,IRQF_TRIGGER_FALLING,"KEY4"},
    {IRQ_EINT19,IRQF_TRIGGER_FALLING,"KEY5"},
  };

3. mini2440_buttons_close函数
mini2440_buttons_close函数的作用是用来卸载6个按键的中断处理函数代码如下:

/* 应用程序对设备文件/dev/buttons执行close(...)时。就会调用mini2440_buttons_close函数*/
static int mini2440_buttons_close(struct inode *inode,struct file *file)
{
  int i;
  for(i=0;i<sizeof(button_irqs)/sizeof(button_irqs[0]);i++)
  { //释放已注册的函数

    free_irq(button_irqs[i].irq,(void *)&press_cnt[i]);
  }
  return 0;
}

4. mini2440_buttons_read函数
中断处理函数会在press_cnt数组中记录按键被按下的次数。mini_buttons_read函数,首先判断是否按键再次按下,如果没有则休眠;否则读取press_cnt数组的数据,

/*等待队列:
 当没有按键被按下时,如果有进程调用mini2440_buttons_read函数,它将休眠*/

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/*中断事件标志,中断服务程序将它置1,mini2440_buttons_read将它清0*/
static volatile int ev_press = 0;
/*应用程序对设备文件/dev/buttons执行read(...)时,就会调用mini2440_buttons_read函数*/
static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)
{
  unsigned long err;
 //如果ev_press等于0,休眠
  wait_event_interruptible(button_waitq,ev_press);
  ev_press = 0;// 执行到这里是ev_press肯定是1,将它清0
  //将按键状态复制给用户,并清0
  err = copy_to_user(buff,(const void *)press_cnt,min(sizeof(press_cnt),count));
  memset((void *)press_cnt,0,sizeof(press_cnt));

  return err ? -EFAULT:0;
}

    wait_event_interruptible首先会判断ev_press是否为0,如果为0才会令当前进程进入休眠,否则向下继续执行,它的第一个参数,button_waitq是一个等待的队列,在前面定义过,第二个参数ev_press用来表示中断是否已经发生,中断服务程序将它置1,如果ev_press为0,当前进程进入休眠,中断发生时中断处理函数buttons_interrupt会把它唤醒。将press_cnt数组的内容复制到用户空间,buff参数表示的缓冲区位于用户空间,使用copy_to_user向它赋值。
5.中断处理函数buttons_interrupt

static irqreturn_t buttons_interrupt(int irq,void *dev_id)
{
  volatile int *press_cnt = (volatile int *)dev_id;
  *press_cnt = *press_cnt + 1; //按键计数加1

  ev_press = 1; //表示中断发生
  wake_up_interruptible(&button_waitq); //唤醒休眠的进程
  return IRQ_RETVAL (IRQ_HANDLED);
}

   buttons_interrupt函数被调用时,第一个参数irq表示发生的中断号,第二个参数dev_id就是前面使用request_irq注册中断时传入的“&pres_cnt[i]”.
   将mini2440_buttons.c放到内核源码目录drivers/char下,在drivers/char目录下生成可加载模块mini2440_buttons.ko,把它放开开发板根文件系统的/lib/modules/2.6.22.6目录下,就可以使用"insmod mini2440_buttons"、“rmmod mini2440_buttons.ko”命令进行加载,卸载了。

6.测试程序

编写的测试程序buttons_test.c,编译后生成可执行文件,然后把它放到开发板根文件系统/usr/bin目录下。在开发板根文件系统中建立设备文件。

#mknod /dev/buttons c 232 0

然后使用“insmod mini2440_buttons”命令加载模块。执行完这条命令后可以看到在控制台中执行“cat /proc/devices”

[root@Frankzfz 2.6.32.2-FriendlyARM]$cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
232 buttons  //主设备号 刚注册的设备名
252 hidraw
253 ttySDIO
254 rtc
                                                                                
Block devices:
  1 ramdisk
256 rfd
259 blkext
 31 mtdblock
 44 ftl
 93 nftl
 96 inftl
179 mmc

运行测试程序button_test后,/dev/buttons设备就会被打开,可以使用“cat /proc/interrupts”命令看到注册的6个中断了。

[root@Frankzfz /mnt]$cat /proc/interrupts
           CPU0
 30: 157894 s3c S3C2410 Timer Tick
 42: 0 s3c ohci_hcd:usb1
 43: 8 s3c s3c2440-i2c
 51: 9751 s3c-ext eth0
 52: 3 s3c-ext KEY0
 55: 1 s3c-ext KEY1
 57: 2 s3c-ext KEY2
 58: 15 s3c-ext KEY3
 59: 75 s3c-ext KEY4
 63: 10 s3c-ext KEY5
 70: 848 s3c-uart0 s3c2440-uart
 71: 1476 s3c-uart0 s3c2440-uart
 83: 0 - s3c2410-wdt
Err: 0

第一列表示中断号,第二列表示这个中断发生的次数,第三列的文字表示这个中断的硬件访问结构“struct irq_chip *chip”的名字。它在初始化中断体系结构时指定,第四列文字表示中断的名称,它在request_irq中指定。
 测试程序buttons_test.c如下

#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char **argv)
{
    int i;
    int ret;
    int fd;
    int press_cnt[4]; 
    fd = open("/dev/buttons",0); // 打开设备

    if (fd < 0) {
        printf("Can't open /dev/buttons\n");
        return -1;
    }
    // 这是个无限循环,进程有可能在read函数中休眠,当有按键被按下时,它才返回
    while (1) {
        // 读出按键被按下的次数

        ret = read(fd, press_cnt, sizeof(press_cnt));
        if (ret < 0) {
            printf("read err!\n");
            continue;
        } 
        for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) {
            // 如果被按下的次数不为0,打印出来

            if (press_cnt[i])
                  }
    }  
    close(fd);
    return 0;
}

在运行button_test时,可以以后台运行在button_test &。在开发板上按下不同的按键可以在串口上看到以下的信息。

$K1 has been pressed 1
K2 has been pressed 1
K2 has been pressed 1
K3 has been pressed 1
K3 has been pressed 5
K3 has been pressed 2
K6 has been pressed 1
K4 has been pressed 1
K4 has been pressed 1
K4 has been pressed 1
K4 has been pressed 3
K4 has been pressed 7
K4 has been pressed 2
K4 has been pressed 5
K4 has been pressed 10
K4 has been pressed 5
K5 has been pressed 1
K5 has been pressed 1
K5 has been pressed 1
K5 has been pressed 1
[3] + Done(255) ./button_test

   这只是一处简单的测试程序,当有时按下一次时,也可能出现说是按下了10次,没有很精确。如果以前没有按键被按下则进行休眠状态。
阅读(224) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~