Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
采用中断的方式,触发开发板上的KEY0按键。采用定时器消抖。应用程序采集按键值,通过终端打印出来。
在“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>;/*因为 KEY0 所使用的 GPIO 为GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。*/
- 9 interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
- /*第一个 cells 的 18 表示 GPIO1 组的 18号 IO。*/
- 10 status = "okay";
- 11 };
使用“make dtbs”命令编译设备树,使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。
- 37 /* 中断 IO 描述结构体 */
- 38 struct irq_keydesc {
- 39 int gpio; /* gpio */
- 40 int irqnum; /* 中断号 */
- 41 unsigned char value; /* 按键对应的键值 */
- 42 char name[10]; /* 名字 */
- 43 irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
- 44 };
结构体 irq_keydesc 为按键的中断描述结构体, gpio 为按键 GPIO 编号, irqnum
为按键 IO 对应的中断号, value 为按键对应的键值, name 为按键名字, handler 为按键中断服
务函数。使用 irq_keydesc 结构体即可描述一个按键中断。
- 46 /* imx6uirq 设备结构体 */
- 47 struct imx6uirq_dev{
- 48 dev_t devid; /* 设备号 */
- 49 struct cdev cdev; /* cdev */
- 50 struct class *class; /* 类 */
- 51 struct device *device; /* 设备 */
- 52 int major; /* 主设备号 */
- 53 int minor; /* 次设备号 */
- 54 struct device_node *nd; /* 设备节点 */
- 55 atomic_t keyvalue; /* 有效的按键键值 */
- 56 atomic_t releasekey; /* 标记是否完成一次完成的按键*/
- 57 struct timer_list timer; /* 定义一个定时器*/
- 58 struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
- 59 unsigned char curkeynum; /* 当前的按键号 */
- 60 };
结构体 imx6uirq_dev 为本例程设备结构体
62 struct imx6uirq_dev imx6uirq; /* irq 设备 */
定义设备结构体变量 imx6uirq。
- 64 /* @description : 中断服务函数,开启定时器,延时 10ms,
- 65 * 定时器用于按键消抖。
- 66 * @param - irq : 中断号
- 67 * @param - dev_id : 设备结构。
- 68 * @return : 中断执行结果
- 69 */
- 70 static irqreturn_t key0_handler(int irq, void *dev_id)
- 71 {
- 72 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
- 73
- 74 dev->curkeynum = 0;
- 75 dev->timer.data = (volatile long)dev_id;
- 76 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
- 77 return IRQ_RETVAL(IRQ_HANDLED);
- 78 }
key0_handler 函数,按键 KEY0 中断处理函数,参数 dev_id 为设备结构体,也就是 imx6uirq。第 74 行设置 curkeynum=0,表示当前按键为 KEY0,第 76 行使用 mod_timer函数启动定时器,定时器周期为 10ms。
- 80 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
- 81 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
- 82 * @param – arg : 设备结构变量
- 83 * @return : 无
- 84 */
- 85 void timer_function(unsigned long arg)
- 86 {
- 87 unsigned char value;
- 88 unsigned char num;
- 89 struct irq_keydesc *keydesc;
- 90 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
- 91
- 92 num = dev->curkeynum;
- 93 keydesc = &dev->irqkeydesc[num];
- 94
- 95 value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
- 96 if(value == 0){ /* 按下按键 */
- 97 atomic_set(&dev->keyvalue, keydesc->value);
- 98 }
- 99 else{ /* 按键松开 */
- 100 atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
- 101 atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
- 102 }
- 103 }
timer_function 函数,定时器定时处理函数,参数 arg 是设备结构体,也就是imx6uirq,在此函数中读取按键值。第 95 行通过 gpio_get_value 函数读取按键值。如果为 0 的话就表示按键被按下去了,按下去的话就设置 imx6uirq 结构体的 keyvalue 成员变量为按键的键值,比如 KEY0 按键的话按键值就是 KEY0VALUE=0。如果按键值为 1 的话表示按键被释放了,按键释放了的话就将 imx6uirq 结构体的 keyvalue 成员变量的最高位置 1,表示按键值有效,也就是将 keyvalue 与 0x80 进行或运算,表示按键松开了, 并且设置 imx6uirq 结构体的 releasekey成员变量为 1,表示按键释放,一次有效的按键过程发生。
- 106 * @description : 按键 IO 初始化
- 107 * @param : 无
- 108 * @return : 无
- 109 */
- 110 static int keyio_init(void)
- 111 {
- 112 unsigned char i = 0;
- 113
- 114 int ret = 0;
- 115
- 116 imx6uirq.nd = of_find_node_by_path("/key");
- 117 if (imx6uirq.nd== NULL){
- 118 printk("key node not find!\r\n");
- 119 return -EINVAL;
- 120 }
- 121
- 122 /* 提取 GPIO */
- 123 for (i = 0; i < KEY_NUM; i++) {
- 124 imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,
- "key-gpio", i);
- 125 if (imx6uirq.irqkeydesc[i].gpio < 0) {
- 126 printk("can't get key%d\r\n", i);
- 127 }
- 128 }
- 129
- 130 /* 初始化 key 所使用的 IO,并且设置成中断模式 */
- 131 for (i = 0; i < KEY_NUM; i++) {
- 132 memset(imx6uirq.irqkeydesc[i].name, 0,
- sizeof(imx6uirq.irqkeydesc[i].name));
- 133 sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
- 134 gpio_request(imx6uirq.irqkeydesc[i].gpio,
- imx6uirq.irqkeydesc[i].name);
- 135 gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
- 136 imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(
- imx6uirq.nd, i);
- 137 #if 0
- 138 imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(
- imx6uirq.irqkeydesc[i].gpio);
- 139 #endif
- 140 printk("key%d:gpio=%d, irqnum=%d\r\n",i,
- imx6uirq.irqkeydesc[i].gpio,
- 141 imx6uirq.irqkeydesc[i].irqnum);
- 142 }
- 143 /* 申请中断 */
- 144 imx6uirq.irqkeydesc[0].handler = key0_handler;
- 145 imx6uirq.irqkeydesc[0].value = KEY0VALUE;
- 146
- 147 for (i = 0; i < KEY_NUM; i++) {
- 148 ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
- imx6uirq.irqkeydesc[i].handler,
- IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
- imx6uirq.irqkeydesc[i].name, &imx6uirq);
- 149 if(ret < 0){
- 150 printk("irq %d request failed!\r\n",
- imx6uirq.irqkeydesc[i].irqnum);
- 151 return -EFAULT;
- 152 }
- 153 }
- 154
- 155 /* 创建定时器 */
- 156 init_timer(&imx6uirq.timer);
- 157 imx6uirq.timer.function = timer_function;
- 158 return 0;
- 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 行初始化定时器,并且设置定时器的定时处理函数。
- /*
- 162 * @description : 打开设备
- 163 * @param – inode : 传递给驱动的 inode
- 164 * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
- 165 * 一般在 open 的时候将 private_data 指向设备结构体。
- 166 * @return : 0 成功;其他 失败
- 167 */
- 168 static int imx6uirq_open(struct inode *inode, struct file *filp)
- 169 {
- 170 filp->private_data = &imx6uirq; /* 设置私有数据 */
- 171 return 0;
- 172 }
- 174 /*
- 175 * @description : 从设备读取数据
- 176 * @param – filp : 要打开的设备文件(文件描述符)
- 177 * @param – buf : 返回给用户空间的数据缓冲区
- 178 * @param - cnt : 要读取的数据长度
- 179 * @param – offt : 相对于文件首地址的偏移
- 180 * @return : 读取的字节数,如果为负值,表示读取失败
- 181 */
- 182 static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
- size_t cnt, loff_t *offt)
- 183 {
- 184 int ret = 0;
- 185 unsigned char keyvalue = 0;
- 186 unsigned char releasekey = 0;
- 187 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)
- filp->private_data;
- 188
- 189 keyvalue = atomic_read(&dev->keyvalue);
- 190 releasekey = atomic_read(&dev->releasekey);
- 191
- 192 if (releasekey) { /* 有按键按下 */
- 193 if (keyvalue & 0x80) {
- 194 keyvalue &= ~0x80;
- 195 ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
- 196 } else {
- 197 goto data_error;
- 198 }
- 199 atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
- 200 } else {
- 201 goto data_error;
- 202 }
- 203 return 0;
- 204
- 205 data_error:
- 206 return -EINVAL;
- 207 }
imx6uirq_read 函数,对应应用程序的 read 函数。此函数向应用程序返回按键值。首先判断 imx6uirq 结构体的 releasekey 成员变量值是否为 1,如果为 1 的话表示有一次有效按键发生,否则的话就直接返回-EINVAL。当有按键事件发生的话就要向应用程序发送按键值,首先判断按键值的最高位是否为 1,如果为 1 的话就表示按键值有效。如果按键值有效的话就将最高位清除,得到真实的按键值,然后通过 copy_to_user 函数返回给应用程序。向应用程序发送按键值完成以后就将 imx6uirq 结构体的 releasekey 成员变量清零,准备下一次按键操作。
- 209 /* 设备操作函数 */
- 210 static struct file_operations imx6uirq_fops = {
- 211 .owner = THIS_MODULE,
- 212 .open = imx6uirq_open,
- 213 .read = imx6uirq_read,
- 214 };
按键中断驱动操作函数集 imx6uirq_fops。
- 216 /*
- 217 * @description : 驱动入口函数
- 218 * @param : 无
- 219 * @return : 无
- 220 */
- 221 static int __init imx6uirq_init(void)
- 222 {
- 223 /* 1、构建设备号 */
- 224 if (imx6uirq.major) {
- 225 imx6uirq.devid = MKDEV(imx6uirq.major, 0);
- 226 register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,
- IMX6UIRQ_NAME);
- 227 } else {
- 228 alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,
- IMX6UIRQ_NAME);
- 229 imx6uirq.major = MAJOR(imx6uirq.devid);
- 230 imx6uirq.minor = MINOR(imx6uirq.devid);
- 231 }
- 232
- 233 /* 2、注册字符设备 */
- 234 cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
- 235 cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
- 236
- 237 /* 3、创建类 */
- 238 imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
- 239 if (IS_ERR(imx6uirq.class)) {
- 240 return PTR_ERR(imx6uirq.class);
- 241 }
- 242
- 243 /* 4、创建设备 */
- 244 imx6uirq.device = device_create(imx6uirq.class, NULL,
- imx6uirq.devid, NULL, IMX6UIRQ_NAME);
- 245 if (IS_ERR(imx6uirq.device)) {
- 246 return PTR_ERR(imx6uirq.device);
- 247 }
- 248
- 249 /* 5、 初始化按键 */
- 250 atomic_set(&imx6uirq.keyvalue, INVAKEY);
- 251 atomic_set(&imx6uirq.releasekey, 0);
- 252 keyio_init();
- 253 return 0;
- 254 }
驱动入口函数,第 250 和 251 行分别初始化 imx6uirq 结构体中的原子变量keyvalue 和releasekey,第 252 行调用 keyio_init 函数初始化按键所使用的 IO。
- 256 /*
- 257 * @description : 驱动出口函数
- 258 * @param : 无
- 259 * @return : 无
- 260 */
- 261 static void __exit imx6uirq_exit(void)
- 262 {
- 263 unsigned int i = 0;
- 264 /* 删除定时器 */
- 265 del_timer_sync(&imx6uirq.timer);
- 266
- 267 /* 释放中断 */
- 268 for (i = 0; i < KEY_NUM; i++) {
- 269 free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
- 270 }
- 271 cdev_del(&imx6uirq.cdev);
- 272 unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
- 273 device_destroy(imx6uirq.class, imx6uirq.devid);
- 274 class_destroy(imx6uirq.class);
- 275 }
- 276
- 277 module_init(imx6uirq_init);
- 278 module_exit(imx6uirq_exit);
- 279 MODULE_LICENSE("GPL");
- 280 MODULE_AUTHOR("zuozhongkai");
驱动出口函数,第 265 行调用 del_timer_sync 函数删除定时器,第 268~070行轮流释放申请的所有按键中断。
3.编写测试APP
不断的读取/dev/imx6uirq 文件来获取按键值,当按键按下以后就会将获取到的按键值输出在终端上.
- 21 /*
- 22 * @description : main 主程序
- 23 * @param - argc : argv 数组元素个数
- 24 * @param - argv : 具体参数
- 25 * @return : 0 成功;其他 失败
- 26 */
- 27 int main(int argc, char *argv[])
- 28 {
- 29 int fd;
- 30 int ret = 0;
- 31 char *filename;
- 32 unsigned char data;
- 33 if (argc != 2) {
- 34 printf("Error Usage!\r\n");
- 35 return -1;
- 36 }
- 37
- 38 filename = argv[1];
- 39 fd = open(filename, O_RDWR);
- 40 if (fd < 0) {
- 41 printf("Can't open file %s\r\n", filename);
- 42 return -1;
- 43 }
- 44
- 45 while (1) {
- 46 ret = read(fd, &data, sizeof(data));
- 47 if (ret < 0) { /* 数据读取错误或者无效 */
- 48
- 49 } else { /* 数据读取正确 */
- 50 if (data) /* 读取到数据 */
- 51 printf("key value = %#X\r\n", data);
- 52 }
- 53 }
- 54 close(fd);
- 55 return ret;
- 56 }
第 45~53 行的 while 循环用于不断的读取按键值,如果读取到有效的按键值就将其输出到
终端上。
- 1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek
- ......
- 4 obj-m := imx6uirq.o
- ......
- 11 clean:
- 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
输入如下命令编译出驱动模块文件:
make -j32
编译成功以后就会生成一个名为“ imx6uirq.ko”的驱动模块文件。
输入如下命令编译测试 imx6uirqApp.c 这个测试程序:
arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp
编译成功以后就会生成 imx6uirqApp 这个应用程序。
- depmod //第一次加载驱动的时候需要运行此命令
- modprobe imx6uirq.ko //加载驱动
用如下命令来测试中断:
./imx6uirqApp /dev/imx6uirq
按下开发板上的 KEY0 键,终端就会输出按键值。
如果要卸载驱动的话输入如下命令即可:
rmmod imx6uirq.ko
本节笔记主要学习了中断驱动开发的下半部分,主要包括中断驱动开发及测试。其中最主要的内容为中断驱动开发相关内容。
本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。