背景:公司有一个PS2键盘驱动的项目,没有控制器,需要模拟PS2协议,检测按键并通过input子系统将按键时间上报
PS2有两个控制线,时钟线和数据线。当按键按下或抬起,设备会向主机发送键码。时钟由设备产生,主机需要在下降沿去数据线采集一位数据。按键码由多个11位数据组成:0 (起始)+ 8位(数据)+1/0(奇偶校验位)+1(结束位)。数据位是先发的低位!
PS2接口是以前台式电脑后边留的圆形接口,6个引脚,主要用VCC,GND,clk,data。与AT(5脚)一样
PS2键码大致分为两类:
第1类按键 通码为一个字节,断码为0xF0+通码形式。如A键,其通码为0x1C;断码为0xF0 0x1C。
第2类按键 通码为两字节0xE0+0xXX形式,断码为0xE0+0xF0+0xXX形式。如Right Ctrl键,其通码为0xE0 0x14;断码为0xE0 0xF0 0x14。
通码为按下,段码为抬起
1,Linux X86 PS/2 键盘驱动框架流程(以下均已Intel 8042键盘控制器为例):
1.1 设备初始化注册流程:
键盘控制器硬件驱动(i8042.c) -> 串口驱动核心(serio.c) -> 串口驱动(atkbd.c)
-> 输入驱动核心(input.c) -> 输入事件驱动程序(keyboard.c) -> 虚拟控制台驱动模块;
注册流程:
1.1.1,键盘控制器硬件驱动(i8042.c):
1)、首先键盘硬件驱动程序读写键盘硬件控制器寄存器,配置寄存器并获取相控制器硬件信息(PS/2键盘控制器一共有三个寄存器加一个命令端口,命令数量也不多,下载地址:https://download.csdn.net/download/a372048518/9835565)
注意:Intel8042键盘控制器内部已经实现了PS/2时序协议,硬件驱动程序只需要读写相关寄存器即可;
2)、将8042键盘控制器作为平台设备(struct platform_device)挂接在平台总线(struct bus_type)上,并将获取到的硬件信息及相关软件数据结构信息保存在平台设备实例中;
3)、将8042键盘硬件控制器驱动(struct platform_driver)注册到平台总线上,平台总线匹配平台设备和平台驱动,并调用平台驱动的probe方法,在probe方法中分配并初始化串口数据结构实例(struct serio),将平台设备信息放入串口数据结构实例中;最后将串口结构实例注册进入串口驱动核心(serio_register_port);
1)、一个CLK引脚设为下降沿触发中断模式,当有中断产生是,读取数据线的值,连续读取11个为一组数据,保存在数组里,调用键码处理函数,将其中1~8位取出、位移,得到一个8位数。
2)、根据项目的键盘共分为12个1类码和4个2类码,置两个码值的标志位,区分按下和抬起某个按键,调用input_report_key()函数,将按键事件上报。
3)、驱动将设备初始化为input设备,这样应用层就可以使用input机制获取事件了
4)、中断处理键码,当有错误码产生后,会一直错误,为避免,需要加一个定时器机制,置零计数
dev = input_allocate_device();
dev->name = "my_ps2_key";
dev->phys = "ps2";
dev->uniq = "20180523";
dev->id.bustype = BUS_I8042;
dev->id.vendor = ID_PRODUCT;
dev->id.version = ID_VENDOR;
set_bit(EV_SYN, dev->evbit); //设置为同步事件,这个宏可以在input.h中找到
set_bit(EV_KEY, dev->evbit); //因为是按键,所以要设置成按键事件
set_bit(KEY_A, dev->keybit); //设置这个按键表示为KEY_A这个键,到时用来上报
。。。
ret = input_register_device(dev); //注册input设备
#define PS2_CLK_PIN 5
irq_num1 = gpio_to_irq(PS2_CLK_PIN); //申请中断号,并注册中断,下降沿触发
err = request_irq(irq_num1, irq_fuction, IRQF_TRIGGER_FALLING, "tiny4412_key1", dev);
#define OVER_TIME 1*HZ //超时时间1秒
struct timer_list timer; //定时器句柄
init_timer(&timer); //初始化定时器timer_list的一些变量
timer.expires = jiffies + OVER_TIME;
timer.function = timer_function;
add_timer(&timer); //将定时器加入到timer列表中,等待处理
mod_timer(&timer,jiffies + OVER_TIME); //激活timer,需要在add后激活才有意义
void timer_function(unsigned long arg) //定时器中断处理函数
{
countter = 0; //置零数据接收位数技术
mod_timer(&timer,jiffies + OVER_TIME); //再次激活定时器
}
代码较多,脚复杂,占篇幅大,语言表达:定义两个键码的两个标志位,初始化为0,当被调用此函数的时候,先判断这两个标志位是否都为零,如果都为零,则表示第一次按下某个按键。对于第一类码,这个就是按键的区分码,则判断是否为12个按键码中的一个,如果是,将其上报为按下,并将标志位置2,抬起的两个数据,将其减为0,下次有按键按下,重复过程。当标志为1时,此次是抬起的区分码,可以用于上报抬起事件。
对于第二类码,第一个数据为0xE0,没法区分键值,将标志位置为4,当进入函数时,二类码标志位为4,则说明此次数据为按键的区分码,判断是4个按键之一,就将其上报为按下,并将标志--,当抬起执行完,标志位正好为0,重复过程。当标志为1时,此次是抬起的区分码,可以用于上报抬起事件。
如果两个标志位不全为0,则表示不是按下的第一个码,区分哪个不为0,对标志位做区分,实现上述过程。
1、键盘是需求方提供的,需要用示波器一次采集出每个按键按下和抬起的所有码
A: 1C F0 1C 第一类键码
上: E0 74 E0 F0 74 第二类键码
2、根据应用层所使用的键码(input或PS2),上报对应的键值,上报之前可能需要转换一下
3、所有使用到的按键值,在初始化input_dev 的时候都需要set_bit到设备中,否则不能上报
PS2的驱动及应用程序测试文件,有需要的可以私聊,刚开始写博客,还不知道怎么上传