• 14.linux按键驱动程序(一)


                按键驱动程序

      本文学习主要包含按键硬件的实现、中断分层管理、按键定时器去抖、阻塞性驱动程序设计。这里面需要使用到混杂设备驱动中断处理程序的内容。

    一、创建按键混杂设备驱动模型 

     1 int key_open(struct inode *node,struct file *filp)
     2 {
     3     return 0;    
     4 }
     5 struct file_operations key_fops = 
     6 {
     7     .open = key_open,    
     8 };
     9 struct miscdevice key_miscdev = {
    10     .minor = 200,     //次设备号
    11     .name = "6410key",   //设备名
    12     .fops = &key_fops,    
    13 };

    二、按键硬件的实现

      首先是按键的初始化,按键的初始化可以选择在open函数,和模块的初始化函数当中完成硬件的初始化。下面我们是选择在模块的初始化函数进行按键的初始化。按键的初始化,主要涉及对GPIO的引脚的功能进行相应的设置。我用的是ok6410A开发板上面有六个按键,核心板原理图如下:

      

    2.1硬件初始化

      

     s3c6410芯片手册对GPNCON的IO功能定义如下因此对按键硬件的初始化如下:

     1 #define GPNCON 0x7f008830
     2 #define GPNDAT 0x7f008834
     3 
     4 void key_hw_init(void)   //硬件初始化
     5 {
     6     unsigned int *gpio_config;
     7     unsigned short data;    
     8     gpio_config = ioremap(GPNCON,4);  //动态映射虚拟地址
     9     data = readw(gpio_config);
    10     data &= ~0xfff;
    11     data |= 0xaaa;
    12     writew(data,gpio_config);
    13     gpio_dat = ioremap(GPNDAT,4);  //将数据寄存器地址转化为虚拟地址
    14 }

     2.2按键中断程序(暂时注册第一个按键)

    来源参照:中断处理程序 

     1 #include <linux/module.h>
     2 #include <linux/init.h>
     3 #include <linux/miscdevice.h>
     4 #include <linux/interrupt.h>
     5 #include <linux/io.h>
     6 #include <linux/fs.h>
     7 
     8 #define GPNCON 0x7f008830
     9 #define GPNDAT 0x7f008834
    10 
    11 MODULE_LICENSE("GPL");
    12  
    13 irqreturn_t key_init(int irp,void *dev_id)
    14 {
    15     //1.检测是否发生中断    
    16     //2.清除已经发生的按键中断
    17     
    18     //3.打印按键值
    19     printk(KERN_EMERG"key down!
    ");
    20     return 0;
    21 }
    22 
    23 void key_hw_init(void)   //硬件初始化
    24 {
    25     unsigned int *gpio_config;
    26     unsigned short data;    
    27     gpio_config = ioremap(GPNCON,4);
    28     data = readw(gpio_config);
    29     data &= ~0xfff;
    30     data |= 0xaaa;
    31     writew(data,gpio_config);
    32     gpio_dat = ioremap(GPNDAT,4);  //将数据寄存器地址转化为虚拟地址
    33 }
    34 
    35 int key_open(struct inode *node,struct file *filp)
    36 {    
    37     return 0;
    38 }
    39 struct file_operations key_fops = 
    40 {
    41     .open = key_open,
    42 };
    43 
    44 struct miscdevice key_miscdev = 
    45 {
    46     .minor = 200,
    47     .name = "key",
    48     .fops = &key_fops,
    49 };
    50 
    51 static int button_init(void)
    52 {
    53     misc_register(&key_miscdev);   //注册混杂设备    
    54     //按键硬件初始化
    55     key_hw_init();
    56     request_irq(IRQ_EINT(0),key_init,IRQF_TRIGGER_FALLING,"key",0);  //注册中断处理程序
    57     return 0;
    58 }
    59 
    60 static void button_exit(void)
    61 {
    62     misc_deregister(&key_miscdev);   //注销混杂设备
    63     
    64     //注销中断处理程序
    65     free_irq(IRQ_EINT(0),0);
    66 }
    67 
    68 module_init(button_init);
    69 module_exit(button_exit);

      这里需要注意上面代码特殊标记的内容:IRQ_EINT(0)中断号、IRQF_TRIGGER_FALLING标志的来源

    标志来源:

      按键中断的处理,对于按键而言,可以在按下的时候产生中断,也可以在弹起的时候产生中断。需要通过一个标志来指定:IRQF_TRIGGER_FALLING,这个是从高电平到低电平产生中断。下表是其他产生中断的方式(内核代码中搜索IRQF_TRIGGER_FALLING):

      

    中断号:

      就是request_irq函数的第一个参数。我们在内核代码中搜索irqs.h,找对应的板子的:

      

      从上面的代码看到,IRQ_EINT0_3的中断号是32.系统留出了S3C_IRQ_OFFSET=32个中断号,这是给软中断的。所以中断号就是等于硬件编号加上偏移量。可以查看内核代码的entry-macro.S

     三、中断分层管理

      

    3.1中断嵌套

      所谓的中断嵌套就是,当一种中断正在执行的时候,又产生了另外中断。可以是同类型的,也可以是不同类型的。

        慢速中断:是指在进行中断处理的时候,中断的总开关是不关闭的。允许其他类型中断产生。

      快速中断:当中断产生的时候,控制位的IF为被置1,别的中断被禁止发生。这样就会产生我们不想看到的情况:中断丢失。

    3.2中断分层

      

      上半部:当中断发生时,它进行相应地硬件读写,并“登记”该中断。通常由中断处理程序充当上半部。

      下半部:在系统空闲的时候对上半部“登记”的中断进行后续处理。

    3.3工作队列

      工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。 每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列

      下图为3内核的处理器队列处理图:

      

      

    3.3.1工作队列描述

      Linux内核使用struct  work_struct来描述一个工作队列:

    1 struct workqueue_struct{
    2     struct cpu_workqueue_struct *cpu_wq;
    3     struct list_head list;
    4     const char *name; /*workqueue name*/
    5     int singlethread;
    6     int freezeable; /* Freeze threads during suspend */
    7     int rt;
    8 };

    3.3.2工作队列项

      Linux内核使用struct work_struct来描述一个工作项:

    1 struct work_struct{
    2     atomic_long_t data;
    3     struct list_headentry;
    4     work_func_t func;
    5 };
    6 typedef void (*work_func_t)(struct work_struct *work);

    3.3.3工作队列使用

    step1. 创建工作队列
      create_workqueue
    step2. 创建工作
      INIT_WORK
    step3. 提交工作
      queue_work

      在大多数情况下, 驱动并不需要自己建立工作队列,只需定义工作, 然后将工作提交到内核已经定义好的工作队列keventd_wq中。

    1. 提交工作到默认队列
      schedule_work

    按照工作队列修改按键程序:

     1 #include <linux/module.h>
     2 #include <linux/init.h>
     3 #include <linux/miscdevice.h>
     4 #include <linux/interrupt.h>
     5 #include <linux/io.h>
     6 #include <linux/fs.h>
     7 #include <linux/slab.h>
     8 
     9 #define GPNCON 0x7f008830
    10 MODULE_LICENSE("GPL");
    11 struct work_struct *work1;
    12 struct timer_list key_timer;
    13 
    14 void work1_func(struct work_struct *work)
    15 {
    16     printk(KERN_EMERG"key down!
    ");
    17 }
    18  
    19 irqreturn_t key_int(int irp,void *dev_id)
    20 {    
    21     //3.提交下半部
    22     schedule_work(work1);
    23     return 0;
    24 }
    25 
    26 void key_hw_init(void)   //硬件初始化
    27 {
    28     unsigned int *gpio_config;
    29     unsigned short data;
    30     
    31     gpio_config = ioremap(GPNCON,4);
    32     data = readw(gpio_config);
    33     data &= ~0xfff;
    34     data |= 0xaaa;
    35     writew(data,gpio_config);
    36     gpio_dat = ioremap(GPNDAT,4);  //将数据寄存器地址转化为虚拟地址
    37 }
    38 
    39 int key_open(struct inode *node,struct file *filp)
    40 {    
    41     return 0;
    42 }
    43 struct file_operations key_fops = 
    44 {
    45     .open = key_open,
    46 };
    47 
    48 struct miscdevice key_miscdev = 
    49 {
    50     .minor = 200,
    51     .name = "key",
    52     .fops = &key_fops,
    53 };
    54 
    55 static int button_init(void)
    56 {
    57     misc_register(&key_miscdev);   //注册混杂设备
    58     
    59     //按键硬件初始化
    60     key_hw_init();
    61     request_irq(IRQ_EINT(0),key_int,IRQF_TRIGGER_FALLING,"key",0);  //注册中断处理程序
    62     //创建工作1
    63     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    64     INIT_WORK(work1,work1_func);
    65     return 0;
    66 }
    67 
    68 static void button_exit(void)
    69 {
    70     misc_deregister(&key_miscdev);   //注销混杂设备
    71     //注销中断处理程序
    72     free_irq(IRQ_EINT(0),0);
    73 }
    74 
    75 module_init(button_init);
    76 module_exit(button_exit);

    接下来的内容在下一个文章里包括:定时器去抖和阻塞性驱动程序设计

  • 相关阅读:
    【转】使用TortoiseSVN搭建本地的版本控制库
    操作系统的大端小端
    从《王者荣耀》谈游戏的帧同步
    二叉搜索树的第K大节点
    Mysql千万级大表优化
    海量数据存储方案
    递归函数思维
    time_wait的快速回收和重用
    Nginx配置反向代理服务器
    MySQL-怎样使update操作sleep一段时间
  • 原文地址:https://www.cnblogs.com/wmx-learn/p/5365104.html
Copyright © 2020-2023  润新知