• Linux驱动开发——(六)按键中断实验


    目录

    一、简介

    二、修改设备树

    2.1 添加pinctrl节点

    2.2 添加KEY设备节点 

    2.3 检查是否被其他外设使用 

    三、代码

    3.1 驱动代码

    3.2 测试代码

    3.3 平台测试


    一、简介

    以I.MX6U-MINI为例,实现KEY0按下后,设备识别到并将数据发送到平台。


    二、修改设备树

    2.1 添加pinctrl节点

    首先查看KEY0使用的PIN,打开原理图,可以找到:

    KEY0使用了UART1_CTS_B这个PIN,打开imx6ul-pinfunc.h文件,可以找到:

    #define MX6UL_PAD_UART1_CTS_B__GPIO1_IO18                         0x008C 0x0318 0x0000 0x5 0x0

    打开设备树dts文件,在iomuxc节点的imx6ul-evk子节点下创建一个名为“pinctrl_key”的子节点:

    1. pinctrl_key: keygrp {
    2. fsl,pins = <
    3. MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
    4. >;
    5. };

    第3行,将UART1_CTS_B这个PIN复用为GPIO1_IO03,电气属性值为0XF0B0。

    对于其电气属性配置,可以查看参考手册以参考配置:

    2.2 添加KEY设备节点 

    在根节点“/”下创建KEY节点,节点名为“key”:

    1. key {
    2. #address-cells = <1>;
    3. #size-cells = <1>;
    4. compatible = "atkalpha-key";
    5. pinctrl-names = "default";
    6. pinctrl-0 = <&pinctrl_key>;
    7. key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
    8. interrupt-parent = <&gpio1>;
    9. interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
    10. status = "okay";
    11. };

    第6行,pinctrl-0属性设置KEY所使用的PIN对应的pinctrl节点。

    第7行,key-gpio属性指定了KEY所使用的GPIO。

    第8行,设置interrupt-parent属性值为“gpio1”,因为KEY0所使用的GPIO为GPIO1_IO18,也就是设置KEY0的GPIO中断控制器为gpio1。

    第9行,设置interrupts属性,也就是设置中断源,第一个cells的18表示GPIO1组的18号 IO。IRQ_TYPE_EDGE_BOTH定义在文件include/linux/irq.h中:

    1. IRQ_TYPE_NONE = 0x00000000,
    2. IRQ_TYPE_EDGE_RISING = 0x00000001,
    3. IRQ_TYPE_EDGE_FALLING = 0x00000002,
    4. IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),

    IRQ_TYPE_EDGE_BOTH表示上升沿和下降沿同时有效,即KEY0按下和释放都会触发中断。

    2.3 检查是否被其他外设使用 

    对于PIN:检查UART1_CTS_B是否有被其他的pinctrl节点使用,如果有的话就要屏蔽掉;

    对于GPIO:检查GPIO1_IO18是否有被其他外设使用,如果有的话也要屏蔽掉。

    以上完成后用“make dtbs”重新编译设备树,然后使用新编译出来的设备树dtb文件启动Linux系统。启动成功以后进入“/proc/device-tree”目录中,此时“key”节点应当存在!


    三、代码

    3.1 驱动代码

    采用中断识别按钮按下,中断服务函数内开启10ms的定时器进行消抖,在定时器服务函数再次读取按键值,如果按键还是处于按下状态就表示按键有效。

    首先编写好头文件、宏和数据结构:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. #include
    18. #include
    19. #include
    20. #include
    21. #define IMX6UIRQ_CNT 1 /* 设备号个数 */
    22. #define IMX6UIRQ_NAME "imx6uirq" /* 名字 */
    23. #define KEY0VALUE 0X01 /* KEY0按键值 */
    24. #define INVAKEY 0XFF /* 无效的按键值 */
    25. #define KEY_NUM 1 /* 按键数量 */
    26. /* 中断IO描述结构体 */
    27. struct irq_keydesc {
    28. int gpio; /* gpio */
    29. int irqnum; /* 中断号 */
    30. unsigned char value; /* 按键对应的键值 */
    31. char name[10]; /* 名字 */
    32. irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
    33. };
    34. /* imx6uirq设备结构体 */
    35. struct imx6uirq_dev{
    36. dev_t devid; /* 设备号 */
    37. struct cdev cdev; /* cdev */
    38. struct class *class; /* 类 */
    39. struct device *device; /* 设备 */
    40. int major; /* 主设备号 */
    41. int minor; /* 次设备号 */
    42. struct device_node *nd; /* 设备节点 */
    43. atomic_t keyvalue; /* 有效的按键键值 */
    44. atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */
    45. struct timer_list timer;/* 定义一个定时器*/
    46. struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
    47. unsigned char curkeynum; /* 当前的按键号 */
    48. };
    49. struct imx6uirq_dev imx6uirq; /* irq设备 */

    然后完善驱动入口/出口函数——申请设备号注册设备创建类创建类里的设备初始化

    1. static int __init imx6uirq_init(void)
    2. {
    3. /* 1、构建设备号 */
    4. if (imx6uirq.major) {
    5. imx6uirq.devid = MKDEV(imx6uirq.major, 0);
    6. register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    7. } else {
    8. alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    9. imx6uirq.major = MAJOR(imx6uirq.devid);
    10. imx6uirq.minor = MINOR(imx6uirq.devid);
    11. }
    12. /* 2、注册字符设备 */
    13. cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
    14. cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    15. /* 3、创建类 */
    16. imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    17. if (IS_ERR(imx6uirq.class)) {
    18. return PTR_ERR(imx6uirq.class);
    19. }
    20. /* 4、创建设备 */
    21. imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    22. if (IS_ERR(imx6uirq.device)) {
    23. return PTR_ERR(imx6uirq.device);
    24. }
    25. /* 5、初始化按键 */
    26. atomic_set(&imx6uirq.keyvalue, INVAKEY);
    27. atomic_set(&imx6uirq.releasekey, 0);
    28. keyio_init();
    29. return 0;
    30. }
    31. module_init(imx6uirq_init);
    32. MODULE_LICENSE("GPL");

    初始化——获取设备节点GPIO编号初始化GPIO

    初始化按键——获取中断号申请中断

    1. static int keyio_init(void)
    2. {
    3. unsigned char i = 0;
    4. int ret = 0;
    5. imx6uirq.nd = of_find_node_by_path("/key");
    6. if (imx6uirq.nd== NULL){
    7. printk("key node not find!\r\n");
    8. return -EINVAL;
    9. }
    10. /* 提取GPIO */
    11. for (i = 0; i < KEY_NUM; i++) {
    12. imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
    13. if (imx6uirq.irqkeydesc[i].gpio < 0) {
    14. printk("can't get key%d\r\n", i);
    15. }
    16. }
    17. /* 初始化key所使用的IO,并且设置成中断模式 */
    18. for (i = 0; i < KEY_NUM; i++) {
    19. memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));
    20. /* 缓冲区清零 */
    21. sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */
    22. gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
    23. gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
    24. imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
    25. #if 0
    26. imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
    27. #endif
    28. printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio,
    29. imx6uirq.irqkeydesc[i].irqnum);
    30. }
    31. /* 申请中断 */
    32. imx6uirq.irqkeydesc[0].handler = key0_handler;
    33. imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    34. for (i = 0; i < KEY_NUM; i++) {
    35. ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
    36. IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
    37. imx6uirq.irqkeydesc[i].name, &imx6uirq);
    38. if(ret < 0){
    39. printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
    40. return -EFAULT;
    41. }
    42. }
    43. /* 创建定时器 */
    44. init_timer(&imx6uirq.timer);
    45. imx6uirq.timer.function = timer_function;
    46. return 0;
    47. }

    中断初始化完了就编写中断服务函数

    1. static irqreturn_t key0_handler(int irq, void *dev_id)
    2. {
    3. struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
    4. dev->curkeynum = 0;
    5. dev->timer.data = (volatile long)dev_id;
    6. mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
    7. return IRQ_RETVAL(IRQ_HANDLED);
    8. }

    在该函数里开启了10ms的定时,再编写定时器服务函数

    1. void timer_function(unsigned long arg)
    2. {
    3. unsigned char value;
    4. unsigned char num;
    5. struct irq_keydesc *keydesc;
    6. struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
    7. num = dev->curkeynum;
    8. keydesc = &dev->irqkeydesc[num];
    9. value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
    10. if(value == 0){ /* 按下按键 */
    11. atomic_set(&dev->keyvalue, keydesc->value);
    12. }
    13. else{ /* 按键松开 */
    14. atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
    15. atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
    16. }
    17. }

    完善设备操作函数

    1. static int imx6uirq_open(struct inode *inode, struct file *filp)
    2. {
    3. filp->private_data = &imx6uirq; /* 设置私有数据 */
    4. return 0;
    5. }
    6. static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
    7. {
    8. int ret = 0;
    9. unsigned char keyvalue = 0;
    10. unsigned char releasekey = 0;
    11. struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
    12. keyvalue = atomic_read(&dev->keyvalue);
    13. releasekey = atomic_read(&dev->releasekey);
    14. if (releasekey) { /* 有按键按下 */
    15. if (keyvalue & 0x80) {
    16. keyvalue &= ~0x80;
    17. ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
    18. } else {
    19. goto data_error;
    20. }
    21. atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
    22. } else {
    23. goto data_error;
    24. }
    25. return 0;
    26. data_error:
    27. return -EINVAL;
    28. }
    29. static struct file_operations imx6uirq_fops = {
    30. .owner = THIS_MODULE,
    31. .open = imx6uirq_open,
    32. .read = imx6uirq_read,
    33. };

    3.2 测试代码

    读取驱动代码发送过来的信息:

    1. #include "stdio.h"
    2. #include "unistd.h"
    3. #include "sys/types.h"
    4. #include "sys/stat.h"
    5. #include "fcntl.h"
    6. #include "stdlib.h"
    7. #include "string.h"
    8. #include "linux/ioctl.h"
    9. int main(int argc, char *argv[])
    10. {
    11. int fd;
    12. int ret = 0;
    13. char *filename;
    14. unsigned char data;
    15. if (argc != 2) {
    16. printf("Error Usage!\r\n");
    17. return -1;
    18. }
    19. filename = argv[1];
    20. fd = open(filename, O_RDWR);
    21. if (fd < 0) {
    22. printf("Can't open file %s\r\n", filename);
    23. return -1;
    24. }
    25. while (1) {
    26. ret = read(fd, &data, sizeof(data));
    27. if (ret < 0) { /* 数据读取错误或者无效 */
    28. } else { /* 数据读取正确 */
    29. if (data) /* 读取到数据 */
    30. printf("key value = %#X\r\n", data);
    31. }
    32. }
    33. close(fd);
    34. return ret;
    35. }

    3.3 平台测试

    insmod挂载模块:

    使用测试代码打开设备后,每当KEY0按下,平台打印正确信息:

  • 相关阅读:
    聚焦AIGC落地,八仙过海,谁更神通?
    前端开发中遇到的复杂数据求和问题
    基于springboot的少儿识字系统
    Google Earth Engine(GEE)扩展—— geetool中的Widgets小部件(geetools:widgets)
    java 网络编程 总结篇
    阿里首席架构师解读:Spring Cloud 与 Docker 微服务架构实战
    How to install mongodb-7.0 as systemd service with podman
    数据结构与算法之美学习笔记:24 | 二叉树基础(下):有了如此高效的散列表,为什么还需要二叉树?
    Linux Shell 编程基础语法汇总
    中国竟然也有这种级别的软件?万万没想到!
  • 原文地址:https://blog.csdn.net/weixin_61979510/article/details/138187866