[OK210开发板体验]功能篇(2)Linux字符驱动之Key按键驱动

前面进行了OK210试用体验的入门篇介绍,算是初步入门,分别包含:

   【OK210开发板体验】的第一篇:开箱验板
   【OK210开发板体验】的第二篇:板载资源
   【OK210开发板体验】的第三篇:开发环境(软件安装,开发环境,烧写系统)
   【OK210开发板体验】的第四篇:编程入门(NFS登录,驱动入门)
前一篇介绍了功能篇的字符驱动之LED灯,今天是功能篇的第二篇:字符驱动之Key按键的控制,本节主要分3部分:硬件分析,软件基础,驱动编程。
一、硬件分析
【OK210开发板体验】的第二篇:板载资源中,简单分析了Key按键的功能和作用。其实对Key的操作,和对LED的操作是一相对的,都是对GPIO的最基本操作,也是入门操作。Key是对GPIO的输入进行操作,LED是对GPIO的输出进行操作。
首先从OK210的底板原理图中可知,OK210的5个按键,通过核心板接到了S5PV210的XEINT3-7引脚上,如下图,

 


接着从用户手册的第103页中得知,这些引脚没有复用功能,默认为GPI,即通用端口输入,如下图,其实在用户手册的92而,有这么一句话:,想必大家都知道其什么意思了。
GPH0,1,2,3: 32 in/out port - Key pad, External Wake-up (up-to 32-bit). (GPH* groups
are in Alive region)

 

就是说,要对5个Key的操作,就是对XEINT3-7这5个引脚的控制。
二、软件基础
1 字符设备驱动
字符驱动模块,可以简单的总结为如下图所示:包括对设备文件的创建,字符驱动的注册以及文件操作的编写等。

 

 

 

 

 

 

 


其实个人理解,对字符驱动的编写,就是对struct file_operations 结构体的填充,该结构体定义在linux/fs.h头文件中,在2.6.35.7内核中,定义为如下形式:

1.     struct file_operations {

2.             struct module *owner;

3.             loff_t (*llseek) (struct file *, loff_t, int);

4.             ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

5.             ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

6.             ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

7.             ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

8.             int (*readdir) (struct file *, void *, filldir_t);

9.             unsigned int (*poll) (struct file *, struct poll_table_struct *);

10.          int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

11.          long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

12.          long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

13.          int (*mmap) (struct file *, struct vm_area_struct *);

14.          int (*open) (struct inode *, struct file *);

15.          int (*flush) (struct file *, fl_owner_t id);

16.          int (*release) (struct inode *, struct file *);

17.          int (*fsync) (struct file *, int datasync);

18.          int (*aio_fsync) (struct kiocb *, int datasync);

19.          int (*fasync) (int, struct file *, int);

20.          int (*lock) (struct file *, int, struct file_lock *);

21.          ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

22.          unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

23.          int (*check_flags)(int);

24.          int (*flock) (struct file *, int, struct file_lock *);

25.          ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

26.          ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

27.          int (*setlease)(struct file *, long, struct file_lock **);

28.  };

复制代码

本节将会用到openreadpollfasyncrelease5个函数,下面将会一一介绍。
2驱动中断
驱动程序中进行中断处理涉及到的最基本的内核API,主要用于申请和释放中断,在linux/sched.h里声明,其原型为:
int request_irq(unsigned int irq, 
void (*handler)(int irq, void *dev_id, struct pt_regs *regs 
), 
unsigned long irqflags, 
const char * devname, 
void *dev_id); 
irq是要申请的硬件中断号;handler是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽。还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。一般设置为这个设备的 device结构本身或者NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用irq2dev_map找到中断对应的设备。
void free_irq(unsigned int irq,void *dev_id); 

三、驱动编程
(一)、本驱动程序分别实现了对按键的简单读(read)操作,支持轮询机制(poll)和支持异步机制(fasync)。
1简单读(read)操作
简单的读操作的工作原理是:当应用程序读取键值时,会调用按键驱动程序的read函数,而实现的read函数检测完读取长度后没有直接读取键值而是等待按键消息,如果没有按键,程序会进入休眠状态,这样可以节省大量的CPU,而当按下按键时硬件会产生中断,程序自动进入中断处理函数,在中断处理函数中,驱动程序读取键值存入全局变量并激活read函数中等待的消息,应用程序被迅速唤醒并通过read函数读取键值,如此,完成了获取键值的工作。测试程序见TestButtons1()。
2支持轮询机制(poll)
上面的实现的按键驱动程序有个弊端,如果不按键,应用程序将会永远阻塞在这里,幸运的是,linux内核提供了poll机制,可以设置超时等待时间,如果在这个时间内读取到键值则正常返回,反之则超时退出。测试程序见TestButtons2()。
3支持异步机制(fasync)
很多情况下,程序在等待按键期间需要处理其它任务而不是在这里空等,这时,就需要采用异步模式了。所谓异步模式,实际上是采用消息机制,即当驱动程序检测到按键后发送消息给应用程序,应用程序接收到消息后再去读取键值。与前面的两种模式相比,最大的不同在于异步方式是驱动告诉应用程序来读而不是应用程序主动去读。测试程序见TestButtons3()。
下面是按键采用异步机制的测试结果:

 

(二)程序代码
1驱动程序:

 

1.     

2.     #include <linux/types.h>

3.     #include <linux/module.h>

4.     #include <linux/cdev.h>

5.     #include <linux/fs.h>

6.     #include <linux/device.h>

7.     #include <linux/gpio.h>

8.     #include <linux/irq.h>

9.     #include <linux/interrupt.h>

10.  #include <linux/sched.h>

11.  #include <linux/wait.h>

12.  #include <linux/uaccess.h>

13.  #include <linux/poll.h>

14.   

15.   

16.  static dev_t devno;

17.  static struct cdev cdev;

18.  static struct class* my_buttons_class;

19.  static struct device* my_buttons_device;

20.   

21.  static wait_queue_head_t my_button_waitq;

22.  static struct fasync_struct *my_button_fasync;

23.  static volatile int pressed = 0;

24.  static unsigned char key_val;

25.   

26.  struct key_desc{

27.          unsigned int  pin;

28.          unsigned char value;

29.          char* name;

30.  };

31.   

32.  static struct key_desc my_key_descs[] = {

33.          [0] = {

34.                  .pin = S5PV210_GPH0(3),

35.                  .value = 0x01,

36.                  .name ="Key1",

37.          },

38.   

39.          [1] = {

40.                  .pin = S5PV210_GPH0(4),

41.                  .value = 0x02,

42.                  .name ="Key2",

43.          },

44.   

45.          [2] = {

46.                  .pin = S5PV210_GPH0(5),

47.                  .value = 0x03,

48.                  .name ="Key3",

49.          },

50.   

51.          [3] = {

52.                  .pin = S5PV210_GPH0(6),

53.                  .value = 0x04,

54.                  .name ="Key4",

55.          },

56.   

57.          [4] = {

58.                  .pin = S5PV210_GPH0(7),

59.                  .value = 0x05,

60.                  .name ="Key5",

61.          },

62.   

63.  };

64.   

65.  #define BUTTONS_NUM        ARRAY_SIZE(my_key_descs)

66.   

67.  static irqreturn_t my_buttons_irq(int irq, void *dev_id){

68.          volatile struct key_desc *key = (volatile struct key_desc *)dev_id;

69.   

70.          if(gpio_get_value(key->pin)){

71.                  key_val = key->value|0x80;

72.          }

73.          else{

74.                  key_val = key->value;

75.          }

76.   

77.          pressed = 1;

78.          wake_up_interruptible(&my_button_waitq);

79.   

80.      kill_fasync(&my_button_fasync, SIGIO, POLL_IN);  //kill_fasync函数告诉应用程序,有数据可读了

81.          return IRQ_RETVAL(IRQ_HANDLED);

82.  }

83.   

84.  static int my_buttons_open(struct inode *inode, struct file *file){

85.          int ret;

86.   

87.          ret = request_irq( IRQ_EINT3,   my_buttons_irq, IRQ_TYPE_EDGE_BOTH, "key1", &my_key_descs[0]);

88.          if(ret)

89.                  return ret;

90.          ret = request_irq( IRQ_EINT4,   my_buttons_irq, IRQ_TYPE_EDGE_BOTH, "key2", &my_key_descs[1]);

91.          if(ret)

92.                  return ret;

93.           ret = request_irq( IRQ_EINT5,   my_buttons_irq, IRQ_TYPE_EDGE_BOTH, "key3", &my_key_descs[2]);

94.          if(ret)

95.                  return ret;

96.           ret = request_irq( IRQ_EINT6,   my_buttons_irq, IRQ_TYPE_EDGE_BOTH, "key4", &my_key_descs[3]);

97.          if(ret)

98.                  return ret;

99.          ret = request_irq( IRQ_EINT7,   my_buttons_irq, IRQ_TYPE_EDGE_BOTH, "key5", &my_key_descs[4]);

100.                      if(ret)

101.                              return ret;

102.               

103.                      return 0;