注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

时间记录器

记录我的Linux、Android学习之路

 
 
 

日志

 
 

3+1键盘驱动学习  

2010-07-14 20:09:18|  分类: Mini4020 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

小桂(fgsink@gmail.com)   
实验环境:
 硬件:Mini4020 v1.1
 软件:U-Boot 1.3.3
            Linux version 2.6.16 (root@localhost.localdomain) (gcc version 3.4.1)
            SEP4020 ARM Linux-2.6.16 SDK 3.1
            nfs: MINI4020资料光盘\Mini4020 SDK开发软件包\Mini4020\nfs
             编译内核 Linux-v3.4
            编译器:arm-linux-gcc version 3.4.1
            键盘驱动文件:sep4020_key.c (见附件)

键盘的实现
键盘原理图:
 

3+1键盘驱动学习 - Goink - 时间记录器

 
键盘使用引脚资源:
 

3+1键盘驱动学习 - Goink - 时间记录器

 
键盘原理分析:
       初始化键盘时,将ROW1,ROW2,ROW3三条线输出高电平,如果有键被按下,三极管基极变为高电平,三极管导通,集电极被拉低,产生一个下降沿,触发中断,进入中断程序。
        扫描时,输出ROW[1:3]=100,若INT0还是低电平,则是第一行有键被按下,否则输出ROW[1:3]=010,查第二行是否有键被按下,INT0为低电平,第二行有键被按下,否则输出ROW[1:3]=001,记录行号。

        假设是第一行有键被按下(row=0),即输出ROW[1:3]=100,再将管脚方向改为ROW1输出,ROW2输入,ROW3输入,保持ROW1输出高电平,此时读ROW2和ROW3,若ROW[3:2]=00,则K1被按下(col=0);若ROW[3:2]=01,则K2被按下(col=1);若ROW[3:2]=10,则K3被按下(col=2)。计算公式keynum=row*3+col。假设K3被按下则row=0,col=2键值应该为keynum=0*3+2=2。

驱动分析:
一、文件中包含的函数列表

//硬件操作函数:
static void unmaskkey(void)   //开启键盘中断
static void maskkey(void)     //关闭键盘中断
static void sep4020_key_setup(void)//初始化中断
static void write_row(int index,int HighLow)
static int ReadCol(void)          
void row_input(int x)
int read_col(int x)
static int keyevent(void)
static int sep4020_request_irqs(void)//向内核申请中断,并将中断服务子程序注册进去
static void sep4020_free_irqs(void)//告诉内核释放硬件中断
static void key_timer_handler(unsigned long arg)
static irqreturn_t sep4020_key_irqhandler(int irq, void *dev_id, struct pt_regs *reg)

//驱动与操作系统接口函数
static int sep4020_key_open(struct inode *inode, struct file *filp)
static ssize_t sep4020_key_read(struct file *filp, char __user *buff, size_t size, loff_t *ppos)
static ssize_t sep4020_key_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
static int sep4020_key_release(struct inode *inode, struct file *filp)
static int __init sep4020_key_init(void)
static void __exit sep4020_key_exit(void)

module_init(sep4020_key_init);
module_exit(sep4020_key_exit);
MODULE_AUTHOR("Lee xiang");
MODULE_LICENSE("GPL");
二、函数层次关系
 

3+1键盘驱动学习 - Goink - 时间记录器

 
三、各函数分析


1、file_operations结构体
对于字符设备来说,file_operations{}是与应用程序唯一的函数接口

static const struct file_operations sep4020_key_fops = 
{
 .owner  = THIS_MODULE,
 .read   = sep4020_key_read,
 .write  = sep4020_key_write,
 .open  = sep4020_key_open,
 .release = sep4020_key_release,
};
在结构 file_operations里,指出了设备驱动程序所提供的入口点位置,分别是:
(1)  lseek:移动文件指针的位置,显然只能用于可以随机存取的设备。
(2)  read:进行读操作,参数buf 为存放读取结果的缓冲区,count为所要读取的数据长度。返回值为负表示读取操作发生错误,否则返回实际读取的字节数。
(3)  write:进行写操作,与read类似。
(4)  readdir:取得下一个目录入口点,只有与文件系统相关的设备驱动程序才使用。
(5)  select:进行选择操作,如果驱动程序没有提供select 入口,select操作将会认为设备已经准备好进行任何的I/O操作。
(6)  ioctl:进行读、写以外的其它操作,参数cmd为自定义的的命令。
(7)  mmap:用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。
(8)  open:打开设备准备进行 I/O操作。返回 0 表示打开成功,返回负数表示失败。如果驱动程序没有提供open入口,则只要/dev/driver 文件存在就认为打开成功。
(9)  release:即 close操作。设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。
由于键盘驱动实现功能较简单,这里只实现了4各接口read,write,open,release。并且做了实质性工作的只有read和 open。

2、初始化工作
 初始化由sep4020_key_init()完成,最终由module_init(sep4020_key_init);加入系统。
 在sep4020_key_init()中完成的工作有:
 a、register_chrdev_region(devno, 1, "sep4020_key"); 向系统静态申请设备号
 b、kmalloc(sizeof(struct keydev), GFP_KERNEL);向系统申请设备描述符内存
 c、memset(key_dev,0,sizeof(struct keydev));  初始化设备描述符内存
 d、sep4020_request_irqs() 向系统申请注册中断资源
 e、setup_timer(&key_timer,key_timer_handler,0); 初始化定时器
 f、cdev_init(&key_dev->cdev, &sep4020_key_fops);初始化设备描述符,完成函数挂接
 g、cdev_add(&key_dev->cdev, devno, 1); 向系统注册该字符设备
 其中每一次操作失败都必须把已经成功申请的那部分资源释放。比如已经执行到第g步了,但注册失败,则应该按f~a的顺序释放相关资源。


3、当有键被按下时发生了什么
 当有键被按下时,触发了外部硬件中断,中断服务程序sep4020_key_irqhandler()响应,

static irqreturn_t sep4020_key_irqhandler(int irq, void *dev_id, struct pt_regs *reg)
{
  //关闭键盘中断
 maskkey();  

 *(volatile unsigned long*)GPIO_PORTA_INTRCLR_V |= 0x0001;
 *(volatile unsigned long*)GPIO_PORTA_INTRCLR_V = 0x0000;    //清除中断  

 key_dev->keystatus = KEY_UNSURE;
 key_timer.expires = jiffies + KEY_TIMER_DELAY_JUDGE;
 add_timer(&key_timer);     //启动定时器
 
 //we will turn on the irq in the timer_handler
 return IRQ_HANDLED;
}
关闭并清除中断后启动定时器,接下来的扫描工作都由定时器完成。add_timer(&key_timer)这一句将把该段程序挂起来,等待时间到期。其中key_timer结构体的内容是
truct timer_list key_timer;
struct timer_list {
 struct list_head entry;
 unsigned long expires;
 void (*function)(unsigned long);
 unsigned long data;
 struct timer_base_s *base;
};
结构体中的function字段在sep4020_key_init()中由
setup_timer(&key_timer,key_timer_handler,0)来与定时器句柄挂钩。
 当add_timer(&key_timer)的时间到期后,将调用key_timer_handler句柄函数,同时内核将key_timer从链表中摘除。此时,键盘处理转入key_timer_handler执行。
static void key_timer_handler(unsigned long arg)
{
 int irq_value = 0;
 static int key_value = -1;
 irq_value = (*(volatile unsigned long*)GPIO_PORTA_DATA_V & 0x01); //读取中断口数值
 if (irq_value != 0x01//如果有低电平,表示键盘仍然有键被按着
 {
  if (key_dev->keystatus == KEY_UNSURE)
  {
   key_dev->keystatus = KEY_DOWN;
   key_value = keyevent();      //读取键盘的位置
   if (key_value >= 0)
   {  
     key_dev->buf[key_dev->write] = key_map[key_value];  
         if(++(key_dev->write) == MAX_KEY_BUF)   //按键缓冲区循环存取
         {      
     key_dev->write = 0;
             }
               } 
   //wake_up_interruptible(&(key_dev->wq));
      up(&key_press); 
   //检测是否持续按键
   key_timer.expires = jiffies + KEY_TIMER_DELAY_LONGTOUCH;
   add_timer(&key_timer); 
  }
  else    //一定是键按下 
  {
   key_timer.expires = jiffies + KEY_TIMER_DELAY_LONGTOUCH;    //延迟
   add_timer(&key_timer);
  }
 }
 else      //键已抬起
 {
  if (key_dev->keystatus == KEY_DOWN)
  {
   key_dev->keystatus = KEY_UP;
   if (key_value >= 0)
   {  
     key_dev->buf[key_dev->write] = key_map[key_value] | 0x70
     if(++(key_dev->write) == MAX_KEY_BUF)   //按键缓冲区循环存取
        {
       key_dev->write = 0;
            } 
   }
     up(&key_press);
         }
  //wake_up_interruptible(&(key_dev->wq));
  //打开键盘中断
  // printk("tree\n");
  *(volatile unsigned long*)GPIO_PORTE_DATA_V |= 0x038;       //输出拉高
  unmaskkey();
 }
}
函数维护了一个环形缓冲区,用以保存读到的键值。其中比较关键的一点是函数会对key_dev->write 进行修改,从而影响 应用程序 的读取。
 通过key_timer_handler句柄函数,当前被按下的键值就被保存在key_dev->buf[ ]中了。
这里key_dev结构体的描述是:
struct keydev
{    
 unsigned int keystatus;          //按键状态    
 unsigned char buf[MAX_KEY_BUF];   //按键缓冲区  
 unsigned int write,read;         //按键缓冲区头和尾
 wait_queue_head_t wq;            //等待队列
 struct cdev cdev;
} ;
其中write,read是缓冲区的操作指针,很重要。
 key_dev->buf[ ] 是一个过渡层的数据区。
4、当应用程序调用了read(fd,buf,2)时发生了什么
       在fd = open("/dev/key",O_RDONLY);  // O_NONBLOCK  打开设备文件后,执行文件读取函数read(fd,buf,2),内核会在适当的时候调用sep4020_key_read()函数,完成键值的读取。由于open没有使用非阻塞方式读取,因此应用程序会停在read()处等待有内容可读,当返回 return -EAGAIN; 时,说明没有内容可读。在sep4020_key_read()中式通过判断key_dev->write和key_dev->read是否相等来判断有没有内容可读的。相等,没有;不等,有。

 

3+1键盘驱动学习 - Goink - 时间记录器
                             read > write                                                                           read < write
分为以上两种情况。每次读取键值缓冲区的时候都会使read和 write 相等。在本驱动函数中,改变write的有两处地方,在key_timer_handler()中,当按键被按下时修改一次,按键抬起又修改一次,因此在应用程序中会看到打印两次信息。如果按键一直按着,则会持续打印。
  评论这张
 
阅读(466)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017