【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验八:PS/2模块② — 键盘与组合键

简介: 实验八:PS/2模块② — 键盘与组合键 实验七之际,我们学习如何读取PS/2键盘发送过来的通码与断码,不过实验内容也是一键按下然后释放,简单按键行为而已。然而,实验八的实验内容却是学习组合键的按键行为。

实验八:PS/2模块② — 键盘与组合键

实验七之际,我们学习如何读取PS/2键盘发送过来的通码与断码,不过实验内容也是一键按下然后释放,简单按键行为而已。然而,实验八的实验内容却是学习组合键的按键行为。

不知读者是否有类似的经历?当我们使用键盘的时候,如果5~6按键同时按下,电脑随之便会发出“哔哔”的警报声,键盘立即失效。这是键盘限制设计,不同产品也有不同限制的按键数量。默认下,最大按键数量是5~7个。所谓组合键就是两个以上的按键所产生的有效按键。举例而言,按下按键 <A> 输出“字符a”,按下 <Shift> + <A>便输出“字符A”。不过要实现组合键,我们必须深入了解键盘的按键行为不可。

clip_image002

图8.1 按下又立即释放。

PS/2键盘最常见的按键行为是按下以后又立即释放,假设笔者按下<A>键又立即释放<A>键,那么PS/2键盘便会产生类似图8.1的时序。如图8.1所示,当笔者按下 <A> 的时候,PS/2键盘便会发送8’h1C的通码;反之,如果 <A> 被释放,PS2键盘也会立即发送8’hF0 8’h1C的断码。

clip_image004

图8.2 长按又立即释放。

如果笔者手痒长按 <A> 不放,那么PS/2键盘便会按照100ms的间隔时间,不断发送通码 8’h1C。期间,如果笔者释放 <A>,那么PS/2键盘便会发送 8’hF0 8’h1C的断码,时序结果如图8.2所示。不管是图8.1还是图8.2的情况,都是PS/2键盘最常见的按键行为,亦即单键行为。话虽如此,单键行为既是最基础的按键行为,多键行为也必须基于它。

clip_image006

图8.3 多键行为,先按后放①。

多键行为不同单键行为,因为多键行为同时存在两个以上的按键被按下,因此多键行为便有先按后放,先按先放等次序。假设笔者先按下<A>,然后又按下<LShift>,随之PS/2键盘便会接续发送通码 8’h1C与 8’h12。如果笔者想要撒手, <LShift> 必须事先释放,再者是 <A>,结果PS/2键盘便会连续发送 8’hF0 8’h12 与 8’hF0 8’h1C的断码。

clip_image008

图8.3 多键行为,先按后放②。

再假设笔者先按下 <A> 后按下 <LShift> 以后并没有立即释放任何按键,作为最后按下的按键,它可以得到执行权。如图8.3所示,笔者先是按下 <A> 然后又按下 <Shift>,那么PS/2键盘便会接续发送 8’h1C 与 8’h12等通码。假设笔者手指麻痹没有立即释放任何按键,那么 <LShift> 就会得到执行权,结果保持长按状态。此刻,PS/2键盘便会不停发送 <LShift> 的通码。

一旦手指回复知觉,然后按照先按后放的次序,先行释放 <LShift> 然后释放 <A>

,结果PS/2键盘便会接续发送 8’hF0 8’h12 与 8’hF0 8’h1C 等断码。

clip_image010

图8.5 多键行为,先按先放。

如果读者不是按照先按后放,而是先按先放的次序,先按下 <A>,后按下 <LShift> 的话 ... 如图8.5所示,假设笔者先按下 <A>,然后又按下 <LShift>,此刻PS/2键盘便会接续发送 8’h1C与 8’h12等通码。期间,笔者忽然手痒,觉得先按先放比较好玩,于是笔者故意松开 <A>,此刻PS/2键盘便会发送 8’hF0 8’h1C的断码。

同一时刻,<LShift> 亦然保持按下的姿势,PS/2键盘发送完毕 <A> 的断码以后,PS/2键盘也会不停发送 <LShift> 的通码 ... 直至笔者释放 <LShift>,PS/2键盘发送 8’hF0 8’h12的断码为止。

多键行为的终点就在于“先按后放”还是“先按先放”。不管是哪一种次序,下一刻按键都会抢夺上一刻按键的执行权与长按状态。不过根据习惯,先按后放固然已经成为主流,唯有意外或者那个神经不协调的傻子才会选择先按先放的次序。当我们理解PS/2键盘的多键行为以后,我们便可以开始实现组合键。

根据笔者的认识,PS/2键盘也有按键分类,如: <Shift>,<Ctrl> 还有 <Alt> 等按键,它们都是常见的组合(补助)按键。除此之外,笔记本或者一些特殊键盘也有不同的组合键,如:<FN> 与 <WIN> 按键。一般而言,我们都认为组合键是软件的工作,虽然这是不择不扣的事实,不过我们只要换个思路,Verilog也可以实现组合键。对此,我们只要将一只组合键视为一个立旗状态,所有难题都能迎刃而解。

clip_image012

图8.6 组合键与立旗状态。

假设笔者先按下 <LCtrl> 又按下 <LShift>,PS/2键盘发送完毕 <LCtrl> 的通码以后,isCtrl便会立旗。紧接着PS/2键盘又会发送 <L Shift> 的通码,随后 isShift也会立旗。

事后,笔者先释放 <LShift> 再释放 <LCtrl>,那么PS/2键盘便会接续发送 <LShift> 与 <LCtrl> 的断码。<LShift> 断码发送完毕以后,isShift便会消除立旗。同样 <LCtrl>断码发送完毕以后 isCtrl也会消除立旗。

clip_image014

图8.7 有效的组合键①。

为了表示有效的组合键,我们依然需要isDone这个高脉冲,我们虽然知道isDone产生高脉冲都是一般通码输出以后。不过在此,组合键不被认为是一般通码。如图8.7所示,假设笔者先按下 <LShift> 又按下 <A>,<LShift> 通码发送完毕以后便立旗 isShift;<A> 通码 发送完毕以后便拉高一会 isDone。如果此刻 isShift为拉高状态,而且通码<A> 又有效,那么有效的组合键 <Shift> + <A> 便产生。

完后,笔者先释放 <A> 在释放 <LShift>,PS/2键盘便会接续发送 <A> 与 <LShift>的断码。<A> 的断码没有产生任何效果,反之 <LShift> 的断码则消除 isShift的立旗状态。

clip_image016

图8.8 有效的组合键②。

为了产生各种各样的有效组合键,我们不可能不断按下又释放组合键 ... 换言之,不断切换的家伙只有非组合键而已,组合键则一直保持有效的状态,直至发送断码为止。如图8.8所示,假设笔者先按下 <LShift> 又按下 <A>, <LShift> 通码使 isShift 立旗,<A> 通码使 isDone产生高脉冲,对此组成键 <Shift> + <A> 完成。

随后,笔者释放 <A>,PS/2键盘便发送 <A> 断码。不一会,笔者又按下 <B>,<B>通码使 isDone产生高脉冲,结果完成组合键 <Shift> + <B>。事后,笔者释放 <B> 又释放 <LShift>,PS/2键盘便会接续发送断码 <B> 与 <LShift>,<B> 断码没有异样,<LShift> 断码则消除 isShift 的立旗状态。

clip_image018

图8.9 多状态有效组合键。

除了当个组合键(一个立即状态)以外,同样的道理也能实现多个组合键(多个立旗状态)。如图8.9所示,笔者先是按下 <LCtrl> 又按下 <LShift>,<LCtrl>通码立旗 isCtrl状态,<LShift> 通码则立旗 isShift 状态。紧接着笔者又按下 <A>,<A>通码导致 isDone产生一个高脉冲,此刻组合键 <Ctrl> + <Shift> + <A> 已经完成。然后笔者释放 <A> 使其产生 <A>断码。

不一会,笔者又按下 <B>,结果 <B> 通码驱使 isDone又产生另一个高脉冲,此刻组合键 <Ctrl> + <Shift> + <B> 已经完成。心满意足的笔者接续释放 <B>,<LShift> 还有 <LCtrl>。<B> 断码没有任何异样,<LShift> 断码消除 isShift立旗状态,<LCtrl> 断码则消除 isCtrl立旗状态。

一般而言,组合键最多可以达到3级,亦即 <Ctrl> + <Shift> + <Alt> + ?。话虽如此,除非对方的手指比猴子更灵活,不然要同时按照次序按下4个按键是一件容易伤害手指的蠢事。换之,一级与两级的组合键已经足够应用。理论上,Verilog要实现多少级组合键也没有问题,但是过多的功能只是浪费而已。

好了,上述这些内容理解完毕以后,我们便可以开始建模了!

clip_image020

图8.10 实验八建模图。

图8.10是实验八的建模图,一个名为ps2_demo的组合模块,内含PS/2功能模块,还有数码管基础模块。PS/2功能模块的左方是 PS2_CLK 与 PS2_DAT 等顶层信号的输入,右方则是oData与oTag联合驱动数码管基础模块。对此,数码管除了输出通码以外,数码管也会表示组合键的有效状态。

ps2_funcmod.v

clip_image022

图8.11 PS/2功能模块的建模图。

相较图8.10与图8.11,图8.11的PS/2功能模块还有oTrig,用来发送isDone的高脉冲。至于具体内容如何,让我们来瞧瞧代码吧:

1.    module ps2_funcmod
2.    (
3.         input CLOCK, RESET,
4.         input PS2_CLK, PS2_DAT,
5.         output oTrig,
6.         output [7:0]oData,
7.         output [2:0]oTag
8.    );

以上内容为出入端声明。

9.    
10.         parameter LSHIFT = 8'h12, LCTRL = 8'h14, LALT = 8'h11, BREAK = 8'hF0;
11.         parameter FF_Read= 5'd5;
12.    
13.         /*******************************/ // sub1
14.         
15.        reg F2,F1; 
16.         
17.        always @ ( posedge CLOCK or negedge RESET )
18.             if( !RESET )
19.                  { F2,F1 } <= 2'b11;
20.              else
21.                  { F2, F1 } <= { F1, PS2_CLK };
22.    
23.         /*******************************/ // core
24.         
25.         wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );

以上内容为常量声明,周边操作以及即时声明。第10行是 LSHIFT,LCTRl 还有 LALT 等通码的常量声明。此外也有 BREAK 断码第一帧数据,还有伪函数的入口(第11行)。第15~21行是用来检测电平变化的周边操作,第25行则是下降沿的即时声明。

26.         reg [7:0]D1;
27.         reg [2:0]isTag;  // [2] isShift, [1] isCtrl, [0] isAlt
28.         reg [4:0]i,Go;
29.         reg isDone;
30.         
31.         always @ ( posedge CLOCK or negedge RESET )
32.             if( !RESET )
33.                  begin
34.                         D1 <= 8'd0;
35.                         isTag <= 3'd0;
36.                         i <= 5'd0;
37.                         Go <= 5'd0;
38.                         isDone <= 1'b0;
39.                    end
40.               else

以上内容是相关的寄存器声明以及复位操作。期间 isTag是状态寄存器,isTag[2] 标示 isShift,isTag[1] 标示 isCtrl,isTag[0] 标示 isAlt。第33~38行则是这番寄存器的复位操作。

65.                          /****************/ // PS2 Read Function
66.                          
67.                          5:  // Start bit
68.                          if( isH2L ) i <= i + 1'b1; 
69.                          
70.                          6,7,8,9,10,11,12,13:  // Data byte
71.                          if( isH2L ) begin i <= i + 1'b1; D1[ i-6 ] <= PS2_DAT; end
72.                          
73.                          14: // Parity bit
74.                          if( isH2L ) i <= i + 1'b1;
75.                          
76.                          15: // Stop bit
77.                          if( isH2L ) i <= Go;
78.                            
79.                     endcase

以上内容为部分核心操作的伪函数。该伪函数读取PS/2的1帧数据。

41.                    case( i )
42.                          
43.                          0: // Read Make
44.                          begin i <= FF_Read; Go <= i + 1'b1; end
45.                          
46.                          1: // Set Flag
47.                          if( D1 == LSHIFT ) begin isTag[2] <= 1'b1; D1 <= 8'd0; i <= 5'd0;end
48.                          else if( D1 == LCTRL ) begin isTag[1] <= 1'b1; D1 <= 8'd0; i <= 5'd0; end
49.                          else if( D1 == LALT ) begin isTag[0] <= 1'b1; D1 <= 8'd0; i <= 5'd0; end
50.                          else if( D1 == BREAK ) begin i <= FF_Read; Go <= i + 5'd3; end
51.                          else begin i <= i + 1'b1; end
52.                          
53.                          2:
54.                          begin isDone <= 1'b1; i <= i + 1'b1; end
55.                          
56.                          3:
57.                          begin isDone <= 1'b0; i <= 5'd0; end
58.                          
59.                          4: // Clear Flag
60.                          if( D1 == LSHIFT  ) begin isTag[2] <= 1'b0; D1 <= 8'd0; i <= 5'd0;  end
61.                          else if( D1 == LCTRL ) begin isTag[1] <= 1'b0; D1 <= 8'd0; i <= 5'd0; end
62.                          else if( D1 == LALT ) begin isTag[0] <= 1'b0; D1 <= 8'd0; i <= 5'd0;  end
63.                          else begin D1 <= 8'd0; i <= 5'd0; end

以上内容是核心操作,操作的过程如下:

步骤0,进入伪函数等待读取通码,并且Go指向下一个步骤。

步骤1,检测组合键与断码,如果是LShift 那么isTag[2]立旗,然后返回步骤0;如果是 LCTRL 那么 isTag[1] 立旗,然后返回步骤0;如果是 LALT 那么 isTag[0] 立旗,然后返回步骤0。如果是 BREAK便进入伪函数,然后Go指向步骤4。如果什么都不是便进入步骤2~3。

步骤2~3,产生完成信号,然后返回步骤0。

步骤4,用来消除立旗状态。步骤1为 BREAK便会进入这里,如果断码为 LSHIFT便会消除 isTag[2],LCTRL消除 isTag[1],LALT 消除 isTag[0],无视其它断码。最后返回步骤0。

80.         
81.         assign oTrig = isDone;
82.         assign oData = D1;
83.         assign oTag = isTag;
84.        
85.    endmodule

第81~83行是输出驱动声明。

ps2_demo.v

笔者在此就不再重复粘贴建模图了,请自行复习图8.10。

1.    module ps2_demo
2.    (
3.         input CLOCK, RESET,
4.         input PS2_CLK, PS2_DAT,
5.         output [7:0]DIG,
6.         output [5:0]SEL
7.    );
8.         wire [7:0]DataU1;
9.         wire [2:0]TagU1;
10.    
11.         ps2_funcmod U1
12.         (
13.              .CLOCK( CLOCK ),
14.              .RESET( RESET ),
15.              .PS2_CLK( PS2_CLK ), // < top
16.              .PS2_DAT( PS2_DAT ), // < top
17.              .oTrig(),
18.              .oData( DataU1 ),  // > U2
19.              .oTag( TagU1 ) // > U2
20.         );
21.         
22.       smg_basemod U2
23.        (
24.            .CLOCK( CLOCK ),
25.            .RESET( RESET ),
26.            .DIG( DIG ),  // > top
27.            .SEL( SEL ),  // > top
28.            .iData( { 12'h000 , 1'b0, TagU1, DataU1 } ) // < U1
29.        );
30.                 
31.    endmodule

基本上,ps2_demo 的内容并没有什么难度,所有连线部署都按照图8.10。至于第28行,DataU1还有 TagU1联合驱动数码管基础模块的iData。换句话说,无视数码管的1~3位,第4位数码管显示组合键状态,第5~6位数码管则显示通码。

编译完后便下载程序。如果同时按下 <LShift> + <LCtrl> + <LAlt>,第4位数码管便会显示 4’h7,亦即 4’b0111,或者说 isTag[2..0] 皆为立旗状态。如果按下其它按键,如 <A>,那么第5~6位的数码管便会显示 8’h1C。假设释放 <LShift>,第4位数码管便会显示4’h3,亦即 4’b0011,或者说 isTag[1..0] 皆为立旗状态。释放 <A>,第5~6位数码管则会显示 8’h00。

细节一:完整的个体模块

clip_image024

图8.12 PS/2键盘功能模块。

图8.12是PS/2键盘功能模块,内容基本上与PS/2功能模块一模一样,至于区别就是穿上其它马甲而已,所以怒笔者不再重复粘贴了。

目录
相关文章
|
数据采集 算法 数据安全/隐私保护
【硬件测试】基于FPGA的MSK调制解调系统系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文基于FPGA实现MSK调制解调系统,采用Verilog开发,包含同步模块、高斯信道模拟、误码率统计等功能。相比仿真版本,新增ILA数据采集与VIO在线SNR设置模块。通过硬件测试验证,展示不同SNR(如10dB和16dB)下的性能表现。研究聚焦软件无线电领域,优化算法复杂度以适应硬件限制,利用MSK恒定包络、相位连续等特性提升频谱效率。核心代码实现信号生成、调制解调、滤波及误码统计,提供完整的硬件设计与分析方案。
552 19
|
数据采集 移动开发 算法
【硬件测试】基于FPGA的QPSK调制+软解调系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文基于FPGA实现QPSK调制与软解调系统,包含Testbench、高斯信道、误码率统计模块,并支持不同SNR设置。硬件版本新增ILA在线数据采集和VIO在线SNR设置功能,提供无水印完整代码及测试结果。通过VIO分别设置SNR为6dB和12dB,验证系统性能。配套操作视频便于用户快速上手。 理论部分详细解析QPSK调制原理及其软解调实现过程,涵盖信号采样、相位估计、判决与解调等关键步骤。软解调通过概率估计(如最大似然法)提高抗噪能力,核心公式为*d = d_hat / P(d_hat|r[n])*,需考虑噪声对信号点分布的影响。 附Verilog核心程序代码及注释,助力理解与开发。
458 5
|
数据采集 算法 数据安全/隐私保护
【硬件测试】基于FPGA的16QAM调制+软解调系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文基于之前开发的16QAM调制与软解调系统,增加了硬件测试功能。该系统包含FPGA实现的16QAM调制、软解调、高斯信道、误码率统计模块,并新增了ILA在线数据采集和VIO在线SNR设置模块。通过硬件测试,验证了不同SNR条件下的系统性能。16QAM软解调通过比较接收信号采样值与16个调制点的距离,选择最近的调制点来恢复原始数据。核心Verilog代码实现了整个系统的功能,包括SNR设置、信号处理及误码率统计。硬件测试结果表明系统在不同SNR下表现良好,详细操作步骤可参考配套视频。
383 13
|
算法 数据安全/隐私保护 异构计算
基于FPGA的变步长LMS自适应滤波器verilog实现,包括testbench
### 自适应滤波器仿真与实现简介 本项目基于Vivado2022a实现了变步长LMS自适应滤波器的FPGA设计。通过动态调整步长因子,该滤波器在收敛速度和稳态误差之间取得良好平衡,适用于信道均衡、噪声消除等信号处理应用。Verilog代码展示了关键模块如延迟单元和LMS更新逻辑。仿真结果验证了算法的有效性,具体操作可参考配套视频。
654 74
|
算法 数据安全/隐私保护 异构计算
基于FPGA的16QAM调制+软解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本项目基于FPGA实现了16QAM基带通信系统,包括调制、信道仿真、解调及误码率统计模块。通过Vivado2019.2仿真,设置不同SNR(如8dB、12dB),验证了软解调相较于传统16QAM系统的优越性,误码率显著降低。系统采用Verilog语言编写,详细介绍了16QAM软解调的原理及实现步骤,适用于高性能数据传输场景。
765 69
|
机器学习/深度学习 算法 数据安全/隐私保护
基于FPGA的SNN脉冲神经网络之LIF神经元verilog实现,包含testbench
本项目展示了 LIF(Leaky Integrate-and-Fire)神经元算法的实现与应用,含无水印运行效果预览。基于 Vivado2019.2 开发,完整代码配有中文注释及操作视频。LIF 模型模拟生物神经元特性,通过积分输入信号并判断膜电位是否达阈值产生脉冲,相较于 Hodgkin-Huxley 模型更简化,适合大规模神经网络模拟。核心程序片段示例,助您快速上手。
|
算法 数据安全/隐私保护 异构计算
基于LSB最低有效位的音频水印嵌入提取算法FPGA实现,包含testbench和MATLAB对比
本项目展示了一种基于FPGA的音频水印算法,采用LSB(最低有效位)技术实现版权保护与数据追踪功能。使用Vivado2019.2和Matlab2022a开发,完整代码含中文注释及操作视频。算法通过修改音频采样点的最低有效位嵌入水印,人耳难以察觉变化。然而,面对滤波或压缩等攻击时,水印提取可能受影响。该项目运行效果无水印干扰,适合实时应用场景,核心逻辑简单高效,时间复杂度低。
|
算法 数据安全/隐私保护 异构计算
基于FPGA的2ASK+帧同步系统verilog开发,包含testbench,高斯信道,误码统计,可设置SNR
本内容展示了基于Vivado2019.2的算法仿真效果,包括设置不同信噪比(SNR=8db和20db)下的结果及整体波形。同时,详细介绍了2ASK调制解调技术的原理与实现,即通过改变载波振幅传输二进制信号,并提供数学公式支持。此外,还涉及帧同步理论,用于确定数据帧起始位置。最后,给出了Verilog核心程序代码,实现了2ASK解调与帧同步功能,结合DDS模块生成载波信号,完成信号处理流程。
314 0
|
编解码 算法 数据安全/隐私保护
基于FPGA的信号DM编解码实现,包含testbench和matlab对比仿真
本项目展示了DM编解码算法的实现与测试结果。FPGA测试结果显示为T1,Matlab仿真结果为T2。使用软件版本为Matlab 2022a和Vivado 2019.2。核心程序包含详细中文注释和操作视频。DM编解码通过比较信号样本差值进行编码,适用于音频等低频信号处理。硬件结构包括编码器(采样器、减法器、比较器)和解码器(解码器、积分器)。
|
移动开发 算法 数据安全/隐私保护
基于FPGA的QPSK调制+软解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了基于FPGA的QPSK调制解调系统,通过Vivado 2019.2进行仿真,展示了在不同信噪比(SNR=1dB, 5dB, 10dB)下的仿真效果。与普通QPSK系统相比,该系统的软解调技术显著降低了误码率。文章还详细阐述了QPSK调制的基本原理、信号采样、判决、解调及软解调的实现过程,并提供了Verilog核心程序代码。
678 26

热门文章

最新文章