一、设备节点添加
首先在设备树文件中添加pinctrl以及在根目录下添加设备节点。
如下:
//创建按键输入的pinctrl pinctrl_key: keygrp { fsl,pins = < mx6ul_pad_uart1_cts_b__gpio1_io18 0xf080 /* key0 */ >; }; //创建按键节点 key { #address-cells = <1>; #size-cells = <1>; compatible = "atkalpha-key"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_key>; key-gpio = <&gpio1 18 gpio_active_low>; /* key0 */ status = "okay"; }; };
二、创建驱动文件代码
2.1 核心数据结构
定义结构体,其中包含按键驱动所需的信息,使用atomic_t类型保证按键值的原子操作。
struct key_dev { dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev结构体 */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ struct device_node *nd; /* 设备树节点 */ int key_gpio; /* 按键gpio编号 */ atomic_t keyvalue; /* 按键值 */ };
2.2 按键值定义
驱动中定义了两个按键状态:按下(1)和未按下/无效(0)。
#define key0value 1 /* 按键值 */ #define invakey 0 /* 无效的按键值 */
2.3 关键函数实现
首先是gpio初始化:从设备树获取按键gpio信息,并配置为输入
static int keyio_init(void) { keydev.nd = of_find_node_by_path("/key"); if (keydev.nd == null) { return -einval; } keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0); if (keydev.key_gpio < 0) { printk("can't get key0\r\n"); return -einval; } printk("key_gpio=%d\r\n", keydev.key_gpio); /* 初始化key所使用的io */ gpio_request(keydev.key_gpio, "key0"); /* 请求io */ gpio_direction_input(keydev.key_gpio); /* 设置为输入 */ return 0; }
按键读取:驱动会阻塞等待按键释放后才返回,进而实现了一次完整按键周期的检测。
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; int value; struct key_dev *dev = filp->private_data; if (gpio_get_value(dev->key_gpio) == 0) { /* key0按下 */ while(!gpio_get_value(dev->key_gpio)); /* 等待按键释放 */ atomic_set(&dev->keyvalue, key0value); } else { atomic_set(&dev->keyvalue, invakey); /* 无效的按键值 */ } value = atomic_read(&dev->keyvalue); ret = copy_to_user(buf, &value, sizeof(value)); return ret; }
三、创建测试文件
在测试文件中,通过对字符设备文件(/dev/key)进行标准文件操作实现与内核驱动层的交互。
程序结构包括四个关键函数:信号处理函数sig_handler()、资源清理函数cleanup_resources()、帮助显示函数show_usage()及主函数main()。
在主函数中,程序首先检查命令行参数格式,注册sigint信号处理确保可通过ctrl+c优雅退出,然后打开设备文件获取文件描述符fd,随后进入核心监测循环,通过read()系统调用读取按键状态并使用前后状态比较算法(prev_keyvalue与keyvalue对比)检测按键事件边缘变化,实时输出中文提示信息反馈按键状态。
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include "signal.h" /* 定义按键值 */ #define key0value 1 #define invakey 0 /* 全局变量 */ static int fd = -1; /* 文件描述符 */ static int running = 1; /* 程序运行标志 */ /* * @description : 信号处理函数 * @param - signum : 信号编号 * @return : 无 */ void sig_handler(int signum) { if (signum == sigint) { printf("\n程序接收到中断信号,正在退出...\n"); running = 0; } } /* * @description : 释放资源 * @param - filename: 设备文件名 * @return : 无 */ void cleanup_resources(const char *filename) { if (fd >= 0) { if (close(fd) < 0) { printf("文件 %s 关闭失败!\n", filename); } else { printf("已关闭设备文件 %s\n", filename); } } } /* * @description : 显示使用帮助 * @param - name : 程序名 * @return : 无 */ void show_usage(const char *name) { printf("使用方法: %s <设备文件>\n", name); printf("示例: %s /dev/key\n", name); } /* * @description : main主程序 * @param - argc : argv数组元素个数 * @param - argv : 具体参数 * @return : 0 成功;其他 失败 */ int main(int argc, char *argv[]) { char *filename; int keyvalue; int prev_keyvalue = invakey; /* 参数检查 */ if (argc != 2) { printf("参数错误!\n"); show_usage(argv[0]); return -1; } filename = argv[1]; /* 注册信号处理函数,捕获ctrl+c */ signal(sigint, sig_handler); /* 打开按键设备 */ fd = open(filename, o_rdwr); if (fd < 0) { printf("无法打开设备文件 %s!\n", filename); return -1; } printf("按键测试程序已启动\n"); printf("按下按键进行测试,按 ctrl+c 退出程序\n"); /* 循环读取按键值数据 */ while (running) { if (read(fd, &keyvalue, sizeof(keyvalue)) < 0) { printf("读取按键数据失败\n"); break; } /* 按键状态变化检测 */ if (keyvalue == key0value && prev_keyvalue != key0value) { printf("按键被按下,键值 = %d\n", keyvalue); } else if (keyvalue == invakey && prev_keyvalue == key0value) { printf("按键已释放\n"); } prev_keyvalue = keyvalue; usleep(50000); /* 短暂延时,降低cpu占用 */ } /* 清理资源 */ cleanup_resources(filename); printf("程序已退出\n"); return 0; }
四、测试
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论