Linux学习第23天:Linux中断驱动开发(二): 突如其来

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长


三、驱动程序开发

采用中断的方式,触发开发板上的KEY0按键。采用定时器消抖。应用程序采集按键值,通过终端打印出来。

1.修改设备树文件

在"key"节点下,添加中断相关属性。如下:

cpp 复制代码
1 key {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-key";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_key>;
7 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
8 interrupt-parent = <&gpio1>;/*因为 KEY0 所使用的 GPIO 为GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。*/
9 interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
/*第一个 cells 的 18 表示 GPIO1 组的 18号 IO。*/
10 status = "okay";
11 };

使用"make dtbs"命令编译设备树,使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。

2.按键中断驱动程序编写

cpp 复制代码
37 /* 中断 IO 描述结构体 */
38 struct irq_keydesc {
39 int gpio; /* gpio */
40 int irqnum; /* 中断号 */
41 unsigned char value; /* 按键对应的键值 */
42 char name[10]; /* 名字 */
43 irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
44 };

结构体 irq_keydesc 为按键的中断描述结构体, gpio 为按键 GPIO 编号, irqnum

为按键 IO 对应的中断号, value 为按键对应的键值, name 为按键名字, handler 为按键中断服

务函数。使用 irq_keydesc 结构体即可描述一个按键中断。

cpp 复制代码
46 /* imx6uirq 设备结构体 */
47 struct imx6uirq_dev{
48 dev_t devid; /* 设备号 */
49 struct cdev cdev; /* cdev */
50 struct class *class; /* 类 */
51 struct device *device; /* 设备 */
52 int major; /* 主设备号 */
53 int minor; /* 次设备号 */
54 struct device_node *nd; /* 设备节点 */
55 atomic_t keyvalue; /* 有效的按键键值 */
56 atomic_t releasekey; /* 标记是否完成一次完成的按键*/
57 struct timer_list timer; /* 定义一个定时器*/
58 struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
59 unsigned char curkeynum; /* 当前的按键号 */
60 };

结构体 imx6uirq_dev 为本例程设备结构体

cpp 复制代码
62 struct imx6uirq_dev imx6uirq; /* irq 设备 */

定义设备结构体变量 imx6uirq。

cpp 复制代码
64 /* @description : 中断服务函数,开启定时器,延时 10ms,
65 * 定时器用于按键消抖。
66 * @param - irq : 中断号
67 * @param - dev_id : 设备结构。
68 * @return : 中断执行结果
69 */
70 static irqreturn_t key0_handler(int irq, void *dev_id)
71 {
72 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
73
74 dev->curkeynum = 0;
75 dev->timer.data = (volatile long)dev_id;
76 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
77 return IRQ_RETVAL(IRQ_HANDLED);
78 }

key0_handler 函数,按键 KEY0 中断处理函数,参数 dev_id 为设备结构体,也就是 imx6uirq。第 74 行设置 curkeynum=0,表示当前按键为 KEY0,第 76 行使用 mod_timer函数启动定时器,定时器周期为 10ms。

cpp 复制代码
80 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
81 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
82 * @param -- arg : 设备结构变量
83 * @return : 无
84 */
85 void timer_function(unsigned long arg)
86 {
87 unsigned char value;
88 unsigned char num;
89 struct irq_keydesc *keydesc;
90 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
91
92 num = dev->curkeynum;
93 keydesc = &dev->irqkeydesc[num];
94
95 value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
96 if(value == 0){ /* 按下按键 */
97 atomic_set(&dev->keyvalue, keydesc->value);
98 }
99 else{ /* 按键松开 */
100 atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
101 atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
102 }
103 }

timer_function 函数,定时器定时处理函数,参数 arg 是设备结构体,也就是imx6uirq,在此函数中读取按键值。第 95 行通过 gpio_get_value 函数读取按键值。如果为 0 的话就表示按键被按下去了,按下去的话就设置 imx6uirq 结构体的 keyvalue 成员变量为按键的键值,比如 KEY0 按键的话按键值就是 KEY0VALUE=0。如果按键值为 1 的话表示按键被释放了,按键释放了的话就将 imx6uirq 结构体的 keyvalue 成员变量的最高位置 1,表示按键值有效,也就是将 keyvalue 与 0x80 进行或运算,表示按键松开了, 并且设置 imx6uirq 结构体的 releasekey成员变量为 1,表示按键释放,一次有效的按键过程发生。

cpp 复制代码
106 * @description : 按键 IO 初始化
107 * @param : 无
108 * @return : 无
109 */
110 static int keyio_init(void)
111 {
112 unsigned char i = 0;
113
114 int ret = 0;
115
116 imx6uirq.nd = of_find_node_by_path("/key");
117 if (imx6uirq.nd== NULL){
118 printk("key node not find!\r\n");
119 return -EINVAL;
120 }
121
122 /* 提取 GPIO */
123 for (i = 0; i < KEY_NUM; i++) {
124 imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,
"key-gpio", i);
125 if (imx6uirq.irqkeydesc[i].gpio < 0) {
126 printk("can't get key%d\r\n", i);
127 }
128 }
129
130 /* 初始化 key 所使用的 IO,并且设置成中断模式 */
131 for (i = 0; i < KEY_NUM; i++) {
132 memset(imx6uirq.irqkeydesc[i].name, 0, 
sizeof(imx6uirq.irqkeydesc[i].name));
133 sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
134 gpio_request(imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].name);
135 gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
136 imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(
imx6uirq.nd, i);
137 #if 0
138 imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(
imx6uirq.irqkeydesc[i].gpio);
139 #endif
140 printk("key%d:gpio=%d, irqnum=%d\r\n",i,
imx6uirq.irqkeydesc[i].gpio,
141 imx6uirq.irqkeydesc[i].irqnum);
142 }
143 /* 申请中断 */
144 imx6uirq.irqkeydesc[0].handler = key0_handler;
145 imx6uirq.irqkeydesc[0].value = KEY0VALUE;
146
147 for (i = 0; i < KEY_NUM; i++) {
148 ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
imx6uirq.irqkeydesc[i].name, &imx6uirq);
149 if(ret < 0){
150 printk("irq %d request failed!\r\n",
imx6uirq.irqkeydesc[i].irqnum);
151 return -EFAULT;
152 }
153 }
154
155 /* 创建定时器 */
156 init_timer(&imx6uirq.timer);
157 imx6uirq.timer.function = timer_function;
158 return 0;
159 }

keyio_init 函数,按键 IO 初始化函数,在驱动入口函数里面会调用 keyio_init来初始化按键 IO。第 131~142 行轮流初始化所有的按键,包括申请 IO、设置 IO 为输入模式、从设备树中获取 IO 的中断号等等。第 136 行通过 irq_of_parse_and_map 函数从设备树中获取按键 IO 对应的中断号。也可以使用 gpio_to_irq 函数将某个 IO 设置为中断状态,并且返回其中断号。第 144 和 145 行设置 KEY0 按键对应的按键中断处理函数为 key0_handler、 KEY0 的按键为 KEY0VALUE。第 147~153 行轮流调用 request_irq 函数申请中断号,设置中断触发模式为IRQF_TRIGGER_FALLING 和 IRQF_TRIGGER_RISING,也就是上升沿和下降沿都可以触发中

断。最后,第 156 行初始化定时器,并且设置定时器的定时处理函数。

cpp 复制代码
 /*
162 * @description : 打开设备
163 * @param -- inode : 传递给驱动的 inode
164 * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
165 * 一般在 open 的时候将 private_data 指向设备结构体。
166 * @return : 0 成功;其他 失败
167 */
168 static int imx6uirq_open(struct inode *inode, struct file *filp)
169 {
170 filp->private_data = &imx6uirq; /* 设置私有数据 */
171 return 0;
172 }
cpp 复制代码
174 /*
175 * @description : 从设备读取数据
176 * @param -- filp : 要打开的设备文件(文件描述符)
177 * @param -- buf : 返回给用户空间的数据缓冲区
178 * @param - cnt : 要读取的数据长度
179 * @param -- offt : 相对于文件首地址的偏移
180 * @return : 读取的字节数,如果为负值,表示读取失败
181 */
182 static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
183 {
184 int ret = 0;
185 unsigned char keyvalue = 0;
186 unsigned char releasekey = 0;
187 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)
filp->private_data;
188
189 keyvalue = atomic_read(&dev->keyvalue);
190 releasekey = atomic_read(&dev->releasekey);
191
192 if (releasekey) { /* 有按键按下 */
193 if (keyvalue & 0x80) {
194 keyvalue &= ~0x80;
195 ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
196 } else {
197 goto data_error;
198 }
199 atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
200 } else {
201 goto data_error;
202 }
203 return 0;
204
205 data_error:
206 return -EINVAL;
207 }

imx6uirq_read 函数,对应应用程序的 read 函数。此函数向应用程序返回按键值。首先判断 imx6uirq 结构体的 releasekey 成员变量值是否为 1,如果为 1 的话表示有一次有效按键发生,否则的话就直接返回-EINVAL。当有按键事件发生的话就要向应用程序发送按键值,首先判断按键值的最高位是否为 1,如果为 1 的话就表示按键值有效。如果按键值有效的话就将最高位清除,得到真实的按键值,然后通过 copy_to_user 函数返回给应用程序。向应用程序发送按键值完成以后就将 imx6uirq 结构体的 releasekey 成员变量清零,准备下一次按键操作。

cpp 复制代码
209 /* 设备操作函数 */
210 static struct file_operations imx6uirq_fops = {
211 .owner = THIS_MODULE,
212 .open = imx6uirq_open,
213 .read = imx6uirq_read,
214 };

按键中断驱动操作函数集 imx6uirq_fops。

cpp 复制代码
216 /*
217 * @description : 驱动入口函数
218 * @param : 无
219 * @return : 无
220 */
221 static int __init imx6uirq_init(void)
222 {
223 /* 1、构建设备号 */
224 if (imx6uirq.major) {
225 imx6uirq.devid = MKDEV(imx6uirq.major, 0);
226 register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,
IMX6UIRQ_NAME);
227 } else {
228 alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,
IMX6UIRQ_NAME);
229 imx6uirq.major = MAJOR(imx6uirq.devid);
230 imx6uirq.minor = MINOR(imx6uirq.devid);
231 }
232
233 /* 2、注册字符设备 */
234 cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
235 cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
236
237 /* 3、创建类 */
238 imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
239 if (IS_ERR(imx6uirq.class)) {
240 return PTR_ERR(imx6uirq.class);
241 }
242
243 /* 4、创建设备 */
244 imx6uirq.device = device_create(imx6uirq.class, NULL,
imx6uirq.devid, NULL, IMX6UIRQ_NAME);
245 if (IS_ERR(imx6uirq.device)) {
246 return PTR_ERR(imx6uirq.device);
247 }
248
249 /* 5、 初始化按键 */
250 atomic_set(&imx6uirq.keyvalue, INVAKEY);
251 atomic_set(&imx6uirq.releasekey, 0);
252 keyio_init();
253 return 0;
254 }

驱动入口函数,第 250 和 251 行分别初始化 imx6uirq 结构体中的原子变量keyvalue 和releasekey,第 252 行调用 keyio_init 函数初始化按键所使用的 IO。

cpp 复制代码
256 /*
257 * @description : 驱动出口函数
258 * @param : 无
259 * @return : 无
260 */
261 static void __exit imx6uirq_exit(void)
262 {
263 unsigned int i = 0;
264 /* 删除定时器 */
265 del_timer_sync(&imx6uirq.timer);
266
267 /* 释放中断 */
268 for (i = 0; i < KEY_NUM; i++) {
269 free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
270 }
271 cdev_del(&imx6uirq.cdev);
272 unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
273 device_destroy(imx6uirq.class, imx6uirq.devid);
274 class_destroy(imx6uirq.class);
275 }
276
277 module_init(imx6uirq_init);
278 module_exit(imx6uirq_exit);
279 MODULE_LICENSE("GPL");
280 MODULE_AUTHOR("zuozhongkai");

驱动出口函数,第 265 行调用 del_timer_sync 函数删除定时器,第 268~070行轮流释放申请的所有按键中断。

3.编写测试APP

不断的读取/dev/imx6uirq 文件来获取按键值,当按键按下以后就会将获取到的按键值输出在终端上.

cpp 复制代码
21 /*
22 * @description : main 主程序
23 * @param - argc : argv 数组元素个数
24 * @param - argv : 具体参数
25 * @return : 0 成功;其他 失败
26 */
27 int main(int argc, char *argv[])
28 {
29 int fd;
30 int ret = 0;
31 char *filename;
32 unsigned char data;
33 if (argc != 2) {
34 printf("Error Usage!\r\n");
35 return -1;
36 }
37
38 filename = argv[1];
39 fd = open(filename, O_RDWR);
40 if (fd < 0) {
41 printf("Can't open file %s\r\n", filename);
42 return -1;
43 }
44
45 while (1) {
46 ret = read(fd, &data, sizeof(data));
47 if (ret < 0) { /* 数据读取错误或者无效 */
48
49 } else { /* 数据读取正确 */
50 if (data) /* 读取到数据 */
51 printf("key value = %#X\r\n", data);
52 }
53 }
54 close(fd);
55 return ret;
56 }

第 45~53 行的 while 循环用于不断的读取按键值,如果读取到有效的按键值就将其输出到

终端上。

四、运行测试

1.编译驱动程序和测试APP

cpp 复制代码
1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := imx6uirq.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

输入如下命令编译出驱动模块文件:

make -j32

编译成功以后就会生成一个名为" imx6uirq.ko"的驱动模块文件。

输入如下命令编译测试 imx6uirqApp.c 这个测试程序:

arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp

编译成功以后就会生成 imx6uirqApp 这个应用程序。

2.运行测试

cpp 复制代码
depmod //第一次加载驱动的时候需要运行此命令
modprobe imx6uirq.ko //加载驱动

用如下命令来测试中断:

cpp 复制代码
./imx6uirqApp /dev/imx6uirq

按下开发板上的 KEY0 键,终端就会输出按键值。

如果要卸载驱动的话输入如下命令即可:

cpp 复制代码
rmmod imx6uirq.ko

五、总结

本节笔记主要学习了中断驱动开发的下半部分,主要包括中断驱动开发及测试。其中最主要的内容为中断驱动开发相关内容。


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

相关推荐
KingRumn2 小时前
Linux信号之标准信号与实时信号
linux·算法
QT 小鲜肉5 小时前
【Linux命令大全】001.文件管理之git命令(实操篇)
linux·服务器·笔记·git·elasticsearch
半夏知半秋5 小时前
docker常用指令整理
运维·笔记·后端·学习·docker·容器
sishen41995 小时前
嵌入式Linux没有学习方向怎么办,嵌入式Linux怎么学
linux
逆风水手5 小时前
Ansible自动化运维入门指南
linux·运维·自动化·ansible
蒸蒸yyyyzwd5 小时前
网络编程——threadpool.h学习笔记
笔记·学习
浪子不回头4155 小时前
SGLang学习笔记
人工智能·笔记·学习
deng-c-f6 小时前
Linux C/C++ 学习日记(53):原子操作(二):实现shared_ptr
开发语言·c++·学习
旖旎夜光7 小时前
Linux(3)(下)
linux·学习