一、设备节点添加
首先在设备树文件中添加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;
}四、测试


总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论