• Linux学习第23天:Linux中断驱动开发(二): 突如其来


    Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长 


    三、驱动程序开发

            采用中断的方式,触发开发板上的KEY0按键。采用定时器消抖。应用程序采集按键值,通过终端打印出来。

    1.修改设备树文件

            在“key”节点下,添加中断相关属性。如下:

    1. 1 key {
    2. 2 #address-cells = <1>;
    3. 3 #size-cells = <1>;
    4. 4 compatible = "atkalpha-key";
    5. 5 pinctrl-names = "default";
    6. 6 pinctrl-0 = <&pinctrl_key>;
    7. 7 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
    8. 8 interrupt-parent = <&gpio1>;/*因为 KEY0 所使用的 GPIO 为GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。*/
    9. 9 interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
    10. /*第一个 cells 的 18 表示 GPIO1 组的 18号 IO。*/
    11. 10 status = "okay";
    12. 11 };

            使用“make dtbs”命令编译设备树,使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。


    2.按键中断驱动程序编写

    1. 37 /* 中断 IO 描述结构体 */
    2. 38 struct irq_keydesc {
    3. 39 int gpio; /* gpio */
    4. 40 int irqnum; /* 中断号 */
    5. 41 unsigned char value; /* 按键对应的键值 */
    6. 42 char name[10]; /* 名字 */
    7. 43 irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
    8. 44 };

            结构体 irq_keydesc 为按键的中断描述结构体, gpio 为按键 GPIO 编号, irqnum
    为按键 IO 对应的中断号, value 为按键对应的键值, name 为按键名字, handler 为按键中断服
    务函数。使用 irq_keydesc 结构体即可描述一个按键中断。

    1. 46 /* imx6uirq 设备结构体 */
    2. 47 struct imx6uirq_dev{
    3. 48 dev_t devid; /* 设备号 */
    4. 49 struct cdev cdev; /* cdev */
    5. 50 struct class *class; /* 类 */
    6. 51 struct device *device; /* 设备 */
    7. 52 int major; /* 主设备号 */
    8. 53 int minor; /* 次设备号 */
    9. 54 struct device_node *nd; /* 设备节点 */
    10. 55 atomic_t keyvalue; /* 有效的按键键值 */
    11. 56 atomic_t releasekey; /* 标记是否完成一次完成的按键*/
    12. 57 struct timer_list timer; /* 定义一个定时器*/
    13. 58 struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
    14. 59 unsigned char curkeynum; /* 当前的按键号 */
    15. 60 };

            结构体 imx6uirq_dev 为本例程设备结构体

    62 struct imx6uirq_dev imx6uirq; /* irq 设备 */

            定义设备结构体变量 imx6uirq。

    1. 64 /* @description : 中断服务函数,开启定时器,延时 10ms,
    2. 65 * 定时器用于按键消抖。
    3. 66 * @param - irq : 中断号
    4. 67 * @param - dev_id : 设备结构。
    5. 68 * @return : 中断执行结果
    6. 69 */
    7. 70 static irqreturn_t key0_handler(int irq, void *dev_id)
    8. 71 {
    9. 72 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
    10. 73
    11. 74 dev->curkeynum = 0;
    12. 75 dev->timer.data = (volatile long)dev_id;
    13. 76 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
    14. 77 return IRQ_RETVAL(IRQ_HANDLED);
    15. 78 }

            key0_handler 函数,按键 KEY0 中断处理函数,参数 dev_id 为设备结构体,也就是 imx6uirq。第 74 行设置 curkeynum=0,表示当前按键为 KEY0,第 76 行使用 mod_timer函数启动定时器,定时器周期为 10ms。

    1. 80 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
    2. 81 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
    3. 82 * @param – arg : 设备结构变量
    4. 83 * @return : 无
    5. 84 */
    6. 85 void timer_function(unsigned long arg)
    7. 86 {
    8. 87 unsigned char value;
    9. 88 unsigned char num;
    10. 89 struct irq_keydesc *keydesc;
    11. 90 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
    12. 91
    13. 92 num = dev->curkeynum;
    14. 93 keydesc = &dev->irqkeydesc[num];
    15. 94
    16. 95 value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
    17. 96 if(value == 0){ /* 按下按键 */
    18. 97 atomic_set(&dev->keyvalue, keydesc->value);
    19. 98 }
    20. 99 else{ /* 按键松开 */
    21. 100 atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
    22. 101 atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
    23. 102 }
    24. 103 }

            timer_function 函数,定时器定时处理函数,参数 arg 是设备结构体,也就是imx6uirq,在此函数中读取按键值。第 95 行通过 gpio_get_value 函数读取按键值。如果为 0 的话就表示按键被按下去了,按下去的话就设置 imx6uirq 结构体的 keyvalue 成员变量为按键的键值,比如 KEY0 按键的话按键值就是 KEY0VALUE=0。如果按键值为 1 的话表示按键被释放了,按键释放了的话就将 imx6uirq 结构体的 keyvalue 成员变量的最高位置 1,表示按键值有效,也就是将 keyvalue 与 0x80 进行或运算,表示按键松开了, 并且设置 imx6uirq 结构体的 releasekey成员变量为 1,表示按键释放,一次有效的按键过程发生。

    1. 106 * @description : 按键 IO 初始化
    2. 107 * @param : 无
    3. 108 * @return : 无
    4. 109 */
    5. 110 static int keyio_init(void)
    6. 111 {
    7. 112 unsigned char i = 0;
    8. 113
    9. 114 int ret = 0;
    10. 115
    11. 116 imx6uirq.nd = of_find_node_by_path("/key");
    12. 117 if (imx6uirq.nd== NULL){
    13. 118 printk("key node not find!\r\n");
    14. 119 return -EINVAL;
    15. 120 }
    16. 121
    17. 122 /* 提取 GPIO */
    18. 123 for (i = 0; i < KEY_NUM; i++) {
    19. 124 imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,
    20. "key-gpio", i);
    21. 125 if (imx6uirq.irqkeydesc[i].gpio < 0) {
    22. 126 printk("can't get key%d\r\n", i);
    23. 127 }
    24. 128 }
    25. 129
    26. 130 /* 初始化 key 所使用的 IO,并且设置成中断模式 */
    27. 131 for (i = 0; i < KEY_NUM; i++) {
    28. 132 memset(imx6uirq.irqkeydesc[i].name, 0,
    29. sizeof(imx6uirq.irqkeydesc[i].name));
    30. 133 sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
    31. 134 gpio_request(imx6uirq.irqkeydesc[i].gpio,
    32. imx6uirq.irqkeydesc[i].name);
    33. 135 gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
    34. 136 imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(
    35. imx6uirq.nd, i);
    36. 137 #if 0
    37. 138 imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(
    38. imx6uirq.irqkeydesc[i].gpio);
    39. 139 #endif
    40. 140 printk("key%d:gpio=%d, irqnum=%d\r\n",i,
    41. imx6uirq.irqkeydesc[i].gpio,
    42. 141 imx6uirq.irqkeydesc[i].irqnum);
    43. 142 }
    44. 143 /* 申请中断 */
    45. 144 imx6uirq.irqkeydesc[0].handler = key0_handler;
    46. 145 imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    47. 146
    48. 147 for (i = 0; i < KEY_NUM; i++) {
    49. 148 ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
    50. imx6uirq.irqkeydesc[i].handler,
    51. IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
    52. imx6uirq.irqkeydesc[i].name, &imx6uirq);
    53. 149 if(ret < 0){
    54. 150 printk("irq %d request failed!\r\n",
    55. imx6uirq.irqkeydesc[i].irqnum);
    56. 151 return -EFAULT;
    57. 152 }
    58. 153 }
    59. 154
    60. 155 /* 创建定时器 */
    61. 156 init_timer(&imx6uirq.timer);
    62. 157 imx6uirq.timer.function = timer_function;
    63. 158 return 0;
    64. 159 }

             keyio_init 函数,按键 IO 初始化函数,在驱动入口函数里面会调用 keyio_init来初始化按键 IO。第 131~142 行轮流初始化所有的按键,包括申请 IO、设置 IO 为输入模式、从设备树中获取 IO 的中断号等等。第 136 行通过 irq_of_parse_and_map 函数从设备树中获取按键 IO 对应的中断号。也可以使用 gpio_to_irq 函数将某个 IO 设置为中断状态,并且返回其中断号。第 144 和 145 行设置 KEY0 按键对应的按键中断处理函数为 key0_handler、 KEY0 的按键为 KEY0VALUE。第 147~153 行轮流调用 request_irq 函数申请中断号,设置中断触发模式为IRQF_TRIGGER_FALLING 和 IRQF_TRIGGER_RISING,也就是上升沿和下降沿都可以触发中
    断。最后,第 156 行初始化定时器,并且设置定时器的定时处理函数。

    1. /*
    2. 162 * @description : 打开设备
    3. 163 * @param – inode : 传递给驱动的 inode
    4. 164 * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
    5. 165 * 一般在 open 的时候将 private_data 指向设备结构体。
    6. 166 * @return : 0 成功;其他 失败
    7. 167 */
    8. 168 static int imx6uirq_open(struct inode *inode, struct file *filp)
    9. 169 {
    10. 170 filp->private_data = &imx6uirq; /* 设置私有数据 */
    11. 171 return 0;
    12. 172 }

    1. 174 /*
    2. 175 * @description : 从设备读取数据
    3. 176 * @param – filp : 要打开的设备文件(文件描述符)
    4. 177 * @param – buf : 返回给用户空间的数据缓冲区
    5. 178 * @param - cnt : 要读取的数据长度
    6. 179 * @param – offt : 相对于文件首地址的偏移
    7. 180 * @return : 读取的字节数,如果为负值,表示读取失败
    8. 181 */
    9. 182 static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
    10. size_t cnt, loff_t *offt)
    11. 183 {
    12. 184 int ret = 0;
    13. 185 unsigned char keyvalue = 0;
    14. 186 unsigned char releasekey = 0;
    15. 187 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)
    16. filp->private_data;
    17. 188
    18. 189 keyvalue = atomic_read(&dev->keyvalue);
    19. 190 releasekey = atomic_read(&dev->releasekey);
    20. 191
    21. 192 if (releasekey) { /* 有按键按下 */
    22. 193 if (keyvalue & 0x80) {
    23. 194 keyvalue &= ~0x80;
    24. 195 ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
    25. 196 } else {
    26. 197 goto data_error;
    27. 198 }
    28. 199 atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    29. 200 } else {
    30. 201 goto data_error;
    31. 202 }
    32. 203 return 0;
    33. 204
    34. 205 data_error:
    35. 206 return -EINVAL;
    36. 207 }

            imx6uirq_read 函数,对应应用程序的 read 函数。此函数向应用程序返回按键值。首先判断 imx6uirq 结构体的 releasekey 成员变量值是否为 1,如果为 1 的话表示有一次有效按键发生,否则的话就直接返回-EINVAL。当有按键事件发生的话就要向应用程序发送按键值,首先判断按键值的最高位是否为 1,如果为 1 的话就表示按键值有效。如果按键值有效的话就将最高位清除,得到真实的按键值,然后通过 copy_to_user 函数返回给应用程序。向应用程序发送按键值完成以后就将 imx6uirq 结构体的 releasekey 成员变量清零,准备下一次按键操作。

    1. 209 /* 设备操作函数 */
    2. 210 static struct file_operations imx6uirq_fops = {
    3. 211 .owner = THIS_MODULE,
    4. 212 .open = imx6uirq_open,
    5. 213 .read = imx6uirq_read,
    6. 214 };

            按键中断驱动操作函数集 imx6uirq_fops。

    1. 216 /*
    2. 217 * @description : 驱动入口函数
    3. 218 * @param : 无
    4. 219 * @return : 无
    5. 220 */
    6. 221 static int __init imx6uirq_init(void)
    7. 222 {
    8. 223 /* 1、构建设备号 */
    9. 224 if (imx6uirq.major) {
    10. 225 imx6uirq.devid = MKDEV(imx6uirq.major, 0);
    11. 226 register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,
    12. IMX6UIRQ_NAME);
    13. 227 } else {
    14. 228 alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,
    15. IMX6UIRQ_NAME);
    16. 229 imx6uirq.major = MAJOR(imx6uirq.devid);
    17. 230 imx6uirq.minor = MINOR(imx6uirq.devid);
    18. 231 }
    19. 232
    20. 233 /* 2、注册字符设备 */
    21. 234 cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
    22. 235 cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    23. 236
    24. 237 /* 3、创建类 */
    25. 238 imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    26. 239 if (IS_ERR(imx6uirq.class)) {
    27. 240 return PTR_ERR(imx6uirq.class);
    28. 241 }
    29. 242
    30. 243 /* 4、创建设备 */
    31. 244 imx6uirq.device = device_create(imx6uirq.class, NULL,
    32. imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    33. 245 if (IS_ERR(imx6uirq.device)) {
    34. 246 return PTR_ERR(imx6uirq.device);
    35. 247 }
    36. 248
    37. 249 /* 5、 初始化按键 */
    38. 250 atomic_set(&imx6uirq.keyvalue, INVAKEY);
    39. 251 atomic_set(&imx6uirq.releasekey, 0);
    40. 252 keyio_init();
    41. 253 return 0;
    42. 254 }

            驱动入口函数,第 250 和 251 行分别初始化 imx6uirq 结构体中的原子变量keyvalue 和releasekey,第 252 行调用 keyio_init 函数初始化按键所使用的 IO。
     

    1. 256 /*
    2. 257 * @description : 驱动出口函数
    3. 258 * @param : 无
    4. 259 * @return : 无
    5. 260 */
    6. 261 static void __exit imx6uirq_exit(void)
    7. 262 {
    8. 263 unsigned int i = 0;
    9. 264 /* 删除定时器 */
    10. 265 del_timer_sync(&imx6uirq.timer);
    11. 266
    12. 267 /* 释放中断 */
    13. 268 for (i = 0; i < KEY_NUM; i++) {
    14. 269 free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
    15. 270 }
    16. 271 cdev_del(&imx6uirq.cdev);
    17. 272 unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
    18. 273 device_destroy(imx6uirq.class, imx6uirq.devid);
    19. 274 class_destroy(imx6uirq.class);
    20. 275 }
    21. 276
    22. 277 module_init(imx6uirq_init);
    23. 278 module_exit(imx6uirq_exit);
    24. 279 MODULE_LICENSE("GPL");
    25. 280 MODULE_AUTHOR("zuozhongkai");

            驱动出口函数,第 265 行调用 del_timer_sync 函数删除定时器,第 268~070行轮流释放申请的所有按键中断。

    3.编写测试APP

            不断的读取/dev/imx6uirq 文件来获取按键值,当按键按下以后就会将获取到的按键值输出在终端上.

    1. 21 /*
    2. 22 * @description : main 主程序
    3. 23 * @param - argc : argv 数组元素个数
    4. 24 * @param - argv : 具体参数
    5. 25 * @return : 0 成功;其他 失败
    6. 26 */
    7. 27 int main(int argc, char *argv[])
    8. 28 {
    9. 29 int fd;
    10. 30 int ret = 0;
    11. 31 char *filename;
    12. 32 unsigned char data;
    13. 33 if (argc != 2) {
    14. 34 printf("Error Usage!\r\n");
    15. 35 return -1;
    16. 36 }
    17. 37
    18. 38 filename = argv[1];
    19. 39 fd = open(filename, O_RDWR);
    20. 40 if (fd < 0) {
    21. 41 printf("Can't open file %s\r\n", filename);
    22. 42 return -1;
    23. 43 }
    24. 44
    25. 45 while (1) {
    26. 46 ret = read(fd, &data, sizeof(data));
    27. 47 if (ret < 0) { /* 数据读取错误或者无效 */
    28. 48
    29. 49 } else { /* 数据读取正确 */
    30. 50 if (data) /* 读取到数据 */
    31. 51 printf("key value = %#X\r\n", data);
    32. 52 }
    33. 53 }
    34. 54 close(fd);
    35. 55 return ret;
    36. 56 }

            第 45~53 行的 while 循环用于不断的读取按键值,如果读取到有效的按键值就将其输出到
    终端上。

    四、运行测试

    1.编译驱动程序和测试APP

    1. 1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek
    2. ......
    3. 4 obj-m := imx6uirq.o
    4. ......
    5. 11 clean:
    6. 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

    输入如下命令编译出驱动模块文件:
    make -j32
    编译成功以后就会生成一个名为“ imx6uirq.ko”的驱动模块文件。

    输入如下命令编译测试 imx6uirqApp.c 这个测试程序:
    arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp
    编译成功以后就会生成 imx6uirqApp 这个应用程序。
     

    2.运行测试

    1. depmod //第一次加载驱动的时候需要运行此命令
    2. modprobe imx6uirq.ko //加载驱动

    用如下命令来测试中断:

    ./imx6uirqApp /dev/imx6uirq


    按下开发板上的 KEY0 键,终端就会输出按键值。

    如果要卸载驱动的话输入如下命令即可:
     

    rmmod imx6uirq.ko

    五、总结

            本节笔记主要学习了中断驱动开发的下半部分,主要包括中断驱动开发及测试。其中最主要的内容为中断驱动开发相关内容。


    本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

  • 相关阅读:
    kibana设置中文
    Elixir学习笔记——输入输出和文件系统
    【JavaSE】类和对象——上
    python每日一题【剑指 Offer 12. 矩阵中的路径】
    package.json与package-lock.json
    关于Altera JTAG Sever服务的问题
    11-20==c++知识点
    二叉树——遍历:按层次非递归遍历、先序非递归、先中后序递归遍历二叉树的链表结构【C语言,数据结构】(内含源代码)
    SeaTunnel 2.3.4 Cluster in K8S
    中国节日主题网站设计 红色建军节HTML+CSS 红色中国文化主题网站设计 HTML学生作业网页
  • 原文地址:https://blog.csdn.net/jiage987450/article/details/134063095