【黑金原创教程】【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模块① — 键盘的更多相关文章
- [黑金原创教程] FPGA那些事儿《设计篇 III》- 图像处理前夕·再续
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《设计篇 II》- 图像处理前夕·续
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《设计篇 I》- 图像处理前夕
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《数学篇》- CORDIC 算法
简介 一本为完善<设计篇>的书,教你CORDIC算法以及定点数等,内容请看目录. 贴士 这本教程难度略高,请先用<时序篇>垫底. 目录 Experiment 01:认识CORD ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】原创教程连载导读【连载完成,共二十九章】
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验八:PS/2模块② — 键盘与组合键
实验八:PS/2模块② — 键盘与组合键 实验七之际,我们学习如何读取PS/2键盘发送过来的通码与断码,不过实验内容也是一键按下然后释放,简单按键行为而已.然而,实验八的实验内容却是学习组合键的按键行 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验九:PS/2模块③ — 键盘与多组合键
实验九:PS/2模块③ — 键盘与多组合键 笔者曾经说过,通码除了单字节以外,也有双字节通码,而且双字节通码都是 8’hE0开头,别名又是 E0按键.常见的的E0按键有,<↑>,<↓ ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】连载导读
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二:按键模块① - 消抖
实验二:按键模块① - 消抖 按键消抖实验可谓是经典中的经典,按键消抖实验虽曾在<建模篇>出现过,而且还惹来一堆麻烦.事实上,笔者这是在刁难各位同学,好让对方的惯性思维短路一下,但是惨遭口 ...
随机推荐
- MATLAB:读取mat文件中物体的三维坐标,显示三维模型
在MATLAB中建立一个脚本show3Dmat.m文件,编写代码: clc; clear; %%read 3D data load('E:\博士\深度学习与三维重建\代码实现\3DRecGAN\X_Y ...
- 磁盘格式化/磁盘挂载/手动增加swap空间
4.5/4.6 磁盘格式化 4.7/4.8 磁盘挂载 4.9 手动增加swap空间 磁盘格式化 查看centos7支持的文件系统格式 cat /etc/filesystem,centos7默认的文件 ...
- sqlserver修改主机名
sqlserver迁移后,主机和原机器不符,将系统修改主机名后,数据库代理服务.邮件服务无法启动 执行下面语句,检查sqlserver中windows主机名 -- 检查SQL Server中的&quo ...
- Window 10 :如何彻底关闭:Windows Defender Service(2015-12-20日更新)
Window 10 :如何彻底关闭:Windows Defender Service? 网上流传的什么组策略gpeidt.msc方法,什么安装其他的杀软之类的方法都很麻烦,且有弊病! 其实很简单: 利 ...
- redis 的hash数据类型
hash的常用命令 1.hset hset key field value 将哈希表key中的域field的值设为value 如果key不存在,一个新的哈希表被创建并进行HSET操作 如果field是 ...
- windows下WAMP php5.x redis扩展
其解压到php的扩展目录ext下,在php.ini文件中扩展部分增加一行:extension=php_redis.dll 新增下载地下: php5.3 http://download.csdn.net ...
- oracle自定义类型 示例
) ); ---自定义类型传参给存储过程,示例如下: create or replace procedure dropWf is cursor c_tenant is and t.id not in ...
- python缓存装饰器,第二种方式(二)
来个简单的装饰器 def cached_method_result(fun): """方法的结果缓存装饰器""" @wraps(fun) d ...
- Oauth2.0(二):开放平台
上一节说到Oauth2.0 的交互模型.模型涉及到三方:资源拥有者.客户端.服务提供方.其中,服务提供方包含两个角色:鉴权服务器和资源服务器.鉴权服务器负责对用户进行认证,并授权给客户端权限.认证这一 ...
- FileSaver.js 浏览器导出Excel文件
限制一:不同浏览器对 blob 对象有不同的限制 具体看看下面这个表格(出自 FileSaver.js): Browser Constructs as Filenames Max Blob Size ...