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

实验九:PS/2模块③ — 键盘与多组合键

笔者曾经说过,通码除了单字节以外,也有双字节通码,而且双字节通码都是 8’hE0开头,别名又是 E0按键。常见的的E0按键有,<↑>,<↓>,<←>,<→>,<HOME>,<PRTSC> 等编辑键。除此之外,一些组合键也是E0按键,例如 <RCtrl> 或者 <RAlt>

。所以说,当我们设计组合键的时候,除了考虑“左边”的组合键以外,我们也要考虑“右边”的组合键。<Ctrl> 为例:

<LCtrl> 通码是 8’h14;

<RCtrl> 通码则是 8’hE0 8’h14。

E0按键除了通码携带 8’hE0字节以外,E0按键的断码同样也会携带 8’hE0字节。<Ctrl>继续为例:

<LCtrl> 断码是 8’hF0 8’h14;

<RCtrl> 断码是 8’hE0 8’F0 8’h14。

至于时序方面呢 ...

图9.1 含有E0的通码与断码。

如图9.1所示,当笔者按下 <RCtrl>,紧接着PS/2键盘会发送 8’hE0 8’h14的通码,完后isCtrl立旗。假设笔者立即释放 <RCtrl>,那么PS/2键盘会发送 8’hE0 8’hF0 8’h14的断码,事后isCtrl就会消除立旗状态。

图9.2 E0按键与组合键①。

假设笔者按下 <RCtrl> 又按下 <A>,那么 <RCtrl> 通码会导致 isCtrl立旗,<A> 通码则会导致 isDone产生高脉冲,此刻组合键 <Ctrl> + <A> 完成。假设笔者手痒,先释放 <A> 再释放 <RCtrl>,<A> 断码没有异常,反之 <RCtrl> 断码则会消除 isCtrl的立旗状态。

图9.3 E0按键与组合键②。

假设顽皮的笔者先按下 <RCtrl> 又按下 <LCtrl> 然后释放 <LCtrl>。首先 <RCtrl> 通码会导致 isCtrl 立旗,不过 <LCtrl> 通码会驱使 isCtrl 重复立旗,但是 <LCtrl> 断码则会消除 isCtrl的立旗状态。如果此刻笔者按下 <A>,虽然 <A> 通码使产生isDone的高脉冲,但是组合键 <Ctrl> + <A> 则没有成立。心灰意冷的笔者,于是便释放 <A>又释放 <RCtrl>,期间 <A> 断码与 <RCtrl> 断码都没有异样。

图9.4 E0按键与组合键③。

为了解决这个问题,我们必须把 isCtrl 旗标区分为 isLCtrl 与 isRCtrl 为两种旗标。如图9.4所示,同样的按键过程,不过却有不同的按键结果。期间,<RCtrl> 通码立旗 isRCtrl,换之 <LCtrl> 通码立旗 isLCtrl。虽然 <LCtrl> 断码消除 isLCtrl的立旗状态,但是 <A> 通码还有isRCtrl 立旗因为合作无间,结果造就组合键 <Ctrl> + <A> 完成。 事后 <RCtrl> 断码再消除 isRCtrl 的立旗状态。

为此,我们 isLCtrl 与 isRCtrl 之间的关系可以这样表示:

wire isCtrl = isLCtrl | isRCtrl;

除此之外, isLShift,isRShift,isLAlt 与 isRAlt也是同样的道理。

我们虽然已经解决 E0按键还有组合键之间的问题,但是还有根本性的问题在等待我们。实验七~八之际,解读一帧数据,数据要么就是通码,数据要么就是断码 ... 换句话说,检测数据的时候,我们只要检测1×2等两种可能性而已,即8’hF0或者非 8’hF0。如果数据是 8’hF0,那么数据就是断码,否则就是通码。

一旦 E0按键乱入搅局,检测的可能性也从原来的 1×2等两种可能性,变成 1×2×3 等8种可能性,这个事实无疑会加剧Verilog的描述难度。简言之就是实验七,还有实验八的思路却不适合实验九,为此我们需要更换一下思路。

假设实验九所针对的组合键有:

<LShift> 与 <RShift>
<LCtrl> 与 <RCtrl>
<LAlt> 与 <RAlt>

然后,我们必须事先考虑所有可能性,包括这些组合键的通码与断码,然后用常量表达出来,结果如代码9.1所示:

      parameter MLSHIFT = 24‘h00_00_12, MLCTRL = 24‘h00_00_14, MLALT = 24‘h00_00_11;
     parameter BLSHIFT = 24‘h00_F0_12, BLCTRL = 24‘h00_F0_14, BLALT = 24‘h00_F0_11;
     parameter MRSHIFT = 24‘h00_00_59, MRCTRL = 24‘hE0_00_14, MRALT = 24‘hE0_00_11;
     parameter BRSHIFT = 24‘h00_F0_59, BRCTRL = 24‘hE0_F0_14, BRALT = 24‘hE0_F0_11;

代码9.1

如代码9.1所示,M××表示通码,B××表示断码 ... 如果算计字节 8’hF0与 8’hE0,

所有组合键的通码与断码都可以使用3字节来表达。期间<RCtrl> 与 <RAlt> 的通码与断码都有8’hE0的字眼。此外,我们知道低级建模II是追求表达能力的建模技巧,凡事力求直观 ... 为此,我们必须建立3个步骤,而且每个步骤处理单一情况,结果如代码9.2所示:

1.      1: // E0_xx_xx & E0_F0_xx Check
2.      if( T == 8‘hE0 ) begin D1[23:16] <= T; i <= FF_Read; Go <= i; end
3.      else if( D1[23:16] == 8‘hE0 && T == 8‘hF0 ) begin D1[15:8] <= T; i <= FF_Read; Go <= i; end
4.      else if( D1[23:8] == 16‘hE0_F0 ) begin D1[7:0] <= T; i <= CLEAR; end
5.      else if( D1[23:16] == 8‘hE0 && T != 8‘hF0 ) begin D1[15:0] <= {8‘d0, T}; i <= SET; end
6.      else i <= i + 1‘b1;
7.                          
8.      2: // 00_F0_xx Check
9.      if( T == BREAK ) begin D1[23:8] <= {8‘d0,T}; i <= FF_Read; Go <= i; end
10.      else if( D1[23:8] == 16‘h00_F0 ) begin D1[7:0] <= T; i <= CLEAR; end
11.      else i <= i + 1‘b1;
12.             
13.      3: // 00_00_xx Check
14.        begin D1 <= {16‘d0,T}; i <= SET; end

代码9.2

如代码9.2所示:

步骤1处理 E0_××_×× 或者 E0_F0_××,亦即针对E0通码与E0断码。

步骤2处理 00_F0_××,亦即针对一般断码。

步骤3处理 00_00_××,亦即针对一般通码。

接下来,让让我们详细理解一下各个步骤的内容:

步骤1:

第2行 if( T == 8‘hE0 ) 表示,如果第一字节是 8’hE0便将8’hE0暂存在 D1[23:16],即E0按键,然后步骤指向伪函数读取第二字节,并且Go返回当前步骤。

第3行 if( D1[23:16] == 8‘hE0 && T == 8‘hF0 ) 表示,如果 D1[23:16] 的内容是 8’hE0并且第二字节是8’hF0,即E0断码。为此,F0暂存在 D1[15:8],然后步骤指向伪函数读取第三字节,Go则返回当前步骤。

第4行 if( D1[23:8] == 16‘hE0_F0 ) 表示,如果 D1[23 :8] 为 16’hE0_F0,那么第三字节也是断码。为此,D1[7:0] 暂存第三字节,然后步骤指向 Clear (消除步骤)。

第5行 if( D1[23:16] == 8‘hE0 && T != 8‘hF0 ) 表示, D1[23:16] 为 8’hE0,但是第二字节不是8’hF0,即E0通码。为此,D1[16:8] 赋值 8’h00,D1[7:0] 则暂存第二字节,然后步骤指向 SET (设置步骤)。

第6行,当什么都不是则表示对象不是E0按键,i递增以示下一个步骤。

步骤2:

第9行,if( T == BREAK ) 表示,第一字节为 8’hF0,即是一般断码。为此,D1[23:18] 赋值8’h00,D1[16:8] 则暂存 8’hF0,然后i指向伪函数读取第二字节,Go返回当前步骤。

第10行,if( D1[23:8] == 16‘h00_F0 ) 表示,第一字节8’hF0已经读取完毕,现正准备断码的后续字节。为此,D1[7:0] 暂存第二字节,然后i指向 Clear(消除步骤)。

第11行,当什么都是则表示对象只是一般通码而已。

步骤3:

第14行,D1[23:8] 赋值16’h00_00 然后 D1[7:0] 暂存第一字节,然后i指向 SET (设置步骤)。

1.      4: // Set state
2.      if( D1 == MRSHIFT ) begin isTag[5] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
3.      else if( D1 == MRCTRL ) begin isTag[4] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
4.      else if( D1 == MRALT ) begin isTag[3] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
5.      else if( D1 == MLSHIFT ) begin isTag[2] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
6.      else if( D1 == MLCTRL ) begin isTag[1] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
7.      else if( D1 == MLALT ) begin isTag[0] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
8.      else i <= DONE;
9.                          
10.      5: // Clear state
11.      if( D1 == BRSHIFT ) begin isTag[5] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
12.      else if( D1 == BRCTRL ) begin isTag[4] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
13.      else if( D1 == BRALT ) begin isTag[3] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
14.      else if( D1 == BLSHIFT ) begin isTag[2] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
15.      else if( D1 == BLCTRL ) begin isTag[1] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
16.      else if( D1 == BLALT ) begin isTag[0] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
17.      else begin D1 <= 24‘d0; i <= 5‘d0; end

代码9.3

当第一至第三字节经由步骤1~3分析并且整理完毕以后,就会路由步骤4(SET)或者步骤5(CLEAR)。

步骤4,第2~7行是用来立旗,如果D1的内容是组合键,那么相关的标志位 isTag[n] 就会立旗,D1清空,然后i返回步骤0;否则,对象只是一般字符按键的通码而已,结果i指向 DONE并且产生完成信号(第8行)。

步骤5,第11~16行是用来消除立旗,如果D的内容是组合键,那么相关的标志位 isTag[n]就会被消除,D1清空,i返回步骤0。否则,对象只是一般的字符按键的断码而已,结果D1清零,i则返回步骤0。

理解这些内容以后,我们就可以开始建模了。

图9.5 实验九的建模图。

图9.5是实验九的建模图,相较实验八,PS/2功能模块的 oTag 则多了3个状态,余下都一样。

ps2_funcmod.v

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

同样,PS/2功能模块相较实验八,oTag增多了3个位宽,此外内容也发生不少改变。

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

以上内容为相关的出入端声明。

9.         parameter MLSHIFT = 24‘h00_00_12, MLCTRL = 24‘h00_00_14, MLALT = 24‘h00_00_11;
10.         parameter BLSHIFT = 24‘h00_F0_12, BLCTRL = 24‘h00_F0_14, BLALT = 24‘h00_F0_11;
11.         parameter MRSHIFT = 24‘h00_00_59, MRCTRL = 24‘hE0_00_14, MRALT = 24‘hE0_00_11;
12.         parameter BRSHIFT = 24‘h00_F0_59, BRCTRL = 24‘hE0_F0_14, BRALT = 24‘hE0_F0_11;
13.         parameter BREAK = 8‘hF0;
14.         parameter FF_Read = 5‘d8, DONE = 5‘d6, SET = 5‘d4, CLEAR = 5‘d5;

以上内容为组合键的常量声明(三字节)。第13行是BREAK的常量声明。第14行是伪函数,SET步骤与CLEAR步骤等入口地址声明。

16.         /*******************************/ // sub1
17.         
18.        reg F2,F1; 
19.         
20.        always @ ( posedge CLOCK or negedge RESET )
21.             if( !RESET )
22.                  { F2,F1 } <= 2‘b11;
23.              else
24.                  { F2, F1 } <= { F1, PS2_CLK };
25.    
26.         /*******************************/ // core
27.         
28.         wire isH2L = ( F2 == 1‘b1 && F1 == 1‘b0 );

以上内容是检测电平的周边操作,第28行则是下降沿的即时声明。

29.         reg [7:0]T;
30.         reg [23:0]D1;
31.         reg [5:0]isTag; // [5]isRShift, [4]isRCtrl, [3]isRAlt, [2]isLShift, [1]isLCtrl, [0]isLAlt;
32.         reg [4:0]i,Go;
33.         reg isDone;
34.         
35.         always @ ( posedge CLOCK or negedge RESET )
36.             if( !RESET )
37.                  begin
38.                         T <= 8‘d0;
39.                         D1 <= 24‘d0;
40.                         isTag <= 6‘d0;
41.                         i <= 5‘d0;
42.                         Go <= 5‘d0;
43.                         isDone <= 1‘b0;
44.                    end
45.               else

以上内容是是相关寄存器的声明以及复位操作。T用于伪函数的暂存空间,D1用来暂存按键数据,isTag用来标示各个组合按键的状态,i指向步骤,Go返回步骤,isDone则标示有效按键。

46.                    case( i )
47.                          
48.                          0: // Read Make
49.                          begin i <= FF_Read; Go <= i + 1‘b1; end
50.                          
51.                          1: // E0_xx_xx & E0_F0_xx Check
52.                          if( T == 8‘hE0 ) begin D1[23:16] <= T; i <= FF_Read; Go <= i; end
53.                          else if( D1[23:16] == 8‘hE0 && T == 8‘hF0 ) begin D1[15:8] <= T; i <= FF_Read; Go <= i; end
54.                          else if( D1[23:8] == 16‘hE0_F0 ) begin D1[7:0] <= T; i <= CLEAR; end
55.                          else if( D1[23:16] == 8‘hE0 && T != 8‘hF0 ) begin D1[15:0] <= {8‘d0, T}; i <= SET; end
56.                          else i <= i + 1‘b1;
57.                          
58.                          2: // 00_F0_xx Check
59.                          if( T == BREAK ) begin D1[23:8] <= {8‘d0,T}; i <= FF_Read; Go <= i; end
60.                          else if( D1[23:8] == 16‘h00_F0 ) begin D1[7:0] <= T; i <= CLEAR; end
61.                           else i <= i + 1‘b1;
62.             
63.                          3: // 00_00_xx Check
64.                          begin D1 <= {16‘d0,T}; i <= SET; end
65.                        

以上内容为部分核心操作,过程如下:

步骤0,进入伪函数以致读取第一字节数据。

步骤1处理 E0_××_×× 或者 E0_F0_××,亦即针对E0通码与E0断码。

步骤2处理 00_F0_××,亦即针对一般断码。

步骤3处理 00_00_××,亦即针对一般通码。

66.                          4: // Set state
67.                          if( D1 == MRSHIFT ) begin isTag[5] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
68.                          else if( D1 == MRCTRL ) begin isTag[4] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
69.                          else if( D1 == MRALT ) begin isTag[3] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
70.                          else if( D1 == MLSHIFT ) begin isTag[2] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
71.                          else if( D1 == MLCTRL ) begin isTag[1] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
72.                          else if( D1 == MLALT ) begin isTag[0] <= 1‘b1; D1 <= 24‘d0; i <= 5‘d0; end
73.                          else i <= DONE;
74.                          
75.                          5: // Clear state
76.                          if( D1 == BRSHIFT ) begin isTag[5] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
77.                          else if( D1 == BRCTRL ) begin isTag[4] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
78.                          else if( D1 == BRALT ) begin isTag[3] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
79.                          else if( D1 == BLSHIFT ) begin isTag[2] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
80.                          else if( D1 == BLCTRL ) begin isTag[1] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
81.                          else if( D1 == BLALT ) begin isTag[0] <= 1‘b0; D1 <= 24‘d0; i <= 5‘d0; end
82.                          else begin D1 <= 24‘d0; i <= 5‘d0; end
83.                          

以上内容为部分核心操作,过程如下:

步骤4,用来立旗组合键。

步骤5,则用来消除组合键。

84.                          6: // DONE
85.                          begin isDone <= 1‘b1; i <= i + 1‘b1; end
86.                          
87.                          7:
88.                          begin isDone <= 1‘b0; i <= 5‘d0; end

以上内容为部分核心操作,步骤6~7用来产生完成信号。

90.                          /****************/ // PS2 Read Function
91.                          
92.                          8:  // Start bit
93.                          if( isH2L ) i <= i + 1‘b1; 
94.                          
95.                          9,10,11,12,13,14,15,16:  // Data byte
96.                          if( isH2L ) begin i <= i + 1‘b1; T[ i-9 ] <= PS2_DAT; end
97.                          
98.                          17: // Parity bit
99.                          if( isH2L ) i <= i + 1‘b1;
100.                          
101.                          18: // Stop bit
102.                          if( isH2L ) i <= Go;
103.                            
104.                     endcase
105.         

以上内容为部分核心操作。步骤8~18是读取一帧数据的伪函数。

106.         assign oTrig = isDone;
107.         assign oData = D1[7:0];
108.         assign oTag = isTag;
109.         
110.    endmodule

以上内容是输出驱动声明。

ps2_demo.v

组合模块 ps2_demo 的连线部署请浏览图9.5。

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 [5: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( { 8‘h00, 1‘b0,TagU1[5:3], 1‘b0,TagU1[2:0], DataU1 } ) // < U1
29.        );
30.                 
31.    endmodule

上述代码基本上没有什么难点,除了第28行的联合驱动。8’h00表示无视第1~2位的数码管。1’b0 + TagU1[5:3] 表示第3位数码管显示 <RCtrl> <RShift> 还有 <RAlt> 等立旗状态。1’b0 + TagU1[2:0] 表示第4位数码管显示 <LCtrl> <LShift> 还有 <LAlt> 等立旗状态。DataU1则表示第5~6位数码管显示通码。

编译完毕便下载程序。如果读者同时按下 <RCtrl> <RShift> 还有 <RAlt>,第3位数码管就会显示4’h7,即4’b0111。如果同时按下 <LCtrl> 还有 <LShift> ,第4位数码管就会显示4’h6,即4’b0110。如果按下 <A>,第5~6位数码管则会显示 8’h1C。注意,千万不要太贪心,同时按下6个以上的按键,PS/2键盘会因此而罢工的 ...

本实验结束之前,让我们来聊聊一些八卦 ... 实验九的 PS/2功能模块虽然支持 E0按键,但是仅限 E0的组合键而已。至于那些 E0 编辑键,如 <↑> 或者 <↓>,则需要进一步扩展,不过该要求已经超出本书的讨论范围。不管对象是 E0组合键,还是E0编辑键,设计思路也是一样的,不过后者比较偏向软件。对此,读者只要基于实验九,再简单扩展一下即可。

细节一:完整的个体模块

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

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

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

时间: 2024-10-01 04:32:56

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验九:PS/2模块③ — 键盘与多组合键的相关文章

【黑金原创教程】【FPGA那些事儿-驱动篇I 】连载导读

前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例如UART,VGA等,但是<驱动篇I>贵就贵在建模技巧的升华,亦即低级建模II. 话说低级建模II,读过<建模篇>的朋友多少也会面熟几分,因为它是低级建模的进化形态.许久以前,笔者就有点燃低级建模II的念头,但是懒惰的性格让笔者别扭许久.某天,老大忽然说道:"让咱们大干一场吧

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验六:数码管模块

实验六:数码管模块 有关数码管的驱动,想必读者已经学烂了 ... 不过,作为学习的新仪式,再烂的东西也要温故知新,不然学习就会不健全.黑金开发板上的数码管资源,由始至终都没有改变过,笔者因此由身怀念.为了点亮多位数码管从而显示数字,一般都会采用动态扫描,然而有关动态扫描的信息请怒笔者不再重复.在此,同样也是动态扫描,但我们却用不同的思路去理解. 图6.1 6位数码管. 如图6.1所示,哪里有一排6位数码管,其中包好8位DIG信号还有6位SEL信号.DIG为digit,即俗称的数码管码,如果数码管

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验三:按键模块② — 点击与长点击

实验三:按键模块② - 点击与长点击 实验二我们学过按键功能模块的基础内容,其中我们知道按键功能模块有如下操作: l 电平变化检测: l 过滤抖动: l 产生有效按键. 实验三我们也会z执行同样的事情,不过却是产生不一样的有效按键: l 按下有效(点击): l 长按下有效(长点击). 图3.1 按下有效,时序示意图. 图3.2 长按下有效,时序示意图. 如图3.1所示,按下有效既是"点击",当按键被按下并且消抖完毕以后,isSClick信号就有被拉高一个时钟(Short Click).

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二:按键模块① - 消抖

实验二:按键模块① - 消抖 按键消抖实验可谓是经典中的经典,按键消抖实验虽曾在<建模篇>出现过,而且还惹来一堆麻烦.事实上,笔者这是在刁难各位同学,好让对方的惯性思维短路一下,但是惨遭口水攻击 ... 面对它,笔者宛如被甩的男人,对它又爱又恨.不管怎么样,如今 I'll be back,笔者再也不会重复一样的悲剧. 按键消抖说傻不傻说难不难.所谓傻,它因为原理不仅简单(就是延迟几下下而已),而且顺序语言(C语言)也有无数不尽的例子.所谓难,那是因为人们很难从单片机的思维跳出来 ... 此外,

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验四:按键模块③ — 单击与双击

实验四:按键模块③ — 单击与双击 实验三我们创建了“点击”还有“长点击”等有效按键的多功能按键模块.在此,实验四同样也是创建多功能按键模块,不过却有不同的有效按键.实验四的按键功能模块有以下两项有效按键: l 单击(按下有效): l 双击(连续按下两下有效). 图4.1 单击有效按键,时序示意图. 实验四的“单击”基本上与实验三的“点击”一模一样,既按键被按下,经过消抖以后isSClick信号被拉高一个时钟,结果如图4.1所示,过程非常单调.反之,“双击”实现起来,会比较麻烦一些,因为我们还要

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验五:按键模块④ — 点击,长点击,双击

实验五:按键模块④ — 点击,长点击,双击 实验二至实验四,我们一共完成如下有效按键: l 点击(按下有效) l 点击(释放有效) l 长击(长按下有效) l 双击(连续按下有效) 然而,不管哪个实验都是只有两项“功能”的按键模块而已,如今我们要创建三项“功能”的按键模块,亦即点击(按下有效),长击,还有双击.实验继续之前,让我们先来复习一下各种有效按键. 图5.1 点击(按下有效). 如图5.1所示,所谓点击(按下有效)就是按键按下以后,isSClick信号(Single Click) 产生一

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

实验七:PS/2模块① — 键盘 实验七依然也是熟烂的PS/2键盘.相较<建模篇>的PS/2键盘实验,实验七实除了实现基本的驱动以外,我们还要深入解PS/2时序,还有PS/2键盘的行为.不过,为了节省珍贵的页数,怒笔者不再重复有关PS/2的基础内容,那些不晓得的读者请复习<建模篇>或者自行谷歌一下. 市场上常见的键盘都是应用第二套扫描码,各种扫描码如图7.2所示.<建模篇>之际,笔者也只是擦边一下PS/2键盘,简单读取单字节通码与断码而已.所谓单字节通码,就是有效的按下

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

实验八:PS/2模块② — 键盘与组合键 实验七之际,我们学习如何读取PS/2键盘发送过来的通码与断码,不过实验内容也是一键按下然后释放,简单按键行为而已.然而,实验八的实验内容却是学习组合键的按键行为. 不知读者是否有类似的经历?当我们使用键盘的时候,如果5~6按键同时按下,电脑随之便会发出“哔哔”的警报声,键盘立即失效.这是键盘限制设计,不同产品也有不同限制的按键数量.默认下,最大按键数量是5~7个.所谓组合键就是两个以上的按键所产生的有效按键.举例而言,按下按键 <A> 输出“字符a”,

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十:PS/2模块④ &mdash; 普通鼠标

实验十:PS/2模块④ - 普通鼠标 学习PS/2键盘以后,接下来就要学习 PS/2 鼠标.PS/2鼠标相较PS/2键盘,驱动难度稍微高了一点点,因为FPGA(从机)不仅仅是从PS/2鼠标哪里读取数据,FPGA还要往鼠标里写数据 ... 反之,FPGA只要对PS/2键盘读取数据即可.然而,最伤脑筋的地方就在于PS/2传输协议有奇怪的写时序. 图10.1 从机视角,从机读数据. 为了方便理解,余下我们经由从机的视角去观察PS/2的读写时序.图10.1是从机视角的读时序,从机都是皆由 PS2_CLK