linux实现poll机制按键点灯

本文详细介绍了Linux环境下按键驱动的实现,包括设备结构体定义、设备操作函数、中断服务函数、按键IO初始化以及APP程序的编写。通过引入poll机制和中断服务,降低了CPU占用,提高了效率。在APP程序中,利用poll函数监听按键事件,并根据按键状态控制LED。在遇到设备文件无法打开、非阻塞访问等问题时,提供了相应的解决办法。

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“)一定要与设备树完全一致,这里很容易出错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@Rangers

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值