嵌入式Linux中的按键中断控制

前面讨论的都是输出控制,这里结合Pinctrl和GPIO子系统,以野火STM32MP157开发板为例,通过引脚中断方式去获取开发板上的一个按键状态。

先来看一下按键部分的电路原理图,如下所示。

111

从上图中可以看到,按键接在了GPIOB_13端口。按键未按下时端口为低电平,按下时为高电平。

下面给出了本例使用的Pinctrl子系统节点内容。

&pinctrl{
    …………    

            pinctrl_button: buttongrp {
                    pins {
                               pinmux = <STM32_PINMUX('B', 13, GPIO)>;
                               drive-push-pull;
                         };
                };
};

上述中,pinctrl_button: buttongrp即为本次追加的子节点,放置在&pinctrl节点的最末尾。

下面给出本例GPIO子系统的节点内容。

        button_interrupt {
                compatible = "fire,button";
                pinctrl-names = "default";
                pinctrl-0 = <&pinctrl_button>;
                button_gpio = <&gpiob 13 GPIO_ACTIVE_LOW>;
                interrupt-parent = <&gpiob>;
                interrupts = <13 IRQ_TYPE_EDGE_RISING>;
                status = "okay";
            };

把上述节点内容添加到stm32mp157a-basic.dts设备树文件的根节点内,完成后就可以编译设备树了,编译完成后替换开发板上的设备树文件,具体操作可参见“嵌入式Linux中的LED驱动控制(设备树方式)”一文中的相关部分。

接下来看平台驱动程序部分,下面是驱动的全部代码,文件名为btn.c。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/of_irq.h>
#include <linux/device.h>
#include <linux/platform_device.h>
//以下定义一个btn的私有结构体
struct btn_dev
{
    unsigned char button_GPIO_number;  //GPIO引脚编号
    unsigned int  interrupt_number;    //引脚中断编号
    unsigned int  interrupt_type;      //中断类型
    dev_t devid;                       //设备号
    struct cdev btn_cdev;              //定义字符型结构体
    struct device *device_button;      //创建的设备
    struct device_node *button_device_node;//定义按键设备节点结构体
};
static struct class *btn_class;           //定义类结构体
atomic_t button_status = ATOMIC_INIT(0);  //定义整型原子变量,保存按键状态,设置初始值为0
//实现open函数,为file_oprations结构体成员函数
static int btn_open(struct inode *inode, struct file *filp)
{
    struct btn_dev *btn = container_of(inode->i_cdev, struct btn_dev, btn_cdev);
    filp->private_data = btn;//把上面获取的btn结构体存入file的私有变量private_data中,后面的函数会用到
    return 0;
}
//实现read函数,为file_oprations结构体成员函数
static int btn_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret;
    int button = 0;
    button = atomic_read(&button_status);//读取按键状态值
    ret = copy_to_user(buf, &button, sizeof(button));//结果拷贝到用户空间
    if(ret < 0)
    {
      printk("copy_to_user ret\n");
      return -EFAULT;
    }
    atomic_set(&button_status,0);//清零按键状态值
    return 0;
}
//实现release函数,为file_oprations结构体函数
static int btn_release(struct inode *inode, struct file *filp)
{
  return 0;
}
//填充一个file_oprations类型的结构体,名为button_chr_dev_fops,包含上述声明的成员函数
static struct file_operations button_chr_dev_fops = {
    .owner = THIS_MODULE,
    .open = btn_open,
    .read = btn_read,
    .release = btn_release,
};
//实现按键中断服务函数
static irqreturn_t btn_irq_hander(int irq, void *dev_id)
{
    atomic_inc(&button_status);    //按键状态值加1
    return IRQ_HANDLED;
}
/*----------------平台驱动函数集-----------------*/
static int btn_pdrv_probe(struct platform_device *pdev)
{
    int ret;
    struct btn_dev *btn;       //定义一个btn_dev结构体的指针变量btn
    //以下动态申请led结构体大小的内存
    btn = devm_kzalloc(&pdev->dev, sizeof(struct btn_dev), GFP_KERNEL);
    if(!btn)
        return -ENOMEM;
    //获取button_interrupt的设备树节点
    btn->button_device_node = of_find_node_by_path("/button_interrupt");
    if(btn->button_device_node == NULL)
    {
      printk(KERN_ERR "get button_interrupt failed!\n");
      return -EFAULT;
    }
    //获取按键使用的GPIO
    btn->button_GPIO_number = of_get_named_gpio(btn->button_device_node ,"button_gpio", 0);
    if(btn->button_GPIO_number == 0)
    {
      printk("of_get_named_gpio failed!\n");
      return -EFAULT;
    }
    ret = gpio_direction_input(btn->button_GPIO_number);//设置引脚为输入模式
    btn->interrupt_number = irq_of_parse_and_map(btn->button_device_node, 0);//获取中断号
    printk("Interrupt number is:%d\n",btn->interrupt_number);
    btn->interrupt_type = irq_get_trigger_type(btn->interrupt_number);//获取中断类型
    printk("Interrupt type is:%d\n", btn->interrupt_type);
    //申请中断, 后面记得要释放
    ret = request_irq(btn->interrupt_number,btn_irq_hander,btn->interrupt_type,"button_interrupt",btn->device_button);
    if(ret != 0)
    {
      printk("request_irq failed!\n");
      free_irq(btn->interrupt_number, btn->device_button);
      return -EFAULT;
    }
    //注意,上述申请成功之后中断已经开启了,不要再去执行enable_irq(interrupt_number)开启
    //下面申请主设备号
    if (alloc_chrdev_region(&btn->devid, 0, 1, "btn") < 0)
    {
      printk("failed to alloc devid\n");
      return -EFAULT;
    }
    btn->btn_cdev.owner = THIS_MODULE;
    //绑定前面声明的file_oprations类型的结构体到字符设备
    cdev_init(&btn->btn_cdev, &button_chr_dev_fops);
    //填充上面申请到的主设备号到字符设备
    if ( cdev_add(&btn->btn_cdev, btn->devid, 1) < 0)
    {
      printk("failed to add cdev\n");
      return -EFAULT;
    }
    //创建一个设备节点
    device_create(btn_class, NULL, btn->devid, NULL, "button");
    //以下把btn数据信息存入在平台驱动结构体中pdev->dev->driver_data中,后面移除时会用到
    platform_set_drvdata(pdev, btn);
    printk("platform driver probed!\n");
    return 0;
}
//remove函数中,删除设备并释放设备号
static int btn_pdrv_remove(struct platform_device *pdev)
{
    //platform_get_drvdata,获取当前LED灯对应的结构体
    struct btn_dev *btn = platform_get_drvdata(pdev);
    //释放申请的中断
    free_irq(btn->interrupt_number, btn->device_button);
    unregister_chrdev_region(btn->devid, 1);        //释放主设备号
    cdev_del(&btn->btn_cdev);                       //删除字符设备
    device_destroy(btn_class, btn->devid);          //销毁设备节点
    printk("platform driver removed!\n");
    return 0;
}
//填充of_device_id结构体,名为button,用于指明匹配表
static const struct of_device_id button[] = {
    {.compatible = "fire,button"},        //匹配内容
    {/* sentinel */}
};
//以下填充一个platform_driver结构体
struct platform_driver btn_platform_driver = {
    .probe = btn_pdrv_probe,        //指定probe函数成员
    .remove = btn_pdrv_remove,      //指定remove函数成员
    .driver = {
        .name = "button-platform",      //指定设备名称
        .owner = THIS_MODULE,
        .of_match_table = button,        //指定匹配表名称
    }
};
//以下定义模块的入口函数
static int __init btn_driver_init(void)
{
    btn_class = class_create(THIS_MODULE, "my_btn");
    platform_driver_register(&btn_platform_driver); //注册一个platform驱动
    printk("button platform driver initted!\n");
    return 0;
}
//以下定义模块的出口函数
static void __exit btn_driver_exit(void)
{
    platform_driver_unregister(&btn_platform_driver); //释放一个platform驱动
    class_destroy(btn_class);                  //销毁类
    printk("button platform driver exited!\n");
}
module_init(btn_driver_init);
module_exit(btn_driver_exit);
MODULE_LICENSE("GPL");

在上述驱动程序中要注意一点,在通过函数irq_get_trigger_type()来获取设备树中配置的中断类型时,前面获取中断号必须使用函数irq_of_parse_and_map()来实现,不能使用函数gpio_to_irq(),否则获取的中断类型永远为0。配套的Makefile文件内容如下。

KERNEL_DIR=/opt/ebf_linux_kernel_mp157_depth1/build_image/build
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export  ARCH  CROSS_COMPILE
obj-m := btn.o
all:
        $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:
        $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean

把上述驱动程序编译后,把生成的btn.ko文件通过NFS加载到开发板中,可查看到以下信息。

111

接下来是应用程序,文件名为app.c。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    int fd, ret, button_status;
    fd = open("/dev/button", O_RDWR);    //打开设备节点
    if (fd < 0)
    {
      printf("can`t open\n");
      return -1;
    }
    printf("Waitting button down...\n");
    do
    {
       //读取按键状态
      ret = read(fd, &button_status, sizeof(button_status));
      if (ret < 0)
        printf("read file error!\n");
      usleep(1000 * 100); //延时100毫秒
    }while(button_status == 0);
    printf("Button Down !\n");
    ret = close(fd);                    //关闭设备节点
    return 0;
}

在上述应用程序中,由于需要循环读取驱动中的按键状态,所以在循环体内需要加入一定的延时,以免程序无响应。把程序交叉编译后,通过NFS在开发板上运行,如下图所示。

111

可看到程序正在等待按键按下,这时按下开发板上的按键,程序立刻捕获到按键动作,显示按键已按下并结束程序,如下图所示。

111
本例通过捕获按键的的上升沿来设置按键状态,并未获取按键的值。而且只记录按键状态,所以只使用了中断“上半部分”,并未启用“下半部分”。如果在按键按下后要处理的事情较多,需要启用“下半部分”来进行。最后,在实际使用按键中断时,还需要考虑按键消抖,以免按键不灵。 
posted @ 2026-07-15 00:01  fxzq  阅读(19)  评论(0)    收藏  举报