文章目录
前言
本文的主要内容是介绍中断及其相关函数,再通过一个实例,即按键响应实验来帮助理解中断相关的知识,按键响应实验的内容是按键按下时在开发板上进行打印,每按一次,打印一次。
一、中断相关的概念
中断:CPU在正常运行期间,由外部或者内部引起的事件,让CPU停下当前正在运行的程序,转而去执行触发其他中断所对应的程序,这就是中断。
中断上下文:中断的存在可以极大的提高CPU的运行效率,但是中断会打断内核进程中正常的调度和运行,所以为了保证系统的实时性,中断服务程序必须足够简短,但实际应用中某些中断必须要处理大量的事物,如果这些都在中断服务中完成,则会严重降低中断的实时性,基于这个原因,Linux系统提出把中断服务程序分为两个部分,即中断上文和中断下文。
中断上文:完成尽可能少且急的任务,其显著特点是响应速度快。
中断下文:处理中断中剩余的比较耗时的任务,该中断可以被新的中断再次打断。
Linux下中断不可以进行嵌套,因为容易进入死循环。
二、中断相关的函数
1.获取中断号相关函数
获取中断号相关的函数有irq_of_parse_and_map和gpio_to_irq,其介绍分别如下。
1>.irq_of_parse_and_map
该函数定义在:/linux-4.1.15/include/linux/of_irq.h文件中。
extern unsigned int irq_of_parse_and_map(struct device_node *node, int index);
参数介绍:
node:设备节点。
index:索引号,interrupts可能包含多条中断信息,通过索引号就可以指定要获取的信息。
返回值:中断号。
irq_of_parse_and_map函数可以从interrupts属性中提取到对应的设备号。
2>.gpio_to_irq
该函数定义在:/linux-4.1.15/include/linux/gpio.h文件中。
static inline int gpio_to_irq(unsigned int gpio)
{
return __gpio_to_irq(gpio);
}
参数介绍:
gpio:要获取的gpio编号。
返回值:gpio对应的中断号。
以上两个函数在调用时使用一个即可,区别在于irq_of_parse_and_map函数要在设备树中添加interrupts属性,而函数gpio_to_irq可以不用。
test_key{
compatible = "led_keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led_keys>;
key-gpio = <&gpio2 18 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio2>; //使用irq_of_parse_and_map函数获取gpio编号时需要添加
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
2.申请中断函数
申请中断的函数为request_irq,该函数定义在:/linux-4.1.15/include/linux/interrupt.h文件中。
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
参数介绍:
irq:要申请的中断号。
handler:中断处理函数,当中断发生时,执行该函数。
flags:中断标志。
name:中断名字,设置后在/proc/interrupts目录下可以查看。
dev:如果flags设置为IRQF_SHARED,dev用来区分不同的中断,一般情况下,dev设置为设备结构体,它会传给中断处理函数irq_handler_t第二个参数,本例中置为NULL。
返回值:申请成功返回0,申请失败返回负值,返回**-EBUSY**表示中断已经被申请。
flags的相关标志介绍如下表。
3.中断释放函数
中断使用完成以后要释放掉,如果中断是非共享的,那么释放函数会删除掉中断处理函数并且禁止中断,共享中断只有在释放最后中断处理函数时才会被禁止。
中断释放函数为free_irq,该函数定义在:/linux-4.1.15/include/linux/interrupt.h文件中。
extern void free_irq(unsigned int, void *dev);
参数介绍:
int:要释放的中断号。
dev:如果flags设置为IRQF_SHARED,dev用来区分不同的中断。
返回值:无。
三、修改设备树
由于我的开发板上没有多余的按键,因此需要通过gpio口外接一个按键开关,这个按键对应的gpio要写在设备树里面。
首先查找原理图文件或者数据手册,选定一个gpio口来外接开关,我这里选择EIM A20口。
然后在文件/linux-4.1.15/arch/arm/boot/dts/imx6dl-pinfunc.h中按CTRL+F查找A20,选择含有gpio的那一个即可。
然后在你开发板对应的设备树文件中添加如下代码。
test_key{
compatible = "led_keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led_keys>;
key-gpio = <&gpio2 18 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio2>; //使用irq_of_parse_and_map函数获取gpio编号时需要添加
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
添加位置为。
添加按键开关gpio口的代码。
pinctrl_led_keys: led_keys{ //开发板上A20口对应的按键开关
fsl,pins = <
MX6QDL_PAD_EIM_A20__GPIO2_IO18 0x80000000
>;
};
添加位置为。
其他位置不再做改动,然后编译设备树文件,将其复制到tftp文件夹中待开发板启动时使用。
这部分的内容可以参见:Linux下通过tftp烧写设备树文件并启动开发板。
四、代码文件
该部分的代码以Linux中设备树下platform总线的应用为基础。
1.interrupt.c文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq;
irq_handler_t test_key(int irq, void *args)
{
printk("test_key trigger off!\n"); //按按键打印一句话
return IRQ_HANDLED;
}
int dts_probe(struct platform_device *pdev)
{
int ret = 0;
printk("dts_probe matching ok!\n");
/*查找节点*/
test_device_node = of_find_node_by_path("/test_key"); //在设备树节点中查找test_key这个节点
if(test_device_node == NULL){
printk("of_find_node_by_path is error!\n");
return -1;
}
/*获得gpio编号*/
gpio_num = of_get_named_gpio(test_device_node,"key-gpio",0);
if(gpio_num < 0){
printk("of_get_named_gpio is error!\n");
return -1;
}
/*设置gpio的方向*/
gpio_direction_input(gpio_num); //输入
/*获得gpio中断号*/
//irq = gpio_to_irq(gpio_num);
irq = irq_of_parse_and_map(test_device_node,0); //与上面这句代码的作用相同
printk("irq is %d\n", irq);
/*申请中断*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
//IRQF_TRIGGER_RISING为上升沿触发,定义在/linux-4.1.15/include/linux/interrupt.h中
if(ret < 0){
printk("request_irq is error!\n");
return -1;
}
return 0;
}
int dts_remove(struct platform_device *pdev)
{
printk("dts_remove!\n");
return 0;
}
const struct platform_device_id dts_idtable = {
.name = "dts_test1" //匹配优先级 第二
};
const struct of_device_id of_match_table_test[] = {
{.compatible = "led_keys"}, //匹配优先级 第一
{}
};
struct platform_driver dts_device = {
.probe = dts_probe,
.remove = dts_remove,
.driver = {
.owner = THIS_MODULE,
.name = "dts_test2", //匹配优先级 第三
.of_match_table = of_match_table_test
},
.id_table = &dts_idtable
};
static int dts_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&dts_device);
if(ret < 0) {
printk("platform_driver_register error!\n");
return ret;
}
printk("platform_driver_register ok!\n");
return 0;
}
static int dts_driver_exit(void)
{
printk("dts_driver_exit!\n");
free_irq(irq,NULL);
platform_driver_unregister(&dts_device);
}
module_init(dts_driver_init);
module_exit(dts_driver_exit);
MODULE_LICENSE("GPL");
2.Makefile文件
obj-m += interrupt.o
KDIR:=/linux/linux-4.1.15
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
五、运行结果
1.四脚按键开关简介
关于四脚按键开关,我查了其数据手册,重要的部分如下图所示。
按照电路图表可知,1,2脚连通,3,4脚连通,对应的实物如下,圈起来的是连通的。
当然,实际使用时最好拿万用表测一下,调到蜂鸣器档位,用万用表的两个探头将开关的管脚两两测试一下,发处蜂鸣声的即为连通的,或者电阻值为0的也是连通的。
在实际焊接时,两个连通的选其一即可,一般情况下选择对角位置的两个脚使用。一个脚接A20对应的gpio口,另一脚根据自己开发板的情况接3.3V或者GND。
2.开发板运行结果
将驱动文件发送到开发板,然后加载驱动文件。
通过如下命令在中断文件下查看刚才加载驱动所生成的新中断是否已经出现。
cat /proc/interrupts
运行结果如下图。
可以看到,中断号为87,中断名为“test_key”的中断已经出现了。
接着来测试按键开关的响应结果,按键每按下一次,开发板上就打印一句话。
按CTRL+C停止后,使用如下命令再看我们这个过程中按下按键的总次数。
cat /proc/irq/87/spurious
通过截图可以看到,打印的次数与count值相同。
总结
以上就是Linux下的中断介绍及其按键响应实验的所有内容了,Linux下中断的应用还是比较多的,应该通过这个简单的例子掌握其核心,这样在以后做中断有关的实验就游刃有余了!
本文参考视频:https://www.bilibili.com/video/BV1Vy4y1B7ta?p=35和https://www.bilibili.com/video/BV1Vy4y1B7ta?p=36。