在以前使用单片机对PS2进行解码的时候,一句话就是苦。 如果是CPLD 或者 FPGA 的前提下,PS2的解码才有意义。
PS2的接口如上图,除了Pin 5 和 Pin 1 其他的引脚对解码没有什么意义。而下图是PS2协议的时序图。PS2协议对数据的移位是“Clock 的下降沿”有效。PS2时钟的频率比较慢,大约是10Khz左右。
第N位
属性
0
开始位
1~8
数据位
9
校验位
10
结束位
PS2的一帧是11位。对PS2进行解码时,除了第1~8位数据位以外,其余的位都可以无视。
普通计算机采用的都是“编码键盘”,但是“编码键盘”的“编码方式”有分为“第一套”“第二套”和“第三套”。“第二套”的编码使用较为普遍,大致上的民用键盘都是采用“第二套”编码方式。
键盘的编码有“通码”(Make)和“断码”(Break)之分。看得简单一点就是,“通码”是某按键的“按下事件”,“断码”是某按键的“释放事件”。
假设,我按下“W”键不放,每秒大约会输出10个“0x1d”的“通码”。然后我释放“W”键,就会输出 “0xF0 0x1d” 的“断码”。 编码键盘还有一个老规则,就是一次只有一个输出。
再假设我按下“W”键不放,然后我再按下“X”键不放,那么会输出“0x1d”“0x22”“0x22”........ 的通码。如果此时我放开“X”键,就会输出“0xf0 0x22”的“断码”,此时“W”键的“通码”已经无效。但是当我释放“W”键的时候,依然会输出“0xf0 0x1d”, “W”键的“断码”。
至于组合键 “ Shift + ?” ,“ Ctrl + ?”都是软件的工作,我们就无视吧。
实验八的目的很简单,实验主要对PS2进行解码,然后对“通码”进行判断,然而对 “断码”采取忽略的行为。
在组合模块 ps2_module.v 中包含了两个功能块: “电平检测模块” detect_module.v 和 "ps2解码模块" ps2_decode_module 。detect_module.v 的添加时为了检查 PS2 时钟信号的“下降沿”,此外也是为了使模块的建立更简单。ps2_decode_module.v 如字面上的意思,对每帧(十一位一组数据)的数据进行解码和过滤的行为,最后将一字节数据输出 PS2_Data,然后 PS2_Done_Sig 产生一个高脉冲,表示完成一次性的操作。
嗯!这个功能模块的解释可以参考实验实验三。
ps2_decode_module.v 最主要的核心是在第31~55行。同样这个模块也是采用“仿顺序操作”的写法。步骤i一开始会停留在0(33行),当检查到 H2L_Sig 的变化,我们知道PS2一帧数据的第第0位是开始位,所以无视。在接下来的8个位都是数据位,PS2的数据位是从最低位开始,最高位结束。然后对 PS2_Data_Pin_In 引脚进行读值(第36~37行)。
PS2一帧数据的第9~10位是校验位和结束位,执行无视操作(39~40行)。在 i 等于 11的时候(42行),我们要进行判断,读取的“数据是通码还是断码?”,如果是“通码”(44行),就进入49行随后产生一个高脉冲的完成信号(49~53行),最后返回33行,等待下一次的操作。
如果是“断码”(43行),进入46行随后对“断码”采取无视的操作(46~47行),最后产生一个高脉冲的完成信号,然后返回第33行(49~53行)。
实验八的PS2解码仅对“通码”执行操作,相反却对“断码”采取无视的行为。实验八的重点主要是如何实现“PS2时钟采样”和“PS2数据采样”操作?
从上图中我们可以看到,黑金的时钟频率比起PS2的时钟频率,大约是2000倍。而 detect_module.v 就是利用这2000个时钟信号去采集每个PS2时钟的下降沿(小箭头)。
当detect_module.v 检测到ps2时钟产生下降沿,(detect_module.v 32行)由于布尔的表达式关系,就会产生一个高脉冲经H2L_Sig 信号。PS2协议的一帧数据大约是11个数据位,换句话说就会产生11个下降沿,也就是说会产生11次高脉冲的H2L_Sig。
PS2时钟在每一个下降沿的时候,PS2数据就会“设置”(移位)数据,而 ps2_decode_module.v 中的 31~55行,就是对PS2数据的移位进行“数据采样”。假设PS2时钟在第一次的下降沿,自然而然PS2数据会“设置”第0位数据( ps2_decode_module.v 34行 ),ps2_decode_module.v 采取无视的操作,接下来的八位数据位才是真正需要的数据位。
有一点比较值得注意的是在 ps2_decode_module.v 第42~44行。从上面我们知道,黑金的时钟频率对ps2的时钟频率有 2000倍的相差,从另一个角度去理解的话,距离每一次PS2时钟信号的下降沿,黑金拥有2000个可以执行的时间。然而在第42~44行只用了一个时间去判断,余下还剩下1999个可空闲的时间。
也就是说,采样和被采样的频率如果相差越大,采样方可执行的空余的时间就越多,在设计上就越轻松。
完成扩展图:
实验八总归也就是属于PS2解码的部分。然而笔者在设计上,对 ps2_detect_module.v 添加了 PS2_Done_Sig, 这个信号无疑是表示了 “一次性操作”已经完成的次数。因为编码的键盘,只要按着不放就会一直发送“通码”,如果笔者针对“通码”去设计的话,笔者永远“只能得到键盘的数据”,已不是“通码”和“次数”这两方。
实验八演示中的“命令控制模块”cmd_control_module.v 会对“W”,“X”和“Ctrl”键作出反应。在 cmd_control_module.v 的输出是4位的信号,初始值是4'b0001。当“W”键按下,就会产生左移的效果,“X”键则是右移。至于“Ctrl”键会产生“互换”。
第16~35行是 cmd_control_module.v 的核心部分,当PS2_Done_Sig 产生高脉冲(23行),该控制模块就会对 PS2_Data 产生反应。8'h1d 是“W”键的通码,而 8'h22 是“X”键的通码,至于 8'h14 是“Ctrl”键的通码。
“W”键,产生左移效果(26~27行),
“X”键,产生右移效果(29~30行),
“Ctrl”键,产生互换效果(32~33行)。
exp08_demo.v 是 ps2_module 和 cmd_control_module.v 的组合模块。ps2_module.v 的具体介绍请参考实验七。
在 cmd_control_module.v 中的第23行,PS2_Done_Sig 很有效被利用来控制执行的次数。而且cmd_control_module.v 仅对 “W”“X”“Ctrl”键的“通码”产生反应而已,换一句话说,cmd_control_module.v 只会在以上三个键在“按下”或者“按下不放”才会有所反应。
完成扩展图:
我以前接触过很多有关 PS2的解码实验,都非常执着与“字符”有关。其实无论是什么输出,只要有效的利用“通码”就不成问题。最重要的是如何编辑这些“通码”称为有效的“命令”!?
还有另一点,编码键盘虽然很方便,但是编码键盘是无法取代独立键盘。在实验八演示中,在两个键同时按下的时候,仅有按下最迟的一方才有输出“通码”的权利。相反独立按键是完全“并行”执行, 不会出现对输出争先恐后的事件。所以在许多方面编码键盘还是不完美。
最后还有一点,就是所有拥有PS2接口的设备,一般上都是“低速”设备,这一点要好好的记住。
实验八主要是表达“低级建模”在“读取”或者“采样”上发挥的效果。在众多同样的试验中,笔者常常可以看到所有模块都是往一个 .v 文件里面写,虽然在编辑上会很方便,但是在“理解的方式上”,“理解的形状上”又是怎么一回事?
同样的,实验八和实验七建模的数量都是不相上下,或者可以说这是“低级建模”的普遍缺点,但是读者是不是会察觉到?当你在组合模块,将一个一个模块实例化,思路无比是清晰,而且设计的工作也容易?
好吧,换另一个话题去讨论实验八的重点:
always @ ( posedge CLK )
......
always @ ( posedge CLK )
......
如上面的代码,在同样一个模块中出现了两个always 块,在一般的情况下,我们都会“从上往下”读,类似顺序操作那样看待。如果你的理解能力好,或者已经习惯了,那么这事儿不成问题。但是在实际的操作中,所有 always块都是“并行”执行者,很多新手都会被自身的“假概念”误导。如果将上述的问题反映在建模上,往往会使更多新手进入瓶颈。
就像在第二章的实验,笔记一直在强调“并行”的概念和重要性。“低级建模”的多模块设计无疑是能很好的发挥“并行”的概念。如实验八演示中,detect_module.v,ps2_decode_module.v,cmd_control_module.v ,它们在每一个时钟的上升都是在独立工作着。至于它们会不会被触发或者协调执行,完全是依赖自身早已被设定好的功能。
detect_module.v 在每一个时钟源的上升沿(posedge CLK),如果检测到 PS2时钟发生下降沿,就会经 H2L_Sig 产生高脉冲,除此之外它的工作就结束了。
然而 ps2_decode_module.v 在每一个时钟源的上升沿(posedge CLK),如果检测到 H2L_Sig 的高脉冲,就会对 PS2数据一帧数据的执行采集和过滤的动作,最后将完整的数据输出至 PS2_Data, 然后 PS2_Done_Sig 产生一个时钟的高脉冲,它就完工了。
cmd_control_module.v 在每一个时钟源的上升沿(posedge CLK),如果被PS2_Done_Sig 触发,那么它就会依据 PS2_Data 的数据作出相对的反应,否则它就一直保持沉默。
虽然整体的主观上,它们会如同连线关系一方接着触发着一方。但是实际上,每一个模块都保持者独立关系,只有在“触发”的时候才工作。当然“低级建模”所强调的准则不仅是重视在“并行”的理解上,还有好多好多的故事会发生在这本笔记的后面。