[OK210開發板體驗]功能篇(2)Linux字符驅動之Key按鍵驅動
【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* groupsare in Alive region)
二、軟件基礎
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. };
復制代碼
本節將會用到open,read,poll,fasync,release這5個函數,下面將會一一介紹。
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;