zoukankan      html  css  js  c++  java
  • 【黑金原创教程】【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。

    至于时序方面呢 ...

    clip_image002

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

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

    clip_image004

    图9.2 E0按键与组合键①。

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

    clip_image006

    图9.3 E0按键与组合键②。

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

    clip_image008

    图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。

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

    clip_image010

    图9.5 实验九的建模图。

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

    ps2_funcmod.v

    clip_image012

    图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编辑键,设计思路也是一样的,不过后者比较偏向软件。对此,读者只要基于实验九,再简单扩展一下即可。

    细节一:完整的个体模块

    clip_image014

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

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

  • 相关阅读:
    Supervisor 管理进程,Cloud Insight 监控进程,完美!
    【灵魂拷问】你为什么要来学习Node.js呢?
    Web数据交互技术
    请求与上传文件,Session简介,Restful API,Nodemon
    Express服务器开发
    HTTP协议,到底是什么鬼?
    大学我都是自学走来的,这些私藏的实用工具/学习网站我贡献出来了,建议收藏精品推荐
    Node.js安装使用-VueCLI安装使用-工程化的Vue.js开发
    React开发环境准备
    【可视化】Vue基础
  • 原文地址:https://www.cnblogs.com/alinx/p/3898704.html
Copyright © 2011-2022 走看看