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

五、总结

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


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

相关推荐
JunLan~2 小时前
Rocky Linux 系统安装/部署 Docker
linux·docker·容器
嵌新程2 小时前
day06(单片机高级)PCB设计
单片机·嵌入式硬件·pcb
方竞3 小时前
Linux空口抓包方法
linux·空口抓包
stm 学习ing3 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
Red Red4 小时前
网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务
网络·笔记·学习·安全·web安全
海岛日记4 小时前
centos一键卸载docker脚本
linux·docker·centos
AttackingLin4 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
Natural_yz5 小时前
大数据学习17之Spark-Core
大数据·学习·spark
qq_172805595 小时前
RUST学习教程-安装教程
开发语言·学习·rust·安装