实验十: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的下降沿读取1帧为11位的数据 ... 期间,有11个下降沿,不过为了方便调用,笔者将其整理为伪函数,结果如代码10.1所示:

  1. 1. 32: // Start bit
  1. 2. if( isH2L ) i <= i + 1'b1;
  1. 3.
  1. 4. 33,34,35,36,37,38,39,40: // Data byte
  1. 5. if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1; end
  1. 6.
  1. 7. 41: // Parity bit
  1. 8. if( isH2L ) i <= i + 1'b1;
  1. 9.
  1. 10. 42: // Stop bit
  1. 11. if( isH2L ) i <= Go;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

代码10.1

图10.2从机视角,从机写数据,第一帧。

首先我们必须明白,PS2_CLK 与 PS2_DAT 信号是双向的IO口,由于FPGA是从机的关系,所以FPGA一开始都处于输入状态,而PS/2鼠标一开始则处于输出状态。假设 isQ1 是FPGA针对PS2_CLK的输出控制,isQ2 是FPGA针对 PS2_DAT 的输出控制,然而复位状态(初始化)都为拉低状态。

FPGA为了获取数据的发送权,首先它必须摘取 PS2_CLK,亦即拉高 isQ1然后又拉低PS2_CLK 100us。

事后,FPGA必须释放 PS2_CLK,亦即关闭 IO或者拉低 isQ1,期间顺手拉高 isQ2,让 PS2_DAT 成为输出状态。那么重点来了,读者是否看见黄色的圈圈?当FPGA释放 PS2_CLK瞬间,PS/2鼠标早已拉低 PS2_CLK,FPGA也因此错过PS2_CLK第一次珍贵的下降沿。在此,读者需要好好注意,因为许多资料都忽略这点,笔者也不小心扑街好几次。

读者需要知道,根据PS/2传输协议,从机(FPGA)不管怎样挣扎也没有 PS2_CLK的拥有权。换句话说,无论从机(FPGA)是写数据还是读数据,从机(FPGA)都必须借助主机(PS/2鼠标)发送过来的 PS2_CLK下降沿。如果我们不小心忽略第一个下降沿,余下几个下降沿,我们也会搞错次序,最终造成数据读取错位的悲剧。

FPGA无论是写数据还是读数据,从机都必须借用 PS2_CLK 的下降沿。FPGA释放PS2_CLK之后,isQ2立即拉高,PS2_DAT 也随之拉低以示一帧数据的起始位 ... 直至下一个下降沿到来为此。

FPGA一共用了10个下降沿,即T0~T9发送8位数据位,1位校验位,还有1位停止位。T9过去不久,PS2_CLK就会引来上升沿,此刻PS/2鼠标则会反馈1位应答位,不过此刻也无暇招呼它。T10之际,那是最后一个下降沿,FPGA拉低isQ2让PS2_DAT成为输入状态,并且读取应答位,然而懒惰的笔者决定无视它。就这样从机写一帧数据就结束了。

在这里,读者是否觉得从机写一帧数据比从机读一帧数据还要麻烦呢?没错,笔者也这样觉得。可是,麻烦归麻烦,余下我们还要完成Verilog的描述工作,结果如代码10.2所示:

  1. 1. /****************/ // PS2 Write Function
  1. 2.
  1. 3. 32: // Press low CLK 100us
  1. 4. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
  1. 5. else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
  1. 6.
  1. 7. 33: // Release PS2_CLK and set in, PS2_DAT set out
  1. 8. begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
  1. 9.
  1. 10. 34: // start bit
  1. 11. begin rDAT <= 1'b0; i <= i + 1'b1; end
  1. 12.
  1. 13. 35,36,37,38,39,40,41,42,43: // Data byte
  1. 14. if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
  1. 15.
  1. 16. 44: // Stop bit
  1. 17. if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
  1. 18.
  1. 19. 45: // Ack bit
  1. 20. if( isH2L ) begin i <= i + 1'b1; end
  1. 21.
  1. 22. 46: // PS2_DAT set in
  1. 23. begin isQ2 <= 1'b0; i <= i + 1'b1; end
  1. 24. 。。。。。。
  1. 25.
  1. 26. assign PS2_CLK = isQ1 ? rCLK : 1'bz;
  1. 27. assign PS2_DAT = isQ2 ? rDAT : 1'bz;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

代码10.2

如代码10.2所示:

步骤32,来拉高 isQ1又拉低 rCLK 持续 100us。

步骤33,拉低 isQ1释放 PS2_CLK,然后又拉高 isQ2 准备发送数据。

步骤34,重点!由于错过第一个下降沿,我们只能拉低 rDAT产生起始位。

步骤35~43,发送8位数据位,还有1位校验位。

步骤44,发送结束位。

步骤45,无视应答位。

步骤46,拉低isQ2,让PS2_CLK为输入状态。

图10.3主机视角,从机写数据,第一帧。

接下来,让我们经由主机的视角去观察,主机如何读取从机发送过来的数据。初始状态,主机亦即 PS/2鼠标掌握 PS2_CLK与 PS2_DAT。一旦从机即FPGA载取PS2_CLK,并且拉低输出100us,立刻PS/2鼠标已经理解从机准备发送数据。100us过后,FPGA释放 PS2_CLK并且载取PS2_DAT,PS/2鼠标便开始产生时钟。如图10.2所示,PS/2鼠标都是借用上升沿读取FPGA发送过来的数据。大约11个上升沿过后,PS/2鼠标就结束读取动作,并且反馈应答位。

图10.4主机视角,从机写数据,第二帧。

每当主机接收完毕一帧数据,就会反馈一帧数据。如图10.2所示,那是图10.1的下半部分,依然是从主机视角观察从机写数据。图中显示,每当PS/2鼠标(主机)接收完毕一帧数据以后,PS/2鼠标便会反馈一帧数据,亦即PS/2鼠标会再度借用10个上升沿发送一帧数据。期间,FPGA(从机)的 PS2_CLK 与 PS2_DAT都都处于输入状态。

图10.5主机视角,从机写数据,完整时序。

图10.5是图10.3与图10.4的完整时序(主机视角)。

图10.6从机视角,从机写数据,第二帧。

既然主机反馈一帧数据,那么从机也不能无视,如图10.6所示,那是经由从机视角观察从机如何读取下一帧数据。期间,从机借用11个下降沿读取1一帧数据。

图10.7从机视角,从机写数据,完整时序。

图10.7是图10.2与图10.5的完整时序(从机视角)。

为此,代码10.2可以继续扩张,结果如代码10.3所示:

  1. 1. /****************/ // PS2 Write Function
  1. 2.
  1. 3. 32: // Press low CLK 100us
  1. 4. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
  1. 5. else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
  1. 6.
  1. 7. 33: // Release PS2_CLK and set in, PS2_DAT set out
  1. 8. begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
  1. 9.
  1. 10. 34: // start bit
  1. 11. begin rDAT <= 1'b0; i <= i + 1'b1; end
  1. 12.
  1. 13. 35,36,37,38,39,40,41,42,43: // Data byte
  1. 14. if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
  1. 15.
  1. 16. 44: // Stop bit
  1. 17. if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
  1. 18.
  1. 19. 45: // Ack bit
  1. 20. if( isH2L ) begin i <= i + 1'b1; end
  1. 21.
  1. 22. 46: // PS2_DAT set in
  1. 23. begin isQ2 <= 1'b0; i <= i + 1'b1; end
  1. 24.
  1. 25. 47,48,49,50,51,52,53,54,55,56,57: // 1 Frame
  1. 26. if( isH2L ) i <= i + 1'b1;
  1. 27.
  1. 28. 58: // Return
  1. 29. i <= Go;
  1. 30.
  1. 31. ......
  1. 32.
  1. 33. assign PS2_CLK = isQ1 ? rCLK : 1'bz;
  1. 34. assign PS2_DAT = isQ2 ? rDAT : 1'bz;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

代码10.3

步骤32~46曾在前面说过,即从机发送一帧数据。余下步骤47~57(共有11个步骤),主要用来过滤主机反馈过来的下一帧数据,步骤58则是步骤返回。不过为什么步骤47~57不是读取数据,而是过滤数据?别着急,答案很快就会揭晓,暂时忍耐一下。

呼!PS/2传输协议的写数据(从机视角)解释起来真有够呛。最后让我们来总结一下,主机无论是输出数据,还是读取数据都是借用 PS2_CLK的上升沿。反之,从机无论是输出数据,还是读取数据都是借用 PS2_CLK 的下降沿。PS/2传输协议是可谓是爷爷级别的传输协议吧,因为近代的传输协议不管对象是从机还是主机,或者是写还是读,一般都是上升沿设置数据下降沿锁存数据。很少情况是主机使用一个时间沿,从机使用另一个时间沿,例如 SPI传输协议就是最好的例子。

说完PS/2传输协议,接下来我们要进入PS/2鼠标的主题了。

鼠标也有普通鼠标与滚扩展鼠标之分 ... 所谓普通鼠标就是包含左键,中建还有右键;所谓扩展鼠标则包含左键,中建,右键还有滚轮,扩展鼠标也称为滚轮鼠标,不过一般的扩展鼠标只有一个滚轮而已。实验十的实验目的就是驱动普通鼠标。PS/2鼠标不像PS/2键盘,PS/2鼠标即使一上电也不会立即工作,期间从机必须将它使能才行。

普通鼠标一旦上电就便会立即复位,然后得到默认化参数,并且进入Steam模式。所谓Steam模式,即位置状况或者按键状况一有变化就会鼠标便会立即发送报告。话虽然那么说,实际上 Stream 模式还要依赖采集频率,默认的采集频率是 100次/秒,即采集间隔为10ms,也就是说鼠标在一秒内会检测100次位置状况还有按键状况。

举例而言,假设笔者按着左键不放,那么鼠标在一秒内会发送100次“左键好疼!左键好疼!“,直至笔者释放左键为止。再假设鼠标不小心被笔者退了一下,然后鼠标在10ms内向左移动10mm,当鼠标察觉位置状况发生变化以后,鼠标便会发送“哎呀!被人推向左边10mm了!”。

图10.7.1 鼠标的位置标示。

鼠标为了标示位置,内建2组9位的寄存器X与Y,结果如图10.7.1所示。默认下,鼠标的分辨率为4计数/mm。此外,鼠标也有能力辨识4处的移动方向,例如左移 10mm 寄存器X便计数 -40,右移10mm 寄存器X便计数 +40,上移10mm 寄存器Y便计数 +40,下移 10mm 寄存器Y便计数 -40。鼠标每隔10ms(默认采集频率)便会清零一次寄存器X与Y的内容。

PS/2鼠标不像PS/2键盘一上电便立即工作,我们必须事先发送命令8’hF4即“使能鼠标发送数据”,开启数据的水龙头。每当鼠标接收一帧数据,鼠标便会反馈一帧数据,为此 ... 从机每次向鼠标写入一帧数据,就必须接收一帧反馈数据。反馈数据为8’hFA表示“数据接收成功”,反馈数据为 8’hFE表示“第一帧数据接收失败”,反馈数据为 8’hFC则表示“第二帧数据接收成功”(有些命令是由2帧或者以上组成)。

图10.8 从机发送 “使能报告”命令,鼠标反馈接收成功。

为了驱使鼠标工作,PS/2鼠标上电以后,从机必须发送命令 8’hF4,并且接收反馈 8’hFA。如果一切顺利,那么鼠标就会开始工作,结果如图10.8所示。鼠标“使能”以后,鼠标便处于就绪状态,采集便开始 ... 此刻,如果鼠标的位置状况或者按键状况发生变化,鼠标就会发送3帧,亦即3字节的报告。

图10.9 鼠标发送报告。

如图10.9所示,那是一份3个字节的报告,Verilog可以这样描述:

  1. 1. 0: // Read 1st byte
  1. 2. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 3.
  1. 4. 1: // Store 1st byte
  1. 5. begin D1[7:0] <= T; i <= i + 1'b1; end
  1. 6.
  1. 7. 2: // Read 2nd byte
  1. 8. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 9.
  1. 10. 3: // Store 2nd byte
  1. 11. begin D1[15:8] <= T; i <= i + 1'b1; end
  1. 12.
  1. 13. 4: // Read 3rd byte
  1. 14. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 15.
  1. 16. 5: // Store 3rd byte
  1. 17. begin D1[23:16] <= T; i <= i + 1'b1; end
  1. 18.
  1. 19. ......
  1. 20.
  1. 21. 32: // Start bit
  1. 22. if( isH2L ) i <= i + 1'b1;
  1. 23.
  1. 24. 33,34,35,36,37,38,39,40: // Data byte
  1. 25. if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1; end
  1. 26.
  1. 27. 41: // Parity bit
  1. 28. if( isH2L ) i <= i + 1'b1;
  1. 29.
  1. 30. 42: // Stop bit
  1. 31. if( isH2L ) i <= Go;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

代码10.4

步骤32~42是读一帧数据的伪函数,步骤0~1读取第一字节并且暂存道D[7:0],步骤

2~3读取第二字节并且暂存到D[15:8],步骤4~5读取第三字节并且暂存到D[23:16]。

至于报告的内容如表10.1所示:

表10.1 普通鼠标的报告。

字节/位

[7]

[6]

[5]

[4]

[3]

[2]

[1]

[0]

字节一

Y溢出位

X溢出位

Y[8]符号位

X[8]符号位

保留

中键

右键

左键

字节二

X[7:0]

字节三

Y[7:0]

如表10.1所示,字节一的第四位表示按键状况以外,字节一的高四位也与内部寄存器X与Y有关。字节二为寄存器X的内容,字节三位寄存器Y的内容。

字节一,[0]标示左键,1表示左键按下;[1]标示右键,1表示右键按下;[2]标示中键,1表示中键按下;[4]为字寄存器的最高位也是符号位;[5]为寄存器最高位也是符号位;节二是寄存器X的低八位,字节三是寄存器Y的低八位,因此寄存器X与Y的位宽为9。这样作的目的是为了使用补码表示鼠标的移动状况。至于补码是什么?失忆的朋友请复习《时序篇》。

图10.9.1 鼠标的有效位置。

假设寄存器X的内容为 9’b1_1111_1100,[8]为1’b1表示鼠标正在左移,[7:0]为8’b1111_1100也是 8个计数,亦即移动2mm的距离。因此 9’b1_1111_1100 表示鼠标左移2mm的举例。再假设寄存器Y的内容为 9’b0_0001_0000,[8]为0表示鼠标正在上移,[7:0] 为 8’b0001_0000也是16个计数,亦即移动4mm。因此 9’b0_0001_0000表示鼠标上移4mm。结果如图10.9.1所示。

上述内容理解完毕以后,我们便可以开始建模了。

图10.10 实验十的建模图。

图10.10 是实验十的建模图,组合模块 ps2_demo 内部包含,PS/2初始化功能模块,PS/2读功能模块,数码管基础模块,然后中间还要正直化的即时操作。顾名思义,PS/2初始化功能模块主要负责初始化的工作,简言之就是发送命令 8’hF4,完后便拉高oEn使能PS/2读功能模块。PS/2读功能模块接收 iEn拉高便会开始读取3字节的报告,并且经由oData将其输出。

稍微注意一下PS2_CLK还有PS2_DAT顶层信号,由于PS/2初始化功能模块需要双向访问PS/2鼠标,为此该顶层信号皆是出入状态(IO)。反之,PS/2读功能模块只有接收数据而已,因此该顶层信号只是出入状态。PS/2读功能模块的oData,其中[2:0]是3只按键的状况,并且直接驱动三位LED资源。

至于[23:4]则是寄存器X与寄存器Y的内容,它们经由即时操作正直化以后便联合驱动数码管基础模块,然后再显示内容。

ps2_init_funcmod.v

图10.11 PS/2 初始化功能模块的建模图。

由于该模块需要来问读写PS/2鼠标,因此顶层信号 PS2_CLK与 PS2_DAT 都是双向,亦即IO口。此外,一旦该模块完成初始化的工作,oEn就会一直拉高。

  1. 1. module ps2_init_funcmod
  1. 2. (
  1. 3. input CLOCK, RESET,
  1. 4. inout PS2_CLK,
  1. 5. inout PS2_DAT,
  1. 6. output oEn
  1. 7. );
  1. 8. parameter T100US = 13'd5000;
  1. 9. parameter FF_Write = 7'd32;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上内容是出入端声明。第8行是100us的常量声明,第9行则是伪函数的入口。

  1. 11. /*******************************/ // sub1
  1. 12.
  1. 13. reg F2,F1;
  1. 14.
  1. 15. always @ ( posedge CLOCK or negedge RESET )
  1. 16. if( !RESET )
  1. 17. { F2,F1 } <= 2'b11;
  1. 18. else
  1. 19. { F2, F1 } <= { F1, PS2_CLK };
  1. 20.
  1. 21. /*******************************/ // Core
  1. 22.
  1. 23. wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上是检测电平变化的周边操作。第23行则是下降沿的即时声明。

  1. 24. reg [8:0]T;
  1. 25. reg [6:0]i,Go;
  1. 26. reg [12:0]C1;
  1. 27. reg rCLK,rDAT;
  1. 28. reg isQ1,isQ2,isEn;
  1. 29.
  1. 30. always @ ( posedge CLOCK or negedge RESET )
  1. 31. if( !RESET )
  1. 32. begin
  1. 33. T <= 9'd0;
  1. 34. C1 <= 13'd0;
  1. 35. { i,Go } <= { 7'd0,7'd0 };
  1. 36. { rCLK,rDAT } <= 2'b11;
  1. 37. { isQ1,isQ2,isEn } <= 3'b000;
  1. 38. end
  1. 39. else

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上内容为相关的寄存器声明以及复位操作。注意,PS2_CLK与PS2_DAT默认下都是高电平,所示 rCLK 与 rDAT 被赋予复位值 2’b11。

  1. 40. case( i )
  2. 41.
  3. 42. /***********/ // INIT Normal Mouse
  4. 43.
  5. 44. 0: // Send F4 1111_0100
  6. 45. begin T <= { 1'b0, 8'hF4 }; i <= FF_Write; Go <= i + 1'b1; end
  7. 46.
  8. 47. 1:
  9. 48. isEn <= 1'b1;
  10. 49.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上内容是部分核心操作。步骤0~1是主操作,主要发送命令 8’hF4,然后拉高isEn。第45行{ 1'b0, 8'hF4 },其中 1’b0是校验位,PS/2的校验位是“奇校验”,如果“1”的数量为单数,那么校验位便是 0。如第45所示,8’hF4有5个“1”所示,校验位为0。

  1. 50. /****************/ // PS2 Write Function
  1. 51.
  1. 52. 32: // Press low CLK 100us
  1. 53. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
  1. 54. else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
  1. 55.
  1. 56. 33: // Release PS2_CLK and set in, PS2_DAT set out
  1. 57. begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
  1. 58.
  1. 59. 34: // start bit
  1. 60. begin rDAT <= 1'b0; i <= i + 1'b1; end
  1. 61.
  1. 62. 35,36,37,38,39,40,41,42,43: // Data byte
  1. 63. if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
  1. 64.
  1. 65. 44: // Stop bit
  1. 66. if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
  1. 67.
  1. 68. 45: // Ack bit
  1. 69. if( isH2L ) begin i <= i + 1'b1; end
  1. 70.
  1. 71. 46: // PS2_DAT set in
  1. 72. begin isQ2 <= 1'b0; i <= i + 1'b1; end
  1. 73.
  1. 74. 47,48,49,50,51,52,53,54,55,56,57: // 1 Frame
  1. 75. if( isH2L ) i <= i + 1'b1;
  1. 76.
  1. 77. 58: // Return
  1. 78. i <= Go;
  1. 79.
  1. 80. endcase

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上内容是部分核心操作。第32~58行是从机写一帧数据,读一帧反馈数据的伪函数。

  1. 81.
  1. 82. assign PS2_CLK = isQ1 ? rCLK : 1'bz;
  1. 83. assign PS2_DAT = isQ2 ? rDAT : 1'bz;
  1. 84. assign oEn = isEn;
  1. 85.
  1. 86. endmodule

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

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

ps2_read_funcmod.v

图10.12 PS/2 读化功能模块的建模图。

PS/2读功能模块,如果iEn不拉高就不工作。此外,该模块也只是读入3字节的报告而已,完后便经由oTrig产生完成信号,报告内容则经由 oData。

  1. 1. module ps2_read_funcmod
  1. 2. (
  1. 3. input CLOCK, RESET,
  1. 4. input PS2_CLK,PS2_DAT,
  1. 5. input iEn,
  1. 6. output oTrig
  1. 7. output [23:0]oData
  1. 8. );
  1. 9. parameter FF_Read = 7'd32;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上内容是出入端声明。第9行则是伪函数的入口地址。

  1. 11. /*******************************/ // sub1
  1. 12.
  1. 13. reg F2,F1;
  1. 14.
  1. 15. always @ ( posedge CLOCK or negedge RESET )
  1. 16. if( !RESET )
  1. 17. { F2,F1 } <= 2'b11;
  1. 18. else
  1. 19. { F2, F1 } <= { F1, PS2_CLK };
  1. 20.
  1. 21. /*******************************/ // core
  1. 22.
  1. 23. wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

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

  1. 24. reg [23:0]D1;
  1. 25. reg [7:0]T;
  1. 26. reg [6:0]i,Go;
  1. 27. reg isDone;
  1. 28.
  1. 29. always @ ( posedge CLOCK or negedge RESET )
  1. 30. if( !RESET )
  1. 31. begin
  1. 32. D1 <= 24'd0;
  1. 33. T <= 8'd0;
  1. 34. { i,Go } <= { 7'd0,7'd0 };
  1. 35. isDone <= 1'b0;
  1. 36. end

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上内容是相关的寄存器声明以及复位操作。

  1. 37. else if( iEn )
  1. 38. case( i )
  1. 39.
  1. 40. /*********/ // Normal mouse
  1. 41.
  1. 42. 0: // Read 1st byte
  1. 43. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 44.
  1. 45. 1: // Store 1st byte
  1. 46. begin D1[7:0] <= T; i <= i + 1'b1; end
  1. 47.
  1. 48. 2: // Read 2nd byte
  1. 49. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 50.
  1. 51. 3: // Store 2nd byte
  1. 52. begin D1[15:8] <= T; i <= i + 1'b1; end
  1. 53.
  1. 54. 4: // Read 3rd byte
  1. 55. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 56.
  1. 57. 5: // Store 3rd byte
  1. 58. begin D1[23:16] <= T; i <= i + 1'b1; end
  1. 59.
  1. 60. 6:
  1. 61. begin isDone <= 1'b1; i <= i + 1'b1; end
  1. 62.
  1. 63. 7:
  1. 64. begin isDone <= 1'b0; i <= 7'd0; end
  1. 65.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上内容为部分核心操作。第37行 if( iEn ) 表示,iEn不拉高核心操作就不运行。步骤0~1是读取第一字节,步骤2~3是读取第二字节,步骤4~5是读取第三字节,步骤6~7则是反馈完成信号,以示一次性的报告读取已经完成。完后,i便指向步骤0。

  1. 66. /****************/ // PS2 Write Function
  1. 67.
  1. 68. 32: // Start bit
  1. 69. if( isH2L ) i <= i + 1'b1;
  1. 70.
  1. 71. 33,34,35,36,37,38,39,40: // Data byte
  1. 72. if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1; end
  1. 73.
  1. 74. 41: // Parity bit
  1. 75. if( isH2L ) i <= i + 1'b1;
  1. 76.
  1. 77. 42: // Stop bit
  1. 78. if( isH2L ) i <= Go;
  1. 79.
  1. 80. endcase

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

以上内容为部分核心操作。步骤32~42则是伪函数,主要是负责读取一帧数据。

  1. 81.
  1. 82. assign oTrig = isDone;
  1. 83. assign oData = D1;
  1. 84.
  1. 85. endmodule

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

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

ps2_demo.v

组合模块ps2_demo.v的建模图就不再重复粘贴了。

  1. 1. module ps2_demo
  1. 2. (
  1. 3. input CLOCK, RESET,
  1. 4. inout PS2_CLK, PS2_DAT,
  1. 5. output [7:0]DIG,
  1. 6. output [5:0]SEL,
  1. 7. output [2:0]LED
  1. 8. );
  1. 9. wire EnU1;
  1. 10.
  1. 11. ps2_init_funcmod U1
  1. 12. (
  1. 13. .CLOCK( CLOCK ),
  1. 14. .RESET( RESET ),
  1. 15. .PS2_CLK( PS2_CLK ), // < top
  1. 16. .PS2_DAT( PS2_DAT ), // < top
  1. 17. .oEn( EnU1 ) // > U2
  1. 18. );
  1. 19.
  1. 20. wire [23:0]DataU2;
  1. 21.
  1. 22. ps2_read_funcmod U2
  1. 23. (
  1. 24. .CLOCK( CLOCK ),
  1. 25. .RESET( RESET ),
  1. 26. .PS2_CLK( PS2_CLK ), // < top
  1. 27. .PS2_DAT( PS2_DAT ), // < top
  1. 28. .iEn( EnU1 ), // < U1
  1. 29. .oTrig(),
  1. 30. .oData( DataU2 ) // > U2
  1. 31. );
  1. 32.
  1. 33. // immediate proses
  1. 34. wire[7:0] X = DataU2[4] ? (~DataU2[15:8] + 1'b1) : DataU2[15:8];
  1. 35. wire[7:0] Y = DataU2[5] ? (~DataU2[23:16] + 1'b1) : DataU2[23:16];
  1. 36.
  1. 37. smg_basemod U3
  1. 38. (
  1. 39. .CLOCK( CLOCK ),
  1. 40. .RESET( RESET ),
  1. 41. .DIG( DIG ), // > top
  1. 42. .SEL( SEL ), // > top
  1. 43. .iData( { 3'd0,DataU2[5],Y,3'd0,DataU2[4],X }) // < U2
  1. 44. );
  1. 45.
  1. 46. assign LED = {DataU2[1], DataU2[2], DataU2[0]};
  1. 47.
  1. 48. endmodule

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

该代码非常简单,第30行表示U2的oTrig无用武之地。第34~35行是正直化的即时声明。第43行是U3的联合驱动,其中 3’d0, DataU2[5] 表示第1位数码管显示Y的符号位,Y表示第2~3位的数码管显示Y的正直结果,3’d0,DataU2[4] 表示第4位数码管显示X的符号位,X表示第5~6位数码管显示X的正直结果。第46行则是各个按键情况直接驱动LED资源。

编译完成并且下载程序。假设笔者按下左键,那么LED[0]便会点亮,释放则消灭。再假设笔者向左移动鼠标,那么鼠标第4位数码管会显示1,第5~6数码管则会显示X的内容,亦即鼠标移动的举例。

细节一:精简与直观

  1. 1. 0: // Read 1st byte
  1. 2. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 3. 1: // Store 1st byte
  1. 4. begin D1[7:0] <= T; i <= i + 1'b1; end
  1. 5. 2: // Read 2nd byte
  1. 6. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 7. 3: // Store 2nd byte
  1. 8. begin D1[15:8] <= T; i <= i + 1'b1; end
  1. 9. 4: // Read 3rd byte
  1. 10. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 11. 5: // Store 3rd byte
  1. 12. begin D1[23:16] <= T; i <= i + 1'b1; end

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

代码10.5

代码10.5是PS/2读功能模块的部分内容,期间步骤0~5表示3字节读取且暂存的过程

。事实上,代码10.5可以进一步精简,结果如代码10.6所示:

  1. 13. 0: // Read 1st byte
  1. 14. begin i <= FF_Read; Go <= i + 1'b1; end
  1. 15. 1: // Store 1st byte
  1. 16. begin D1[7:0] <= T; i <= FF_Read; Go <= i + 1'b1;end
  1. 17. 2: // Read 2nd byte
  1. 18. begin D1[15:8] <= T i <= FF_Read; Go <= i + 1'b1; end
  1. 19. 3: // Store 2nd byte
  1. 20. begin D1[23:16] <= T; i <= i + 1'b1; end
  1. 21. ......

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

代码10.6

代码10.6相较代码10.5,它虽然有很高程度的精简度,不过直观程度却不如代码10.6。

到头来到底是直观好,还是精简好,唯有见仁见智了。

细节二:完整的个体模块

图10.13 实验十的完整个体模块。

图10.13是PS/2鼠标基础模块,里边包含PS/2初始化功能模块,还有PS/2读功能模块。

该模块的最左边是顶层信号 PS2_CLK 与 PS2_DAT 的输入,鼠标完成初始化以后,PS/2初始化功能模块便会拉高 oEn 使能 PS/2读功能模块。PS/2读功能模块的左边除了顶层信号以外还有iEn,iEn不拉高该模块就不工作。PS/2读功能模块每完成3字节报告的读取,就会经由 oTrig 产生完成信号。

ps2mouse_basemod.v
  1. 1. module ps2mouse_basemod
  1. 2. (
  1. 3. input CLOCK, RESET,
  1. 4. inout PS2_CLK, PS2_DAT,
  1. 5. output oTrig,
  1. 6. output [31:0]oData
  1. 7. );
  1. 8. wire EnU1;
  1. 9.
  1. 10. ps2_init_funcmod U1
  1. 11. (
  1. 12. .CLOCK( CLOCK ),
  1. 13. .RESET( RESET ),
  1. 14. .PS2_CLK( PS2_CLK ), // < top
  1. 15. .PS2_DAT( PS2_DAT ), // < top
  1. 16. .oEn( EnU1 ) // > U2
  1. 17. );
  1. 18.
  1. 19. ps2_read_funcmod U2
  1. 20. (
  1. 21. .CLOCK( CLOCK ),
  1. 22. .RESET( RESET ),
  1. 23. .PS2_CLK( PS2_CLK ), // < top
  1. 24. .PS2_DAT( PS2_DAT ), // < top
  1. 25. .iEn( EnU1 ), // < U1
  1. 26. .oTrig( oTrig ), // > Top
  1. 27. .oData( oData ) // > Top
  1. 28. );
  1. 29.
  1. 30. endmodule

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十:PS/2模块④ — 普通鼠标的更多相关文章

  1. [黑金原创教程] FPGA那些事儿《设计篇 III》- 图像处理前夕·再续

    简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...

  2. [黑金原创教程] FPGA那些事儿《设计篇 II》- 图像处理前夕·续

    简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...

  3. [黑金原创教程] FPGA那些事儿《设计篇 I》- 图像处理前夕

    简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...

  4. [黑金原创教程] FPGA那些事儿《数学篇》- CORDIC 算法

    简介 一本为完善<设计篇>的书,教你CORDIC算法以及定点数等,内容请看目录. 贴士 这本教程难度略高,请先用<时序篇>垫底. 目录 Experiment 01:认识CORD ...

  5. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】原创教程连载导读【连载完成,共二十九章】

    前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...

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

    前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...

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

    实验二:按键模块① - 消抖 按键消抖实验可谓是经典中的经典,按键消抖实验虽曾在<建模篇>出现过,而且还惹来一堆麻烦.事实上,笔者这是在刁难各位同学,好让对方的惯性思维短路一下,但是惨遭口 ...

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

    实验七:PS/2模块① — 键盘 实验七依然也是熟烂的PS/2键盘.相较<建模篇>的PS/2键盘实验,实验七实除了实现基本的驱动以外,我们还要深入解PS/2时序,还有PS/2键盘的行为.不 ...

  9. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十八:SDRAM模块① — 单字读写

    实验十八:SDRAM模块① — 单字读写 笔者与SDRAM有段不短的孽缘,它作为冤魂日夜不断纠缠笔者.笔者尝试过许多方法将其退散,不过屡试屡败的笔者,最终心情像橘子一样橙.<整合篇>之际, ...

随机推荐

  1. u3d Mecanim动画

    作为Unity4.0的主要更新功能,Mecanim动画被寄予了很多的期望.系统有先进的地方,也有不足的地方.这些我们留到最后再来总结. 阿赵粗略的学习了一下,写下以下的教程.这篇教程简单的说明了Mec ...

  2. LAMP架构介绍MySQL、MariaDB介绍 MySQL安装

  3. Lua中调用函数使用点号和冒号的区别

    1.初学者最易混乱Top1——调用函数时用点号还是用冒号? 我们来看看下面的两句代码: mSprite.setPosition(, ); mSprite:setPosition(, ); 对于初次接触 ...

  4. 织梦Dedecms容易被挂马文件以及可疑文件汇总

    1. 被植入木马,然后网站打开后自动弹出博彩,赌博,色情网站,一般这种病毒的特征代码如下 二.织梦CMS被挂马清理方法 1.删除增加的管理员service.spider等用户名. 2.删除根目录的as ...

  5. kendo-ui的MVVM模式

    摘要: MVVM(Model View ViewModel)是一种帮助开发者将数据从模型分离的设计模式.MVVM的ViewModel负责将数据对象从模型中分离出来,通过这种方式数据就很容易控制数据如何 ...

  6. splash渲染网页

    #coding=utf8 import requests,time,random import threadpool render_html = 'http://192.168.30.128:8050 ...

  7. C# winform pictureBox如何突出显示,放大并给pictureBox边框变色

    PictureBox old = null; private void pictureBox2_Click(object sender, EventArgs e) { PictureBox p = ( ...

  8. 【代码审计】iCMS_v7.0.7 keywords.admincp.php页面存在SQL注入漏洞分析

      0x00 环境准备 iCMS官网:https://www.icmsdev.com 网站源码版本:iCMS-v7.0.7 程序源码下载:https://www.icmsdev.com/downloa ...

  9. python中json格式数据输出实现方式

    python中json格式数据输出实现方式 主要使用json模块,直接导入import json即可. 小例子如下: #coding=UTF-8 import json info={} info[&q ...

  10. React Native(十二)——嵌套WebView中的返回处理

    情景描述: 从一个名为"My"的组件点击进去,进入一个列表(该列表内容为webView中内容),其中一个webView也可以点击进入详情页(也为webView),但是如果对导航栏不 ...