1.按键驱动篇
实现思路,分别编写LED(前篇文章),以及按键的驱动。并编写一个APP程序进行驱动的调度。
这里一共有5个函数需要实现
首先需要定义一个设备的结构体用于描述设备的各种参数:
* key设备结构体 */
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; /* key所使用的GPIO编号 */
atomic_t keyvalue; /* 按键值 */
};
/* 设备操作函数 */
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.release =key_release,
.poll=key_poll,
};
之所以引进poll机制是因为,原本的按键检测需要一直进行监测,导致cpu占用十分高,在实际开发中不可能使用如此低效的机制,为此我们引入poll 机制,提升效率,在这里不科普,自行百度了解怕poll机制,以及等待队列的内容。
open()
static int key_open(struct inode *inode, struct file *filp)
{
int ret = 0;
filp->private_data = &keydev; /* 设置私有数据 */
ret = keyio_init(); /* 初始化按键IO */
if (ret < 0) {
return ret;
}
IRQ_EINT2=gpio_to_irq(keydev.key_gpio); //这里使用内置函数获取中断号
ret = request_irq(IRQ_EINT2, button_irq, IRQF_TRIGGER_FALLING, "button_irq", NULL);
if (ret < 0) {
return ret;
}
return 0;
}
1.open函数里调用了init,以及中断服务函数button_irq
ret = request_irq(IRQ_EINT2, button_irq, IRQF_TRIGGER_FALLING, "button_irq", NULL);
2.init函数
static int __init mykey_init(void)
{
/* 初始化原子变量 */
atomic_set(&keydev.keyvalue, INVAKEY);
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (keydev.major) { /* 定义了设备号 */
keydev.devid = MKDEV(keydev.major, 0);
register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME); /* 申请设备号 */
keydev.major = MAJOR(keydev.devid); /* 获取分配号的主设备号 */
keydev.minor = MINOR(keydev.devid); /* 获取分配号的次设备号 */
}
/* 2、初始化cdev *
keydev.cdev.owner = THIS_MODULE;
cdev_init(&keydev.cdev, &key_fops);
/* 3、添加一个cdev */
cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
/* 4、创建类 */
keydev.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(keydev.class)) {
return PTR_ERR(keydev.class);
}
/* 5、创建设备 */
keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);
if (IS_ERR(keydev.device)) {
return PTR_ERR(keydev.device);
}
printk("init success!\r\n");
return 0;
}
3.中断服务函数
这里可以简单理解为,poll机制使得等待队列(进程)休眠,中断就是叫醒等待队列的工具,如果没有这个工具那么进程就会一直没办法被唤醒。这是不允许的,所以引入中断是为了中断发生时唤醒等待队列。
这里的队列头可以直接使用;
DECLARE_WAIT_QUEUE_HEAD(button_wq_head);一步到位初始化队列头。
static irqreturn_t button_irq(int irq,void *data)
{
/* 判断等待队列中是否有等待元素 */
if(!waitqueue_active(&button_wq_head))
return IRQ_HANDLED;
/* 读取按键值 */
button_val =gpio_get_value(keydev.key_gpio);
if(!button_val)
{
button_press = 1;
//按下为0
}
else
{
//松开
button_press=0
}
printk("keyvalue%d\r\n",button_val);
press_event=1;
/* 唤醒等待队列 */
wake_up_interruptible(&button_wq_head);
return IRQ_HANDLED;
}
4.按键IO初始化
这里首先需要修改设备树,增加key节点。务必注意填写正确的GPIO号,这里大家可以查看芯片手册,查看引脚复用,查表得到GPIO号,重新编译设备树,烧写,在/proc/device-tree 目录下查看自己的key有没有被加载。

如图所示则正确。
key{
compatible="gpio-keys";
pinctrl-names="default";
pinctrl-0=<&pinctrl_gpio_keys>;
key-gpios=<&gpio1 18 GPIO_ACTIVE_LOW>;
status="okay";
};
sound {
compatible = "fsl,imx6ul-evk-wm8960",
"fsl,imx-audio-wm8960";
model = "wm8960-audio";
、、、、、、、
、、、、、、、
、、、、、、、
接下来就到了ioinit函数的编写
static int keyio_init(void)
{
printk("1\r\n");
keydev.nd=of_find_node_by_path("/key");
if (keydev.nd== NULL) {
return -EINVAL;
}
printk("2\r\n");
keydev.key_gpio = of_get_named_gpio(keydev.nd ,"key-gpios", 0);
if (keydev.key_gpio < 0) {
printk("can't get key0\r\n");
return -EINVAL;
}
printk("3\r\n");
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;
}
5.read函数
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
struct key_dev *dev = filp->private_data;
int ret = 0;
int value;
button_conditon = 0;
button_conditon = 0;
atomic_set(&dev->keyvalue, KEY0VALUE);
value = atomic_read(&dev->keyvalue);
ret = copy_to_user(buf, &value, sizeof(value));
printk("%d\r\n",ret);
return ret;
}
6.程序APP
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/stat.h>
#include <unistd.h>
/* 定义按键值 */
#define KEY0VALUE 0XF0
#define INVAKEY 0X00
#define LED_DEV "/dev/led"
int main(int argc, char *argv[])
{
int fd, ret,led_fd,led_ret;
char *filename;
int keyvalue;
int databuf[1];
databuf[0]=0;
struct pollfd fds[1];
if(argc != 2){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开key驱动 */
fd = open(filename, O_RDONLY|O_NONBLOCK);
printf("opening!\r\n");
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
/* 打开led驱动 */
led_fd= open(LED_DEV, O_RDWR|O_NONBLOCK);
printf("opening led/dev!\r\n");
if(fd < 0)
{
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
printf("1\r\n");
fds[0].fd=fd;
fds[0].events=POLLIN;
while (1)
{
printf("2\r\n");
poll(fds,1,-1);
printf("poll return\n");
if (fds[0].revents&POLLIN)
{
//keyvalue=0;
int ret=read(fd, &keyvalue, sizeof(keyvalue));
if (ret<0)
{
printf ("wrong\r\n");
}
printf("%d",keyvalue);
printf("11\n");
if (keyvalue==240)
{
printf("18\n");
printf("%d",keyvalue);
databuf[0]=1;
printf("key pressed!\n");
led_ret=write(led_fd,databuf, sizeof(databuf))
if(led_ret<0)
{
printf("LED CONTROL FAILED!\r\n");
}
}
sleep(1);
databuf[0]=0;
printf("key release!\n")
led_ret=write(led_fd,databuf, sizeof(databuf));
if(led_ret<0)
{
printf("LED CONTROL FAILED!\r\n");
}
}
}
ret= close(fd); /* 关闭文件 */
if(ret < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
led_ret=close(led_fd);
if(ret < 0){
printf("file %s close failed!\r\n",LED_DEV);
Return -1;
}
return 0;
}
测试结果

项目总结
问题1:设备文件无法打开
首先需要检查驱动是否已经被挂载,其次再检查函数是否写错,特别是关于设备树的地方,请确保开发板的设备树节点名称存在并且与函数中的名称一至,并且保证GPIO没有被占用。
问题2:app端访问驱动设备的函数需要使用非阻塞访问
fd = open(filename, O_RDONLY|O_NONBLOCK);
问题3:提示设备已经存在无法加载驱动,这时应该检查之前的驱动有没有被卸载,并且主设备号,次设备号有没有被其他占用,如果还是不行,重新烧写镜像。
问题4:of_get_name_by_path(”/key“)一定要与设备树完全一致,这里很容易出错
本文详细介绍了Linux环境下按键驱动的实现,包括设备结构体定义、设备操作函数、中断服务函数、按键IO初始化以及APP程序的编写。通过引入poll机制和中断服务,降低了CPU占用,提高了效率。在APP程序中,利用poll函数监听按键事件,并根据按键状态控制LED。在遇到设备文件无法打开、非阻塞访问等问题时,提供了相应的解决办法。
788

被折叠的 条评论
为什么被折叠?



