借助Linux中断机制的按键开关功能实现

2023-11-18

AlienTek的IMX6ULL开发板自带了一个按键和一个LED灯,这两个外设分别接在两个不同的GPIO端口,各自独立。我们想把按键作为灯的开关,通过按压按键来控制灯的亮灭,即灯亮时按一下则灯灭,灯灭时按一下则灯亮,这里的“按一下”是指按键从被按下到抬起后的整个过程。对开发板SOC而言,按键的物理状态可通过与其相连的GPIO端口的电平值得到反映,GPIO本身可作为中断控制器,GPIO输入电平的值或者电平值的变化都可以作为中断触发信号,中断的处理过程由Linux中断子系统作统一管理。我们可以利用Linux框架下的中断来实现前面所说的按键开关功能。

[1] Hardware层

① 电路原理

首先要明确按键和灯的硬件信息。LED灯和按键的硬件原理图主要如下。据图我们可得知LED0的阴极和GPIO1_03连接,阳极电平为3.3V,所以GPIO1_03输出低电平时灯亮,否则灯灭。按键与引脚UART1_CTS连接,这是哪个GPIO端口?查阅IMX6ULL的参考手册如下图,可以得知UART1_CTS复用GPIO1_18的功能,我们可以通过设置复用寄存器,把该引脚的功能配成GPIO,那么根据原理图可知,当按键按下时GPIO1_18输入低电平,平时输入高电平。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

② 硬件的设备树表示

当前的Linux内核框架下,开发板的硬件信息都是基于设备树呈现的,所以我们要把以上的硬件外设写成设备树的形式以供内核读取。我们直接在设备树SOC的根目录下创建2个独立节点,分别表示LED和按键,如下。LED的节点名称为gpioled,其节点属性pinctrl-0指向pinctrl_led节点,它描述了GPIO1_03的复用、电气属性等信息,字段MX6UL_PAD_GPIO1_IO03__GPIO1_IO03可在imx6ul-pinfunc.h中查到,它是由5个十六进制数组成的宏定义,表示该端口配置为GPIO功能,即GPIO1_IO03。后面跟的数0x10B0表示了GPIO1_IO03的电气配置参数,它的各位含义可参照参考手册中的寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03。属性字段led-gpio描述了gpioled节点使用到的GPIO信息,“&gpio1 3”就是指GPIO1_03,GPIO_ACTIVE_LOW表示“低电平有效”,“有效”在这里是指LED灯亮,由于GPIO输出低电平时灯亮,所以我们说低有效。这样LED的设备树节点用到的信息就完成了。按键的节点名称为key,它用到的引脚GPIO信息和LED的引脚信息写法类似,无须赘述。需要关注的是,字段interrupt-parent意思是中断继承,因为我们要利用按键GPIO的中断功能,所以需要在这里添加中断相关的信息,中断继承的内容是&gpio1,GPIO1本身是中断控制器,这里的意思是按键连接的GPIO的中断触发由GPIO1中断控制器处理。后面的interrupts字段,18表示GPIO1_18在GPIO1中断控制器的中断索引,IRQ_TYPE_EDGE_RISING表示GPIO输入电平上升沿触发中断,也就是按键弹起时中断应得到响应。

pinctrl_led: ledgrp {
    fsl,pins = <
    	MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
    >;
};
pinctrl_key: keygrp {
	fsl,pins = <
		MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080
	>;
};
gpioled {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "atkalpha-gpioled";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_led>;
	led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
	status = "okay";
};
key {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "atkalpha-key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
	interrupt-parent = <&gpio1>;
	interrupts = <18 IRQ_TYPE_EDGE_RISING>;
	status = "okay";
};

[2] Driver层

我们在driver层定义一个字符设备,整个驱动框架按Linux字符设备框架搭建,主要的几个任务有从设备树获取定义好的硬件信息、按键GPIO申请中断、定义中断服务函数、按键消抖、LED亮灭控制等。我们设想的开关控制灯亮灭的逻辑是:按键被按压完成后触发中断,在中断服务函数中更新灯当前的亮灭状态,driver把该状态上报到app层,app层读取LED状态并据此发送相应的控制命令到driver,driver根据命令控制LED灯GPIO的输出电平从而控制亮灭。在文件操作结构体file_operations中,我们可以实现read和ioctl这两个接口,前者用来向应用层报告“按键被按”这个消息,后者用来实现LED灯的控制方法。
对于机械按键而言,往往需要考虑按键抖动问题,在按键刚按完后立即读对应的GPIO电平,由于抖动存在常会出现随机的电平值,时高时低,难以说明按键的真正状态。一般需要在按键动作结束后间隔几毫秒至几十毫秒,再去获取GPIO电平,此时基本上能得到真实的按键状态,即消抖。但是在中断处理函数中最好不要做任何延时操作,我们可以利用内核定时器的特性,当按键动作后中断被触发,在中断处理函数中激活定时器,这样定时器将在给定的时间后调用定时器处理函数,我们在定时器处理函数中再去读取按键GPIO电平。

① 设备结构

我们首先定义设备结构体,如下,把能用到的几乎所有信息都写进去了。成员ledIndex和keyIndex表示LED和按键对应的GPIO编号,这是通过系统提供的of函数从设备树外设节点获取得到的,后面要用于灯的控制及中断申请等操作。成员keyRelease标志按键的动作是否完成,完成为true,否则为false。成员irqHandler将来要指向我们自定义的中断处理函数。

typedef struct _keyGpioledDev {
	struct cdev dev;  // 字符设备
	dev_t devid;  // 设备号
	int major;  // 主设备号
	int minor;  // 次设备号
#define DEVNAME "key_gpioled"  // 设备名
#define CLASSNAME "key_gpioled"  // 设备类名
	struct class *devclass;  // 设备类
	struct device *devdevice;  // 设备
	struct device_node *devnode_led;  // LED设备节点
	int ledIndex;  // LED GPIO编号
#define SETLEDON (_IO(0xEF, 0x1))  // 开灯命令
#define SETLEDOFF (_IO(0xEF, 0x2))  // 关灯命令
	struct device_node *devnode_key;  // 按键设备节点
	int keyIndex;  // 按键GPIO编号
	int irqNum;  // 中断号
	bool keyRelease;  // 按键动作完成标志
	irqreturn_t (*irqHandler)(int, void *);  // 按键GPIO中断服务函数指针
	struct timer_list timer;  // 按键消抖定时器
} keyGpioledDev;

② 驱动模块初始化

驱动初始化时主要处理3件事情,即注册字符设备(注册cdev、创建类和设备等)、从设备树获取外设节点(申请GPIO使用权、申请中断等)和初始化定时器(指定定时器触发函数)。获取外设节点的GPIO信息后,首先要调用函数gpio_request获得GPIO的使用权限。对于按键GPIO,先调用gpio_direction_input将GPIO1_18配置成输入模式,然后调用gpio_to_irq根据GPIO编号得到中断号,并指定该中断号关联的中断服务函数,再调用request_irq向系统申请按键的GPIO中断,中断触发方式要按上升沿配置,并且要给这个接口传入设备实例指针,这样在中断服务函数中就可以获取到设备信息。在中断处理中我们激活定时器,让它在一定时间后执行定时器触发函数。在定时器触发函数中,我们应该读取GPIO1_18的电平值,如果为高电平则表示按键已经弹起,按键动作完成了,否则表示按键还处于被按压的状态,按键动作未完成。

/* 驱动模块初始化 */
static int __init keyGpioled_init(void) {
	/* 注册字符设备(注册cdev、创建类和设备等) */
	if((setupCdev()) != 0) {
		printk("setupCdev failed\n");
		return -1;
	}
	/* 从设备树获取外设节点(申请GPIO使用权、申请中断等) */
	if((getDevnode()) != 0) {
		printk("getDevnode failed\n");
		return -1;
	}
	/* 初始化定时器(指定定时器触发函数) */
	timerInit();
	return 0;
}
/* 中断服务函数 */
irqreturn_t key0_irqHandler(int irq, void *dev_id) {
	keyGpioledDev *temp_dev = (keyGpioledDev *)dev_id;
	printk("key0_irqHandler triggered\n");
	temp_dev->timer.data = (volatile long)dev_id;  // 让timer.data指向设备实例,可以在定时器触发函数中使用设备信息
	mod_timer(&temp_dev->timer, jiffies + msecs_to_jiffies(10));  // 激活定时器,10ms后运行触发函数
	return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器触发函数 */
void timer_func(unsigned long arg) {
	keyGpioledDev *temp_dev = (keyGpioledDev *)arg;
	unsigned char value;
	value = gpio_get_value(temp_dev->keyIndex);  // 读取按键GPIO电平值
	if(value == 1) {
		printk("key released\n");
		temp_dev->keyRelease = true;  // 按键释放
	} else if(value == 0) {
		printk("key not released\n");
	} else if(value < 0) {
		printk("gpio_get_value error\n");
	}
}

③ 系统调用接口实现

我们主要实现read和ioctl。在read中,我们先判断设备实例中的keyRelease标志是否为true,是则设置灯控制标志,如果当前灯亮则标志为关灯,否则标志为开灯,然后将keyRelease和灯控制标志一起通过copy_to_user报告给上层。在ioctl中,根据上层发送来的灯控制命令调用gpio_set_value设置LED GPIO的输出电平,实现开灯或关灯,这里的灯控制命令就是上面设备结构体中定义的SETLEDON和SETLEDOFF。

static ssize_t keyGpioled_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
	unsigned long ret;
	keyGpioledDev *temp_dev = filp->private_data;
	if(temp_dev->keyRelease == true) {  // 按键动作完成
		updateLedOnFlag();  // 设置灯控制标志
		memset(flag, 0, sizeof(flag));
		flag[0] = keyReleaseFlag;
		flag[1] = ledOnFlag;
		ret = copy_to_user(buf, flag, sizeof(flag));  // 报告给上层
		if(ret != 0) {
			printk("copy_to_user failed\n");
			return -1;
		}
		temp_dev->keyRelease = false;  // 清除按键动作完成标志,等待下一次按键动作完成
	} else
		return -EINVAL;
	return 0;
}
static long keyGpioled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
	keyGpioledDev *temp_dev = filp->private_data;
	switch(cmd) {
		case SETLEDON:
			printk("ioctl cmd SETLEDON\n");
			gpio_set_value(temp_dev->ledIndex, 0);
			break;
		case SETLEDOFF:
			printk("ioctl cmd SETLEDOFF\n");
			gpio_set_value(temp_dev->ledIndex, 1);
			break;
		default:
			printk("unknown ioctl cmd\n");
			break;
	}
	return 0;
}

④ driver完整代码

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/* 设备结构体 */
typedef struct _keyGpioledDev {
	struct cdev dev;  // 字符设备
	dev_t devid;  // 设备号
	int major;  // 主设备号
	int minor;  // 次设备号
#define DEVNAME "key_gpioled"  // 设备名
#define CLASSNAME "key_gpioled"  // 设备类名
	struct class *devclass;  // 设备类
	struct device *devdevice;  // 设备
	struct device_node *devnode_led;  // LED设备节点
	int ledIndex;  // LED GPIO编号
#define SETLEDON (_IO(0xEF, 0x1))  // 开灯命令
#define SETLEDOFF (_IO(0xEF, 0x2))  // 关灯命令
	struct device_node *devnode_key;  // 按键设备节点
	int keyIndex;  // 按键GPIO编号
	int irqNum;  // 中断号
	bool keyRelease;  // 按键动作完成标志
	irqreturn_t (*irqHandler)(int, void *);  // 按键GPIO中断服务函数指针
	struct timer_list timer;  // 按键消抖定时器
} keyGpioledDev;

keyGpioledDev keyGpioled;  // 设备实例

unsigned char keyReleaseFlag = 1;  // 按键完成标志
unsigned char ledOnFlag;  // 开灯标志
unsigned char flag[2];

static int keyGpioled_open(struct inode *inode, struct file *filp) {
	filp->private_data = &keyGpioled;  // 设置文件私有数据指向设备实例
	printk("keyGpioled_open\n");
	return 0;
}
static int keyGpioled_release(struct inode *inode, struct file *filp)
{
	printk("keyGpioled_release\n");
	return 0;
}
/* 灯亮灭控制标志切换 */
void updateLedOnFlag(void) {
	if(!ledOnFlag)
		ledOnFlag = 1;
	else
		ledOnFlag = 0;
}
static ssize_t keyGpioled_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
	unsigned long ret;
	keyGpioledDev *temp_dev = filp->private_data;
	if(temp_dev->keyRelease == true) {  // 按键动作完成
		updateLedOnFlag();  // 设置灯控制标志
		memset(flag, 0, sizeof(flag));
		flag[0] = keyReleaseFlag;
		flag[1] = ledOnFlag;
		ret = copy_to_user(buf, flag, sizeof(flag));  // 报告给上层
		if(ret != 0) {
			printk("copy_to_user failed\n");
			return -1;
		}
		temp_dev->keyRelease = false;  // 清除按键动作完成标志,等待下一次按键动作完成
	} else
		return -EINVAL;
	return 0;
}
static long keyGpioled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
	keyGpioledDev *temp_dev = filp->private_data;
	switch(cmd) {
		case SETLEDON:
			printk("ioctl cmd SETLEDON\n");
			gpio_set_value(temp_dev->ledIndex, 0);
			break;
		case SETLEDOFF:
			printk("ioctl cmd SETLEDOFF\n");
			gpio_set_value(temp_dev->ledIndex, 1);
			break;
		default:
			printk("unknown ioctl cmd\n");
			break;
	}
	return 0;
}
struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = keyGpioled_open,
	.read = keyGpioled_read,
	.unlocked_ioctl = keyGpioled_ioctl,
	.release = keyGpioled_release,	
};
static int setupCdev(void) {
	if((alloc_chrdev_region(&keyGpioled.devid, 0, 1, DEVNAME)) != 0) {
		printk("alloc_chrdev_region failed\n");
		return -1;
	}
	keyGpioled.major = MAJOR(keyGpioled.devid);
	keyGpioled.minor = MINOR(keyGpioled.devid);
	printk("major dev num = %d, minor dev num = %d\n", keyGpioled.major, keyGpioled.minor);

	keyGpioled.dev.owner = THIS_MODULE;
	cdev_init(&keyGpioled.dev, &fops);
	if((cdev_add(&keyGpioled.dev, keyGpioled.devid, 1)) != 0) {
		printk("cdev_add failed\n");
		return -1;
	}

	keyGpioled.devclass = class_create(THIS_MODULE, CLASSNAME);
	if(IS_ERR(keyGpioled.devclass)) {
		printk("class_create failed\n");
		cdev_del(&keyGpioled.dev);
		return PTR_ERR(keyGpioled.devclass);
	}

	keyGpioled.devdevice = device_create(keyGpioled.devclass, NULL, keyGpioled.devid, NULL, DEVNAME);
	if(IS_ERR(keyGpioled.devdevice)) {
		printk("device_create failed\n");
		class_destroy(keyGpioled.devclass);
		cdev_del(&keyGpioled.dev);
		return PTR_ERR(keyGpioled.devdevice);
	}
	return 0;
}
static int getLednode(void) {
	keyGpioled.devnode_led = of_find_node_by_path("/gpioled");  // 获取gpioled设备树节点
	if(keyGpioled.devnode_led == NULL) {
		printk("of_find_node_by_path gpioled failed\n");
		return -1;
	}

	keyGpioled.ledIndex = of_get_named_gpio(keyGpioled.devnode_led, "led-gpio", 0);  // 获取LED GPIO编号
	if(keyGpioled.ledIndex < 0) {
		printk("of_get_named_gpio led-gpio failed\n");
		return -1;
	} else
		printk("get ledIndex (gpio num): %d\n", keyGpioled.ledIndex);

	if((gpio_request(keyGpioled.ledIndex, "led0")) != 0) {  // 申请GPIO1_03的使用权限
		printk("gpio_request led0 failed\n");
		return -1;
	}

	if((gpio_direction_output(keyGpioled.ledIndex, 1)) < 0) {  // 默认关闭LED灯
		printk("unable to set gpio: %d\n", keyGpioled.ledIndex);
		gpio_free(keyGpioled.ledIndex);
		return -1;
	}

	ledOnFlag = 0;  // 默认灯不亮
	return 0;
}
/* 定时器触发函数 */
void timer_func(unsigned long arg) {
	keyGpioledDev *temp_dev = (keyGpioledDev *)arg;
	unsigned char value;
	value = gpio_get_value(temp_dev->keyIndex);  // 读取按键GPIO电平值
	if(value == 1) {
		printk("key released\n");
		temp_dev->keyRelease = true;  // 按键释放
	} else if(value == 0) {
		printk("key not released\n");
	} else if(value < 0) {
		printk("gpio_get_value error\n");
	}
}
/* 中断服务函数 */
irqreturn_t key0_irqHandler(int irq, void *dev_id) {
	keyGpioledDev *temp_dev = (keyGpioledDev *)dev_id;
	printk("key0_irqHandler triggered\n");
	temp_dev->timer.data = (volatile long)dev_id;  // 让timer.data指向设备实例,可以在定时器触发函数中使用设备信息
	mod_timer(&temp_dev->timer, jiffies + msecs_to_jiffies(10));  // 激活定时器,10ms后运行触发函数
	return IRQ_RETVAL(IRQ_HANDLED);
}
static int getKeynode(void) {
	keyGpioled.devnode_key = of_find_node_by_path("/key");
	if(keyGpioled.devnode_key == NULL) {
		printk("of_find_node_by_path key failed\n");
		return -1;
	}

	keyGpioled.keyIndex = of_get_named_gpio(keyGpioled.devnode_key, "key-gpio", 0);
	if(keyGpioled.keyIndex < 0) {
		printk("of_get_named_gpio key-gpio failed\n");
		return -1;
	} else
		printk("get keyIndex (gpio num): %d\n", keyGpioled.keyIndex);
		
	if((gpio_request(keyGpioled.keyIndex, "key0")) != 0) {
		printk("gpio_request failed\n");
		return -1;
	}

	if((gpio_direction_input(keyGpioled.keyIndex)) != 0) {  // 按键GPIO配置为输入
		printk("gpio_direction_input failed\n");
		gpio_free(keyGpioled.keyIndex);
		return -1;
	}

	keyGpioled.irqNum = gpio_to_irq(keyGpioled.keyIndex);  // 获取GPIO1_18对应的中断号
	if(keyGpioled.irqNum < 0) {
		printk("gpio_to_irq failed\n");
		return -1;
	} else
		printk("get gpio irq num: %d\n", keyGpioled.irqNum);

	keyGpioled.irqHandler = key0_irqHandler;
	/* 申请中断 */
	if((request_irq(keyGpioled.irqNum, keyGpioled.irqHandler, IRQF_TRIGGER_RISING, "key0", &keyGpioled)) != 0) {
		printk("request_irq failed\n");
		return -1;
	}

	keyGpioled.keyRelease = false;  // 默认没有按键动作
	return 0;
}
static int getDevnode(void) {
	if((getLednode()) != 0) {
		printk("getLednode failed\n");
		return -1;
	}
	if((getKeynode()) != 0) {
		printk("getKeynode failed\n");
		return -1;
	}
	return 0;
}
void timerInit(void) {
	init_timer(&keyGpioled.timer);
	keyGpioled.timer.function = timer_func;
	printk("timerInit\n");
}
/* 驱动模块初始化 */
static int __init keyGpioled_init(void) {
	/* 注册字符设备(注册cdev、创建类和设备等) */
	if((setupCdev()) != 0) {
		printk("setupCdev failed\n");
		return -1;
	}
	/* 从设备树获取外设节点(申请GPIO使用权、申请中断等) */
	if((getDevnode()) != 0) {
		printk("getDevnode failed\n");
		return -1;
	}
	/* 初始化定时器(指定定时器触发函数) */
	timerInit();
	return 0;
}
static void __exit keyGpioled_exit(void) {
	del_timer_sync(&keyGpioled.timer);  // 删除定时器

	free_irq(keyGpioled.irqNum, &keyGpioled);  // 释放申请到的中断
	gpio_free(keyGpioled.keyIndex);  // 释放申请到使用权限的GPIO
	gpio_free(keyGpioled.ledIndex);

	device_destroy(keyGpioled.devclass, keyGpioled.devid);
	class_destroy(keyGpioled.devclass);
	cdev_del(&keyGpioled.dev);
	unregister_chrdev_region(keyGpioled.devid, 1);
}

module_init(keyGpioled_init);
module_exit(keyGpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fym");

[3] App层

我们在应用层使用read轮询是否有内核上报按键完成标志和灯控制标志,一旦有则根据灯控制标志来调用ioctl的具体命令,向内核发送LED控制信号,主要的实现如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/ioctl.h>

#define SETLEDON (_IO(0xEF, 0x1))
#define SETLEDOFF (_IO(0xEF, 0x2))

int main(int argc, char *argv[])
{
	int fd;
	int ret = 0;
	char *filename;
	unsigned char data[2];
	
	if (argc != 2) {
		printf("error usage!\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if(fd < 0) {
		printf("can not open file %s\n", filename);
		return -1;
	}

	while (1) {
		ret = read(fd, data, sizeof(data));
		if(ret < 0) { 
		} else {
			printf("keyReleaseFlag: %d, ledOnFlag: %d\n", data[0], data[1]);
			if(data[0]) {
				if(data[1]) {
					if((ioctl(fd, SETLEDON)) != 0) {
						printf("ioctl error\n");
						break;
					}
				} else {
					if((ioctl(fd, SETLEDOFF)) != 0) {
					  printf("ioctl error\n");
					  break;
					}
				}
			} else {
				printf("wrong keyReleaseFlag\n");
				break;
			}
		}
	}
	
	close(fd);
	return ret;
}

[4] 实践结果

编写驱动模块的Makefile,编译更新后的设备树、内核驱动和上层应用,得到新的内核模块、dtb及可执行文件,把它们导入到IMX6ULL开发板系统中的相应目录,重启开发板。首先加载驱动模块,可看到内核打印信息:
在这里插入图片描述
对照driver代码可知这些日志位置是在驱动模块加载函数中,显示该字符设备的主设备号是248,LED对应GPIO的编号是3,按键GPIO编号是18,这些都和硬件信息相符,获得的中断号是46。查看系统中的中断列表:
在这里插入图片描述
可见最后一列中断名称为key0的那行,正是我们申请的GPIO中断,最左侧是中断号46,目前被触发次数为0,因为我们还没按下按键。运行上层应用对应的可执行文件,然后按下开发板上的按键KEY0数次,可以看到每完成一次按键动作,应用层收到一次标志上报,同时现象上LED亮灭逐次翻转:
在这里插入图片描述
与此对应的,内核打印了相同次数的中断触发、按键完成及灯控制命令等信息:
在这里插入图片描述
根据日志信息也可验证驱动调用顺序,每次都是先进入中断服务key0_irqHandler,再进入定时器触发函数打印按键释放信息,然后打印来自上层的灯控制命令信息。这时再次查看中断信息:
在这里插入图片描述
发现46号中断的触发次数为13,对应上面key0_irqHandler triggered日志次数也是13,但实际上刚才按下按键的次数为10,对应应用层日志也是打印10次,这里就体现了前面所说的按键抖动问题,由于抖动一次按键动作可能引起了不止一次的GPIO电平上升过程,而定时器延时消抖的作用使得按键次数和灯状态翻转次数保持了一致。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

借助Linux中断机制的按键开关功能实现 的相关文章

随机推荐

  • mysql8.0出现group by报错

    数据库跟目录执行set GLOBAL sql mode STRICT TRANS TABLES NO ZERO IN DATE NO ZERO DATE ERROR FOR DIVISION BY ZERO NO ENGINE SUBSTI
  • Qt 使用笔记 --转自 wangwenx190/Note

    转自 https github com wangwenx190 notes blob master qt zh cn md Qt 使用笔记 Qt 6 目标平台变更 Qt6 不再支持32位Windows系统 不再支持Windows 7 Win
  • pageHelper.startPage(m,n)的用法

    pageHelper startPage m n 的用法 pageHelper startPage m n 是分页查询 PageHelper startPage m n 两个参数 第一个参数是页数 第二个参数是条数 每页查询的条数 例如我想
  • 疯壳AI开源无人机SPI(六轴传感器数据获取)

    一 ICM20602简介 六轴传感器在当今智能穿戴和定位导航产品中被广泛应用 而六轴传感器中做的最好的要属InvenSense公司的产品了 ICM20602便是其推出的优秀六轴传感器之一 ICM20602集成3轴加速度计和3轴陀螺仪 其中陀
  • bat命令备份oracle数据库,并且删除7天之前的数据文件

    用批处理命令备份oracle数据库 我是用在windows server 2008 服务器上 并且创建了定时任务 让他7天执行一次 下面贴出代码 echo off echo echo Windows环境下Oracle数据库的自动备份脚本 e
  • Fast DDS入门五、在Windows平台创建一个简单的Fast DDS示例程序

    1 创建简单示例程序 在这里 先建立一个IDL文件 然后通过使用Fast DDS Gen生成程序生成这个简单示例程序 Fast DDS Gen程序的编译安装请参考 Fast DDS入门二 Fast DDS在Windows平台的编译安装 Fa
  • 狂热的NFT,泡沫还是风口?

    比特币市场狂热的NFT今年以来 不仅 元宇宙 爆火 NFT Non Fungible Token 非同质化代币 也掀起一波波炒作热潮 3月份 数字艺术家Beeple的NFT作品 每一天 前5000天 在佳士得拍卖行以6934万美元成交 创造
  • 解决查询时报的cannot be cast to com.credithc.enjoy.manager.OrderResp错误

    报的错误如下所示 14 30 54 637 ERROR http nio 8094 exec 4 127 0 0 1 f6c45349d812457bbb5e42bc3a1bc09d 1 0 com credithc enjoy manag
  • 【Python函数的递归】

    递归的定义 函数作为一种代码封装 可以被其他程序调用 当然 也可以被函数内部代码调用 这种函数定义中调用函数自身的方式称为递归 就像一个人站在装满镜子的房间中 看到的影像就是递归的结果 递归在数学和计算机应用上非常强大 能够非常简洁的解决重
  • 可视化的数据结构和算法

    转载自 http sd csdn net a 20110506 297285 html 导读 作者陈皓之前写过关于可视化排序的一篇文章 现在他又给大家罗列出可视化的数据结构和算法来供大家学习参考 文中分别从基础 索引 排序 动态编程等方面进
  • 微软句向量工具包Sent2vec

    工具介绍 What is sent2vec sent2vec maps a pair of short text strings e g sentences or query answer pairs to a pair of featur
  • 关于 OneNote 无法正常同步 问题

    问题 无法正常同步 备注 问题开始之前请刷新一下DNS缓存 https blog csdn net ljason1993 article details 83040313 看一下 无法正常同步 意思是卡同步条 一直显示同步条问题 就这个绿条
  • 如何找Ubuntu的历史版本的iso镜像文件

    目录 1 中文网站上的查找 2 英文网站上的查找 1 中文网站上的查找 中文网站为 企业开源和Linux UbuntuUbuntu是适用于企业服务器 桌面电脑 云 IoT物联网的现代化开源Linux操作系统 Ubuntu官网 https c
  • 计算机网络安全论文选题提纲,计算机网络安全毕业论文提纲

    计算机网络安全毕业论文提纲 想要写好一篇论文 首先需要一份提纲 理清思路 才能帮助你顺利的写好论文 那么 计算机网络安全毕业论文提纲又应该怎样写呢 下面是小编为大家整理的计算机网络安全毕业论文提纲 欢迎参考 题目 主标题 数据结构课程建设
  • 【软件工程】详细设计说明书

    详细设计说明书 1引言 1 1编写目的 说明编写这份详细设计说明书的目的 指出预期的读者 该文档实在概要设计的基础上 进一步的细化系统结构 展示了软件啊结构的图标 物理设计 数据结构设计 及算法设计 详细的介绍了系统各个模块是如何实现的 包
  • 【Go语言核心手册11】context.Context

    往期精选 欢迎转发 如何看待程序员35岁职业危机 Java全套学习资料 14W字 耗时半年整理 我肝了三个月 为你写出了GO核心手册 消息队列 从选型到原理 一文带你全部掌握 肝了一个月的ETCD 从Raft原理到实践 更多 11 1 内容
  • 仓储系统货位优化毕业论文【Flexsim仿真】

    一 内容简介 由堆垛机 货架 输送系统 管理系统 控制系统等组成的传统堆垛式仓储系统因为其成熟的技术和推广方式 高效等特点广泛的应用在物流 车间 制造等行业 但是堆垛机仓库每个巷道都会拥有一台堆垛机 其作业方式受到限制 鲁棒性比较差 一个巷
  • iOS 展示 gif

    gif 图 是多张依次有连续动作的图 顺时间展示的一种动态效果图 有的是均匀时间更换下一张 有的 则不是均匀时间变化 1 那么 对于均匀 时间变化的gif图 比较适合 使用 iOS 系统自带方法 imageView 的动态展示图片的方法就好
  • 30分钟掌握STL系列(三)

    30分钟掌握STL系列 三 使用迭代器编程 下面就讲一讲几种类型迭代器的编程 输入迭代器 输入迭代器是最普通的类型 输入迭代器至少能够使用 和 等 使用 来访问数据 使用 操作来递进迭代器到下一个元素或到达past the end值 为了理
  • 借助Linux中断机制的按键开关功能实现

    AlienTek的IMX6ULL开发板自带了一个按键和一个LED灯 这两个外设分别接在两个不同的GPIO端口 各自独立 我们想把按键作为灯的开关 通过按压按键来控制灯的亮灭 即灯亮时按一下则灯灭 灯灭时按一下则灯亮 这里的 按一下 是指按键