基于树莓派4B的Linux驱动------按键中断
本人也是接触Linux不久,可能有些问题也没考虑到,以下仅是个人观点,欢迎留言,共同进步,话不多说,直接步入正题。
一、实验说明
本次实验采用设备树pinctrl方式编程
开发板基于树莓派4B
linux内核版本:linux-rpi-5.15.y
开发平台:ubuntu交叉编译
按键按下为高电平
二、修改设备树文件
为了方便,我这里是直接在根节点上添加了以下内容,我这里修改的是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就可以了
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>
struct gpio_key{
int gpio;
int irq;
enum of_gpio_flags flag;
};
static struct gpio_key *gpio_keys;
static irqreturn_t gpio_key_irq_handle(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
printk("key %d val %d\n", irq, gpio_get_value(gpio_key->gpio));
return IRQ_HANDLED;
}
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;
}
}
return 0;
fail_request:
fail_irq:
fail_flags:
fail_kzalloc:
kfree(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(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);
return 0;
}
static __exit void test_gpio_exit(void)
{
printk("test_gpio_exit\n");
platform_driver_unregister(&test_gpio_drv);
}
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命令查看内核日志
按下按键,查看内核日志,打印如下
key 80 val 1
key 80 val 0
好的,实验完成