首页 > 系统相关 >linux按键的几种实现方式

linux按键的几种实现方式

时间:2024-12-04 21:33:22浏览次数:14  
标签:linux 几种 驱动 键值 KEY 按键 gpio include

Linux 按键有哪些实现方法

常用的按键实现方法

继点亮了LED 和 OLED之后,该讲讲我常用的键盘怎么实现的了。才入行的时候,想实现一个按键监测功能,对linux驱动也是一无所知。只会基础的在sys/class/gpios/目录下操作gpio,好一点的驱动呢,还支持在sys下配置edge(触发类型),支持上升沿、下降沿、或则双边沿。如果遇到以下不支持中断的gpio或则平台,真的一点办法都没有。
调试龙芯2K的时候,就因为它只有4个专用gpio支持边沿中断,而且都还不支持双边沿中断。直到后来发现了gpio_keys.c,当时居然都不知道有gpio_keys_poll.c驱动。后来慢慢了解到怎么使用串口实现输入子系统的按键等等。
也终于理解了输入子系统怎么实现按键的,按下、释放、长按等实现原理。
才疏学浅,非科班出身,分享的多是些实用型的技术,想要深入学习内核的我分享的内容可能不太适合。有讲的不对的地方,欢迎指出来。

gpio_keys.c配置方法

驱动位置:drivers/input/keyboard/gpio_keys.c
该驱动是利用gpio管脚可配置边沿中断的功能,分别在上升沿或则下降沿中断触发后再进行软件消抖,通过输入子系统上报输入事件的变化。
这一层其实只检测了按键按下和释放,长按实际上是输入子系统实现。
原理就是当产生按键pressed事件后,如果250ms内没有收到release,那么内核就会上报该键被持续按下,之后每35ms上报一次直到按键释放。
这就是长按的内核实现原理,和后面不同的驱动实现的原理也都是一样的。

内核配置中使能该驱动。
设备树配置:
文章讲的都是通过设备树配置,最早的通过板级文件配置的方式现在也几乎都淘汰了,以前的项目配置过,配置文件乱糟糟的一大堆,也不想再去翻看了。还是设备树好用,配置信息一目了然,层次结构也清晰。

#include <dt-bindings/input/input.h>
#include "m300.dtsi"
#include <dt-bindings/interrupt-controller/irq.h>
#include <generated/autoconf.h>


/{
        gpio_keys: gpio_keys {
               compatible = "gpio-keys";
               autorepeat;
               up {
								label = "GPIO Key UP";
								linux,code = <103>;
								gpios = <&gpb 26 GPIO_ACTIVE_HIGH INGENIC_GPIO_NOBIAS>;
              ;  
              down {
								label = "GPIO Key DOWN";
								linux,code = <108>;
								gpios = <&gpb 27 GPIO_ACTIVE_HIGH INGENIC_GPIO_NOBIAS>;
              ;           
        };
};

在gpio_keys分支下继续添加需要的按键信息即可, 其中autorepeat 这个属性是让按键支持长按效果。

gpio_keys_polled.c配置方法

polled,顾名思义就是轮训的意思,当年不知道有这个驱动,只会用上面这个驱动。所以配置龙芯按键驱动的时候,是修改了内核源码,当下降沿中断发生后,立即配置成上升沿中断,如此反复交替,基本能够实现按键的功能。
后来发现轮询的驱动方式后,不支持中断的gpio也就都不是问题了。
驱动位置:drivers/input/keyboard/gpio_keys_poll.c
这个驱动就没什么可讲的,就是把轮询的事情放在内核里面来完成,用户态程序按照输入子系统来编程即可。同样也是支持pressed、continue pressed、release,甚至在作为普通按键,我都建议统一成轮询的方式,可以减少中断的产生,降低上下文切换。
设备树配置:

	gpio_keys {
		compatible = "gpio-keys-polled";
		#address-cells = <1>;
		#size-cells = <0>;
		autorepeat;
		poll-interval = <100>;
		up {
			label = "UP";          /*键值196*/
			linux,code = <103>;
			gpios = <&pioA 0 1>;
			debounce-interval = <5>;
	};
	
	down {
			label = "DOWN";          /*键值195*/
			linux,code = <108>;
			gpios = <&pioA 1 1>;
			debounce-interval = <5>;
			#gpio-key,wakeup;
			};
	};

串口键盘驱动

当我们需要实现更多键值的键盘时候,以上两种实现方式可能就有点不适合了。这时候就需要对按键进行编码,例如USB键盘、蓝牙键盘等。这里介绍一种在嵌入式平台上的一种简单灵活的实现方式:
适用场景:
在这里插入图片描述不排除还有一些CPU保留了beyboard接口,那样也可以支持扫描矩阵式键盘,但是在一般的嵌入式CPU上还是很少见到这类接口。如上图,我们可以通过一片FPGA、MCU或则是一些专用芯片,来实现按键的硬件检测;然后将检测到的按键事件,编码后通过串口发送到CPU。CPU通过stowaway驱动,处理键值再提交给输入子系统。
至于FPGA、MCU的按键检测方法,可以是IO直连的,也可以是矩阵扫描,消抖那些工作也都由这一层来完成。

驱动位置:drivers/input/keyboard/stowaway.c

驱动实现方式是将串口作为一个serio总线设备,利用linux内核提供的serio总线驱动,通过设置对应的串口,调用setport提供的函数将串口当做serio总线设备,在驱动里面需要按照serio总线设备的驱动框架来实现。
驱动的核心代码就是通过serio接收一个字节的键值编码,然后解析该键值,提交输入子系统该键值和按键状态。
以4个按键为例,为了让键值更有通用性,采用linux的标准input code对键值进行编码。键值定义如下:

按键标准键值pressedrelease
KEY_UPd 1030x670xe7
KEY_DOWNd 1080x6c0xec
ENTERd 280x1c0x9c
CANCELd 10x010x81

按照编码规则,占用位宽为1字节,其中最高为用来标志按下或则释放,所以理论上最多支持128个按键。
对驱动代码作出如下修改:

//static unsigned char skbd_keycode[128] = {
//      KEY_1, KEY_2, KEY_3, KEY_Z, KEY_4, KEY_5, KEY_6, KEY_7,
//      0, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_GRAVE,
//      KEY_X, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_SPACE,
//      KEY_CAPSLOCK, KEY_TAB, KEY_LEFTCTRL, 0, 0, 0, 0, 0,
//      0, 0, 0, KEY_LEFTALT, 0, 0, 0, 0,
//      0, 0, 0, 0, KEY_C, KEY_V, KEY_B, KEY_N,
//      KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_HOME, KEY_8, KEY_9, KEY_0, KEY_ESC,
//      KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_BACKSLASH, KEY_END, KEY_U, KEY_I, KEY_O, KEY_P,
//      KEY_APOSTROPHE, KEY_ENTER, KEY_PAGEUP,0, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON,
//      KEY_SLASH, KEY_UP, KEY_PAGEDOWN, 0,KEY_M, KEY_COMMA, KEY_DOT, KEY_INSERT,
//      KEY_DELETE, KEY_LEFT, KEY_DOWN, KEY_RIGHT,  0, 0, 0,
//      KEY_LEFTSHIFT, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0,
//      0, 0, 0, 0, 0, 0, 0, 0,
//      0, 0, 0, 0, 0, 0, 0, 0,
//      0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7,
//      KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, 0, 0, 0
//};

static unsigned char skbd_keycode[] = {
        KEY_UP, KEY_DOWN, KEY_ENTER, KEY_ESC};

struct skbd
{
        //unsigned char keycode[128];
        unsigned char keycode[4];
        struct input_dev *dev;
        struct serio *serio;
        char phys[32];
};

static irqreturn_t skbd_interrupt(struct serio *serio, unsigned char data,
                                                                  unsigned int flags)
{
        struct skbd *skbd = serio_get_drvdata(serio);
        struct input_dev *dev = skbd->dev;

        // if (skbd->keycode[data & SKBD_KEY_MASK]) {
        // input_report_key(dev, skbd->keycode[data & SKBD_KEY_MASK],
        // !(data & SKBD_RELEASE));
        // input_sync(dev);
        // }

        if (data & SKBD_KEY_MASK)
        {
                input_report_key(dev, data & SKBD_KEY_MASK,
                                                 !(data & SKBD_RELEASE));
                input_sync(dev);
        }

        return IRQ_HANDLED;
}

源码中的键值定义和键值查找被我修改了一下,源码中的匹配规则我也不太清除,应该是有相应的映射关系,不过不重要了。改了之后的规则简单明了,有新增需求直接按照以上表格定义的规则添加键值即可。

驱动改好了,但是这时候驱动并不会被调用,烧写完成进系统后,设备目录下并没有出现对应的input/event节点,说明connect并没有被调用,也就是serio总线上并没有对应的serio设备。
此时串口还是作为一个tty设备存在,通过以下用户态代码,可以将tty设备设置成一个serio设备。该程序需要保持在后台运行,所以将程序启动为Deamon守护进程。代码如下:

#include <stdio.h>	/*标准输入输出定义*/
#include <unistd.h> /*Unix标准函数定义*/
#include <stdlib.h>
#include <sys/types.h> /**/
#include <sys/stat.h>  /**/
#include <sys/ioctl.h>
#include <fcntl.h> /*文件控制定义*/
#include <linux/fs.h>
#include <errno.h> /*错误号定义*/
#include <string.h>
#include <linux/fb.h>
#include <malloc.h>
#include <sys/mman.h> /**/
#include <termios.h>  /*PPSIX终端控制定义*/
#include <linux/serio.h>

#define SERIO_ANG 0xff
#define SERIO_EGALAX 0x3f
#define SERIO_STOWAWAY 0x20

void Deamon()
{
	pid_t pid = fork();
	int size;
	int i;
	if (0 != pid)
	{
		exit(0);
	}

	setsid(); //创建新会话

	pid = fork(); //
	if (0 != pid)
	{
		exit(0);
	}

	chdir("/");
	umask(0);
	size = getdtablesize();
	for (i = 0; i < size; i++)
	{
		close(i);
	}
}

int main(int argc, char *argv[])
{
	int ret;
	int ldisc;
	unsigned long type;
	struct termios option;
	int fd = -1;

	Deamon();

	//子进程工作
	fd = open("/dev/ttyS2", O_RDWR | O_NONBLOCK | O_NOCTTY);
	if (0 > fd)
	{
		perror("open");
		return -1;
	}

	tcgetattr(fd, &option);
	option.c_iflag = IGNPAR | IGNBRK;
	option.c_cflag = HUPCL | CS8 | CREAD | CLOCAL | B9600;
	option.c_cc[VMIN] = 1;
	option.c_cc[VTIME] = 0;

	cfsetispeed(&option, B9600);
	cfsetospeed(&option, B9600);

	ret = tcsetattr(fd, TCSANOW, &option);
	if (0 > fd)
	{
		perror("TCSANOW");
		return -1;
	}

	ldisc = N_MOUSE;
	ret = ioctl(fd, TIOCSETD, &ldisc);
	if (ret)
	{
		perror("TIOCSETD");
	}
	type = SERIO_STOWAWAY | (SERIO_ANG << 8) | (SERIO_ANG << 16);
	ret = ioctl(fd, SPIOCSTYPE, &type);
	if (ret)
	{
		perror("SPIOCSTYPE");
	}
	read(fd, NULL, 0);
	return 0;
}

将以上代码修改成对应的串口号,编译后上板子执行,内核提示生成了对应的event节点:

[ 149.801033] input: Stowaway Keyboard as
/devices/platform/apb/10032000.serial/tty/ttyS2/serio2/input/input3
[ 149.811235] serio: Serial port ttyS2

这时候,就可以按照标准的input输入设备进行编程。

其他输入设备实现方式

linux还提供了一种更灵活的输入设备驱动实现方式,就是完全基于用户态的编程。
驱动路径:drivers/input/misc/uinput.c
该驱动可以用于自动测试脚本、外挂、VNC等网络应用;例如远端通过网络发送输入事件,本地通过uinput编程的方式,将事件通知到内核,内核再按照输入子系统的方式,把输入事件分发到各个检测模块。
这个目前之用在过一个仪表的远程桌面开发项目,了解的也不够深入。等有机会再探索一些,再来分享。

标签:linux,几种,驱动,键值,KEY,按键,gpio,include
From: https://blog.csdn.net/weixin_41972926/article/details/144181106

相关文章

  • 『Linux』 第五章 基础IO
    1.理解“文件”1.1狭义理解 文件在磁盘里磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的磁盘是外设(即是输出设备也是输入设备)磁盘上的文件本质是对文件的所有操作,都是对外设的输入和输出简称IO1.2广义理解Linux下一切皆文件(键盘、显示器、网卡、磁盘……......
  • linux ssd1307fb驱动适配总结
    linuxssd1307fb驱动适配总结linuxframebuffer点亮oled屏幕继第一篇文章,点亮了LED指示灯之后,正好手里有一个小模块,OLED显示屏。同样也是需要点亮的,那就继续点亮更多的东西吧。现在项目是越来越扣成本了,以前最少也是用一个彩色的串口屏,现在已经降级到使用黑白的OLED点阵......
  • Linux配置Apache服务
    通过yum源安装Apache服务启动apache服务查看服务运行状态(结果中有active(running)字样,表示服务正在运行中)设置服务开机自启动systemctlenablehttpd设置为开机自启动后,当开机或者重启操作系统后,httpd自动运行浏览器查看Apache默认测试页面。http://192.168.21.144A......
  • linux下使用Devhelp添加c++帮助手册
    首先需要两个资源:devhelp和https://zh.cppreference.com/的帮助文档一、准备1.安装devhelpsudoaptinstalldevhelp2.下载https://zh.cppreference.com/的html版本的帮助文档下载以html开头的压缩包 二、将帮助文档解压并将文件放入devhelp目录下1.帮助文档解压后目......
  • Linux: Centos7 Cannot find a valid baseurl for repo: base/7/x86_64 解决方案
    问题背景执行yumupdate出现如下报错排查虚拟机是否联网ping-c4www.baidu.com可以看到网络链接没有问题解决方案原因是国外的镜像源有问题,换成国内的即可。备份原有的镜像源sudomv/etc/yum.repos.d/CentOS-Base.repo/etc/yum.repos.d/CentOS-Base.repo.backup......
  • linux,ssh连接
    Linux,ssh远程连接一、linux端配置1、安装ssh服务sudoapt-getupdatesudoapt-getinstallopenssh-clientsudoapt-getinstallopenssh-server2、启动sshservicesshstart3、检查是否成功启动sshps-e|grepssh确保出现ssh-agent,若无ssh-agent,执行下列代码......
  • Linux无图形界面环境使用Python+Selenium实践 (转载)
    原文链接:https://developer.aliyun.com/article/1511623简介: 在Linux上使用Selenium和Python来控制浏览器进行自动化测试或者网页数据抓取是常见的需求。本文将介绍如何在Linux无图形界面环境下使用Selenium与Firefox浏览器以headless模式运行,并提供geckodriver、Xvfb和pyvirtu......
  • Java 中几种常用的数据库访问技术
    摘要: 本文深入探讨了Java中几种常用的数据库访问技术,包括JDBC、Hibernate、MyBatis等。详细阐述了每种技术的基本原理、核心组件、使用方法,并通过丰富的示例代码展示了它们在实际应用中的数据库交互操作。通过对这些技术的学习,读者能够全面了解Java与数据库交互的多种途......
  • Linux红帽ISO镜像以及VMware Workstation Pro 16.1.2的下载与安装
    目录一,VMwareWorkstationPro1.VMware(16Pro)下载:2,软件安装二,Linux红帽ISO镜像下载1,用迅雷下载2,安装步骤一,VMwareWorkstationPro我本人在官网已经找了好久,发现寻找及其麻烦,在csdn中找到了大佬的分享附上原文链接https://blog.csdn.net/Qi_1337/article/details/......
  • 嵌入式Linux,文件I/O深入探究,函数相关详解。
    1. 返回错误处理在Linux系统下对常见的错误做了一个编号,每一个编号都代表着每一种不同的错误类型,当函数执行发生错误的时候,操作系统会将这个错误所对应的编号赋值给errno变量,每一个进程都维护了自己的errno变量,它是程序中的全局变量,该变量用于存储就近发生的函......