资讯详情

基于树莓派4B的Linux驱动------按键中断(消抖、下半部处理)

基于树莓派4B的Linux驱动-按钮中断(抖动,下半部分处理)

我也接触过Linux不久,可能没有考虑到一些问题,以下只是个人观点,欢迎留言,共同进步,不多说,直接进入主题。

一、实验说明

本实验采用设备树pinctrl方式编程 以树莓派4为基础的开发板B linux内核版本:linux-rpi-5.15.y 开发平台:ubuntu交叉编译 按键为高电平 本次实验继续上一次《基于树莓派4》B的Linux驱动-按钮中断

二、修改设备树文件

为了方便,我直接在根节点添加了以下内容。我在这里修改的是arch/arm/boot/bts/bcm2711-rpi-4-b.dts文件,在文件下添加以下内容,具体一些pinctrl设备树语法可参考内核说明文档,在Documentation/devicetree/bindings/pinctrl目录下的文档,如brcm,bcm2835-gpio.txt文件

bcm2711-rpi-4-b.dts根节点添加以下内容

keytest{ 
          #address-cells = <1>;     #size-cells = <1>;  compatible = "keytest";  gpios = <&gpio 19 GPIO_ACTIVE_HIGH>;  pinctrl-names = "default";  pinctrl-0 = <&key1_test>;  status = "okay"; }; 

bcm2711-rpi-4-b.dts添加以下附加内容

&gpio { 
          key1_test: key1_test { 
           brcm,pins = <19>;   brcm,function = <0>;   brcm,pull = <2>;  }; }; 

在属性brcm,pins中,0表示GPIO0,1表示GPIO1,2表示GPIO2 ··· 在属性brcm,function中,0表示in,1表示out,2表示ALT0,3表示ALT1 ··· 在属性brcm,pull 中,0表示none, 1表示up, 2表示down 可以参考内核文档Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt ALT0,ALT1,ALT3,ALT4,ALT5可以去看bcm2711芯片手册,在GPIO章节,手册可以在树莓派官网下载

三、编写驱动程序

驱动程序可以参考核心驱动程序,如drivers/input/keyboard/gpio_keys.c文件 以下驱动程序可以与多个按钮兼容,但我只在设备树上设置了一个按钮,所以如果你想使用多个按钮,你只能修改设备树 of_gpio_count 该节点由设备树描述的函数获取GPIO个数 kzalloc 申请内存的函数 of_get_gpio_flags 函数用于获取GPIO编号 gpio_to_irq 获取中断编号函数 request_irq 函数用于申请中断。第一个参数是中断编号,第二个参数是中断服务函数的指针,第三个参数是触发模式,第四个参数是中断名称,第五个参数是传输到一个地址。这个地址将传输到中断服务函数,然后中断服务函数可以使用一些变量值。当然,如果你不使用它,你不能传输它。NULL就可以了 因为中断注重快进快出,为了避免中断处理时间过长,所以linux提出中断上半部分和中断下半部分,简单地把紧急情况放在上半部分,不那么紧急情况放在下半部分,linux在处理上半部分时,不允许其他中断,即没有中断优先级linux在处理下半部分时,允许其他中断,所以,如果有两个中断,那么linux我们将首先处理先到达的中断。如果第一个中断执行时间长,第二个中断需要很长时间才能执行。我们不想看到这种情况,所以我们可以把执行时间长的程序放在下半部分,这样当第二个中断来临时,我们就可以打断当前的中断,这样每个中断都可以快速执行,对于下半部分,被打断后,linux当所有上半部分完成后,将标记这部分,linux将执行下半部分,将有相应的单元找到以前的标记,然后执行,执行后删除标记,简单地说,上半部分是中断服务函数,中断服务函数我们可以调用处理下半部分的函数,我只是粗略地说,具体可以上网找一些linux关于中断处理的书,这部分我就到这里。 还有按钮抖动,我们知道,按键会有硬件抖动,所以会让我们只按一次,系统会识别很多次,这种情况我们可以在电路中添加电容器,使用电容器充放电可以解决这个问题,所以知道电容器会提高硬件成本,电容器坏没用,所以还有其他方法吗?学过51或stm32单片机的朋友应该知道,对于51或者stm32单片机扫描读取按钮的处理可以添加软件延迟。目的是等到按钮稳定下来,我们就读取按钮值,也就是忽略按钮后的开头和结尾部分。linux呢?也差不多。目的是忽略按钮按下后的开头和结尾。当按钮稳定时,我们正在读取按钮值,因此我们可以添加定时器,即当按钮中断时,我们可以在中断服务函数中添加定时器,使定时器计时一段时间我才去读取,这样就可以实现了。但是,如果直接把定时的过程放到中断服务函数里面,这样是不好的,因为中断讲究的是快进快出,放到中断服务函数里面会占用中断的资源,别的中断就等你执行完了他才可以执行,所以我们应该把定时的过程放到中断下半部。 梳理一下思路,首先按键按下,按下就可以产生一个按键中断,产生中断就会进入中断服务函数,中断服务函数调用中断下半部处理函数,中断下半部处理函数再调用定时器处理函数,然后我们就可以把自己想要实现的功能写在定时器处理函数里面。以下是我写的程序,可以参考一下。

testkey.c

#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/jiffies.h>


struct gpio_key{ 
        
	int gpio;
	int irq;
	enum of_gpio_flags flag;
};

struct pi4irq_dev{ 
        
	struct tasklet_struct tasklet;        /* 中断下半部 */
	struct timer_list timer;              /* 定时器 */
    int timePeriod;                       /* 定时周期,单位为ms */
};


static struct gpio_key *gpio_keys;
static struct pi4irq_dev pi4irq;

/* 中断上半部,中断服务函数 */
static irqreturn_t gpio_key_irq_handle(int irq, void *dev_id)
{ 
        
	/* 调度tasklet */
    tasklet_schedule(&pi4irq.tasklet);

	return IRQ_HANDLED;
}

/* 中断下半部tasklet */
static void key_tasklet(unsigned long data)
{ 
        
	struct pi4irq_dev *dev = &pi4irq;
	int timerPeriod;

	timerPeriod = dev->timePeriod;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerPeriod));
}


/* 定时器 */
static void timer_function(struct timer_list* time)
{ 
        
	struct gpio_key *dev = gpio_keys;
	int value;

	value = gpio_get_value(dev->gpio);
	if(value == 0)
	{ 
        
		printk("中断号:%d 松开\n", dev->irq);
	}
	else
	{ 
        
		printk("中断号:%d 按下\n", dev->irq);
	}
}


static int chip_demo_gpio_probe(struct platform_device *pdev)
{ 
        
	struct device_node *node = pdev->dev.of_node;
	int count, i;
	int ret = 0;

	count = of_gpio_count(node);
	if(count <= 0)
	{ 
        
		ret = -EINVAL;
		goto fail_count;
	}

	gpio_keys = kzalloc(count * sizeof(struct gpio_key), GFP_KERNEL);
	if(!gpio_keys)
	{ 
        
		printk("内存分配失败\n");
		ret = -ENOMEM;
		goto fail_kzalloc;
	}
	for(i = 0; i < count; i++)
	{ 
        
		gpio_keys[i].gpio = of_get_gpio_flags(node, i, &gpio_keys[i].flag);
		if (!gpio_is_valid(gpio_keys[i].gpio))
		{ 
        
			printk("设备树获取失败 key: %d\n", i);
			ret =  -EINVAL;
			goto fail_flags;
		}
		gpio_keys[i].irq = gpio_to_irq(gpio_keys[i].gpio);
		if(gpio_keys[i].irq < 0)
		{ 
        
			printk("中断号获取失败 key: %d\n", i);
			ret = gpio_keys[i].irq;
			goto fail_irq;
		}
		ret = request_irq(gpio_keys[i].irq, gpio_key_irq_handle, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "test_gpio_key", &gpio_keys[i]);
		if (ret != 0)
		{ 
        
			ret = -1;
			printk("无法请求 gpio_keys irq\n");
			free_irq(gpio_keys[i].irq, &gpio_keys[i]);
			goto fail_request;
		}
	}
	tasklet_init(&pi4irq.tasklet, key_tasklet, (unsigned long)gpio_keys);

	return 0;

fail_request:
fail_irq:
fail_flags:
fail_kzalloc:
	kfree((struct gpio_key*)gpio_keys);
fail_count:
	return ret;
}

static const struct of_device_id key_gpios[] = { 
        
	{ 
        .compatible = "keytest"},
	{ 
        },
};

static int chip_demo_gpio_remove(struct platform_device *pdev)
{ 
        
	struct device_node *node = pdev->dev.of_node;
	int count, i;

	count = of_gpio_count(node);
	for(i = 0; i < count; i++)
	{ 
        
		free_irq(gpio_keys[i].irq, &gpio_keys[i]);
	}
	kfree((struct gpio_key*)gpio_keys);

	return 0;
}


static struct platform_driver test_gpio_drv = { 
        
	.probe = chip_demo_gpio_probe,
	.remove = chip_demo_gpio_remove,
	.driver = { 
        
		.name = "keytest",
		.of_match_table = key_gpios,
	},
};

static __init int test_gpio_init(void)
{ 
        
	printk("test_gpio_init\n");
	platform_driver_register(&test_gpio_drv);

	/* 初始化定时器 */
    timer_setup(&pi4irq.timer, timer_function, 0);
    pi4irq.timePeriod = 20;
    pi4irq.timer.expires = jiffies + msecs_to_jiffies(pi4irq.timePeriod);

	return 0;
}

static __exit void test_gpio_exit(void)
{ 
        
	printk("test_gpio_exit\n");
	platform_driver_unregister(&test_gpio_drv);

	/* 删除 timer */
    del_timer_sync(&pi4irq.timer);
}

module_init(test_gpio_init);
module_exit(test_gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");

四、编写MakeFile程序

KERNELDIR := /home/pi/linux/pi4_kernel/linux-rpi-5.15.y

CURRENT_PATH := $(shell pwd)

obj-m := testkey.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

五、编译测试

make 编译驱动程序,并把编译好的.ko文件拷贝到树莓派 到内核目录 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs 编译设备树,把编译好的设备树文件拷贝到树莓派boot目录下,重启树莓派,执行 sudo insmod testkey.ko 加载驱动程序 可以使用dmesg命令查看内核日志 按下按键,查看内核日志,打印如下 中断号:80 按下 中断号:80 松开 好的,实验完成

标签: bcm电容

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台