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

实验七:PS/2模块① — 键盘

实验七依然也是熟烂的PS/2键盘。相较《建模篇》的PS/2键盘实验,实验七实除了实现基本的驱动以外,我们还要深入解PS/2时序,还有PS/2键盘的行为。不过,为了节省珍贵的页数,怒笔者不再重复有关PS/2的基础内容,那些不晓得的读者请复习《建模篇》或者自行谷歌一下。

市场上常见的键盘都是应用第二套扫描码,各种扫描码如图7.2所示。《建模篇》之际,笔者也只是擦边一下PS/2键盘,简单读取单字节通码与断码而已。所谓单字节通码,就是有效的按下内容,例如 <A> 键被按下的时候会输出 1C。所谓单字节断码,就是有效的释放内容,例如 <A> 键被释放的时候会输出 F0 1C。

除了单字节的通码以外,PS/2键盘也有双字节通码与断码。所谓双字节通码,例如 <R CTRL>键被按下时候会输出 E0 14;反之,所谓双字节断码,例如 <R CTRL> 键被释放时候会输出 E0 F0 14。不管是单字节还是双字节,断码都包含F0。

除了上述的要求以外,笔者还要实现双组合键,例如 <Ctrl> + <A>。不仅而已,笔者也要实现三组合键,例如 <Ctrl> + <Alt > + <A>。常识上,这些任性的要求都是软件的工作,然而这种认识也仅局限小气的脑袋而已。换做笔者,笔者就算霸王硬上弓,笔者也要使用Verilog实现这些任性的要求。

未进入实验之前,笔者需要强调一下!Verilog究竟如何驱动PS/2设备,然后又如何实现软件的工作,这一切Verilog自有方法。不管C语言还有单片机这对活宝,驱动PS/2设备再怎么神,它们也没有资格在旁指指点点。读者千万也别尝试用借用它们的思路去思考Verilog,否则后果只有撞墙而已。

图7.1 PS/2键盘发送数据(主机视角)。

PS/2传输协议与一般的传输协议一样,除了主从之分之余,它也有“读写”两个访问的方向。除非有特殊的需要,不然从机(FPGA)是不会访问PS/2键盘的内部。换之,从机只要不停从PS/2键盘哪里读取数据即可 ... 换句话说,驱动PS/2键盘仅有读数据这一环而已,然而PS/2键盘是主机,FPGA是从机。(主机的定义是时钟信号的拥有者)

不管是何种传输协议,只要协议当中存有时钟信号,那么“什么时钟沿,怎样对待数据”这个铁则是不会改变的。如图7.1所示,那是PS/2键盘发送数据的时序图,亦即上升沿设置数据(输出数据)。根据主机视角,除了开始位以外,PS/2键盘一共利用10个上升沿输出10位数据。

图7.2 第二套键盘的扫描码。

图7.3 PS/2键盘发送数据,FPGA读取数据(从机视角)。

PS/2传输数据一般都是一帧一帧互相往来,一帧有11位数据。Bit 0位为拉低的开始位,Bit 1~8 是由低自高的数据位,Bit 9为校验位,Bit 10为拉高的结束位。根据从机视角,如图7.3所示,PS/2键盘在发送数据的时候,FPGA是下降沿锁存数据(读取数据)。PS2_CLK信号一共产生了11个下降沿,FPGA也根据这11次下降沿锁存11位数据。

图7.4 检测PS2_CLK的电平变化。

为了察觉下降沿,我们可以借用F2~F1的力量,对此Verilog可以这样表示:

reg F2,F1;
always @ ( posedge CLOCK )
    { F2,F1 } <= { F1,KEY }; 

然后下降沿声明为即时:

wire isH2L = ( F2 == 1 && F1 == 0 );

那么,从机接收1帧11位数据的操作可以这样描述,结果如代码7.1所示:

1.        case( i )
2.    
3.            0:
4.            if(isH2L) i <= i + 1’b1; 
5.            1,2,3,4,5,6,7,8:
6.            if(isH2L) D1[i-1] <= PS2_DAT; i <= i + 1’b1; end
7.            9:
8.            if(isH2L) i <= i + 1’b1; 
9.            10:
10.            if(isH2L) i <= 4‘d0;
11.    
12.        endcase

代码7.1

不过,为了方便控制代码7.1,笔者设法将代码7.1设置为伪函数,结果如代码7.2所示:

1.       parameter RDFUNC = 4’d4;
2.        ......
3.        case( i )
4.            ......
5.            /*********************/
6.            4:
7.            if(isH2L) i <= i + 1’b1; 
8.            5,6,7,8,9,10,11,12:
9.            if(isH2L) D1[i-5] <= PS2_DAT; i <= i + 1’b1; end
10.            13:
11.            if(isH2L) i <= i + 1’b1; 
12.            14:
13.            if(isH2L) i <= Go;
14.    
15.        endcase

代码7.2

理解这些以后,我们就要开始认识PS/2键盘的按键行为了。

图7.5 PS/2键盘,按一下又释放。

假设笔者轻按一下<A>然后又释放,如图7.5所示,PS/2键盘先会发送一帧8’h1C的通码,然后又发送两帧8’hF0 8’h1C的断码,这是PS/2键盘最常见的按键行为。

图7.6 PS/2键盘,长按又释放。

如果笔者长按 <A> 键不放,如图7.6所示,PS/2键盘会不停发送通码,直至释放才发送断码。至于长按期间,通码的发送间隔大约是100ms,亦即1秒内发送10个通码。

图7.7 PS/2键盘,有效通码。

一般而言,我们都会选择通码放弃断码,为了表示一次性,而且也是有效性的通码。每当一帧通码完成接收,isDone就会产生一个高脉冲,以示一次性而且有效的通码已经接收完毕。

图7.8 实验七的建模图。

如图7.8所示,那是实验七的建模图,其中名为 ps2_demo的组合模块,内容包含实验六的 smg_basemod 以外,该组合模块也包含 ps2_funcmod。PS/2功能模块接收来PS/2键盘发送过来的数据,然后再经由oData驱动smg_basemod的iData,最后并将通码显示在数码管上。

ps2_funcmod.v

图7.9 PS/2功能模块。

图7.9是PS/2功能模块的建模图,左方是PS2_CLK与PS2_DAT顶层信号的输入,右方则是1位oTrig与8位oData。具体内容,让我们来瞧瞧代码:

1.    module ps2_funcmod
2.    (
3.         input CLOCK, RESET,
4.         input PS2_CLK, PS2_DAT,
5.         output oTrig,
6.         output [7:0]oData
7.    );
8.         parameter BREAK = 8‘hF0;
9.         parameter FF_Read = 5‘d4;

以上内容为相关的出入端声明以及常量声明。第8行是断码的常量声明(第一帧),第9行则是伪函数的入口。

10.     
11.         /******************/ // sub
12.    
13.        reg F2,F1;
14.         
15.        always @ ( posedge CLOCK or negedge RESET )
16.             if( !RESET )
17.                  { F2,F1 } <= 2‘b11;
18.              else
19.                  { F2, F1 } <= { F1, PS2_CLK };
20.    
21.         /******************/ // core
22.         
23.         wire isH2L = ( F2 == 1‘b1 && F1 == 1‘b0 );
24.         reg [7:0]D1;
25.         reg [4:0]i,Go;
26.         reg isDone;
27.         
28.         always @ ( posedge CLOCK or negedge RESET )
29.             if( !RESET )
30.                  begin
31.                         D1<= 8‘d0;
32.                         i <= 5‘d0;
33.                         Go <= 5‘d0;
34.                         isDone <= 1‘b0;
35.                    end
36.               else

以上内容是周边操作以及相关寄存器声明,还有它们的复位操作。周边操作主要用来检测PS2_CLK的电平变化。第23行是下降沿的即时声明。第24~26行是相关的寄存器声明,第30~34行则是这些寄存器的复位操作。

37.                    case( i )
38.                     
39.                          0:
40.                          begin i <= FF_Read; Go <= i + 1‘b1; end
41.                      
42.                          1:
43.                          if( D1 == BREAK ) begin i <= FF_Read; Go <= 5‘d0; end
44.                          else i <= i + 1‘b1;
45.                          
46.                          2:
47.                          begin isDone <= 1‘b1; i <= i + 1‘b1; end
48.                          
49.                          3:
50.                          begin isDone <= 1‘b0; i <= 5‘d0; end
51.                          
52.                          /*************/ // PS2 read function
53.                          
54.                          4:  // Start bit
55.                          if( isH2L ) i <= i + 1‘b1; 
56.                          
57.                          5,6,7,8,9,10,11,12:  // Data byte
58.                          if( isH2L ) begin D1[ i-5 ] <= PS2_DAT; i <= i + 1‘b1; end
59.                          
60.                          13: // Parity bit
61.                          if( isH2L ) i <= i + 1‘b1;
62.                          
63.                          14: // Stop bit
64.                          if( isH2L ) i <= Go;
65.    
66.                     endcase

以上内容为核心操作。其中步骤4~14(第54~64行)是读取1帧数据的伪函数,入口地址是4。步骤0~3则是主要操作,过程如下:

步骤0,进入伪函数准备读取第一帧数据。读完第一帧数据以后便返回步骤1。

步骤1,判断第一帧数据是否为断码?是,进入伪函数,完整第二帧数据的读取,然后返回步骤指向为0。否,继续步骤。

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

67.                     
68.        /************************************/
69.         
70.         assign oTrig = isDone;
71.         assign oData = D1;
72.         
73.         /*************************************/
74.      
75.    endmodule

以上内容为输出驱动的声明。

ps2_demo.v

组合模块 ps2_demo的联系部署请复习图7.8。

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.    
9.         wire [7:0]DataU1;
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.              .oData( DataU1 ),  // > U2
18.              .oTrig()
19.         );
20.         
21.       smg_basemod U2
22.        (
23.            .CLOCK( CLOCK ),
24.            .RESET( RESET ),
25.            .DIG( DIG ),  // > top
26.            .SEL( SEL ),  // > top
27.            .iData( { 16‘h0000, DataU1 } ) // < U1
28.        );
29.                 
30.    endmodule

上述代码没有什么特别,除了第18行,无视触发信号的输出以外,还有第27行其 16’h0000 则表示数码管的前四位皆为0,后两位则是通码。编译完后便下载程序。

如果笔者按下 <A> 键,数码管便会显示1C;如果笔者释放 <A> 键,数码管也是显示1C,期间也会发生一丝的闪耀。由于ps2_funcmod的暂存空间D直切驱动oData,所以数码管事实反映ps2_funcmod的读取状况。从演示上来看的确如此,不过在时序身上,唯有通码读取成功以后,才会产生触发信号。

细节一:主操作与伪函数的距离

1.    case( i )
2.                     
3.        0:
4.        begin i <= FF_Read; Go <= i + 1‘b1; end      
5.        1:
6.        if( D1 == BREAK ) begin i <= FF_Read; Go <= 5‘d0; end
7.        else i <= i + 1‘b1;      
8.        2:
9.        begin isDone <= 1‘b1; i <= i + 1‘b1; end  
10.        3:
11.        begin isDone <= 1‘b0; i <= 5‘d0; end
12.        /*************/ // PS2 read function 
13.        4:  // Start bit
14.        if( isH2L ) i <= i + 1‘b1; 
15.        5,6,7,8,9,10,11,12:  // Data byte
16.        if( isH2L ) begin D1[ i-5 ] <= PS2_DAT; i <= i + 1‘b1; end
17.        13: // Parity bit
18.        if( isH2L ) i <= i + 1‘b1;                  
19.        14: // Stop bit
20.        if( isH2L ) i <= Go;
21.    
22.    endcase

代码7.3

如代码7.3所示,步骤0~3是主操作,步骤4~14则是伪函数,期间主操作的下任步骤直接连接伪函数的入口。一般而言,如果模块的核心操作是小功能的话,这样做倒没有什么问题。反之,如果遇上复杂功能的核心操作,主操作与伪函数之间必须隔空一段距离。根据笔者的习惯,默认下都会设为16或者32,不过也有例外的情况。

23.    case( i )
24.                     
25.        0:
26.        begin i <= FF_Read; Go <= i + 1‘b1; end      
27.        1:
28.        if( D1 == BREAK ) begin i <= FF_Read; Go <= 5‘d0; end
29.        else i <= i + 1‘b1;      
30.        2:
31.        begin isDone <= 1‘b1; i <= i + 1‘b1; end  
32.        3:
33.        begin isDone <= 1‘b0; i <= 5‘d0; end
34.        /*************/ // PS2 read function 
35.        16:  // Start bit
36.        if( isH2L ) i <= i + 1‘b1; 
37.        17,18,19,20,21,22,23,24:  // Data byte
38.        if( isH2L ) begin D1[ i-5 ] <= PS2_DAT; i <= i + 1‘b1; end
39.        25: // Parity bit
40.        if( isH2L ) i <= i + 1‘b1;                  
41.        26: // Stop bit
42.        if( isH2L ) i <= Go;
43.    
44.    endcase

代码7.4

如代码7.4所示,伪函数的入口地址已经设为16,为此主操作与伪函数之间有16个步骤的距离。如此一来,主操作拥有更多的步骤空间。

细节二:完整的个体模块

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

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

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

时间: 2024-10-26 22:33:32

【黑金原创教程】【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模块③ — 键盘与多组合键 笔者曾经说过,通码除了单字节以外,也有双字节通码,而且双字节通码都是 8’hE0开头,别名又是 E0按键.常见的的E0按键有,<↑>,<↓>,<←>,<→>,<HOME>,<PRTSC> 等编辑键.除此之外,一些组合键也是E0按键,例如 <RCtrl> 或者 <RAlt> .所以说,当我们设计组合键的时候,除了考虑“左边”的组合键以外,我们也要考虑“右边”的组合键.&

【黑金原创教程】【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