实验十一:PS/2模块⑤ — 扩展鼠标

当普通鼠标即三键鼠标再也无法满足需求的时候,扩展鼠标即滚轮鼠标就诞生了,然而实验十一的实验目的就是实现滚轮鼠标的驱动。不过,进入整体之前,先让我们来了解一下鼠标的常用命令。

图11.1 命令F3,设置采样频率。

命令F3也是Set Sample Rate,主要是用来设置采集频率。笔者曾经说过,采集频率就是鼠标采集按键状况还有位置状况的间隔时间,默认下是100次/秒。如图11.1所示,FPGA先发送命令数据8’hF3,事后鼠标会反馈8’hFA以示接收成功,余下FPGA再发送参数数据8’d200,鼠标接收成功后也会反馈 8’hFA。如此一来,鼠标的采集频率从原本的 100次/秒,变成 200次/秒。

图11.2 命令E8,设置分辨率。

命令E8也是 Set Resolution,主要是用来设置分辨率。所谓分辨率就是位置对应寄存器计数的单位,默认下是4计数/mm,亦即 1mm 的距离,鼠标计数4下。如图11.2所示,FPGA先发送命令数据 8’hE8,鼠标接收以后便反馈 8’hFA,FPGA随之也会发送参数数据 8’h01,鼠标接收以后也会反馈数据 8’hFA。完后,鼠标的分辨从原本的 4计数/mm 变成 2计数/mm。

参数数据所对应的分辨率如表11.1所示:

表11.1 参数数据所对应的分辨率。

参数数据

分辨率

8’h00

1计数/mm

8’h01

2计数/mm

8’h02

4计数/mm

8’h03

8计数/mm

图11.3 命令F6,使用默认参数。

假设笔者手痒,不小心打乱鼠标内部的参数数据,此刻笔者可以发送命令F6,即Set Defaults将参数数据回复成原来的缺省值。如图11.3所示,FPGA先发送命令数据8’hF6

,鼠标完成接收以后便会反馈8’hFA。

图11.4 命令F4使能报告,命令F5关闭报告。

PS/2鼠标不像PS/2键盘,上电并且完成初始化以后它便会陷入发呆状态,如果不发送命令数据8’hF4(即Enable Data Report)手动开启鼠标的水龙头,鼠标是不会发送报告(即夹杂按键状况与位置状况的数据)。如图11.4所示,FPGA先发送命令数据8’hF4,鼠标接收以后便会反馈8’hFA,事后鼠标立即处于就绪状态,一旦按键状况或者位置状况发生改变,鼠标就会发送报告。

假设读者觉得鼠标太唠叨,什么大事小事都报告,笔者可以发送命令数据 8’hF5(即 Disable Data Report)为了使其闭嘴。如图11.4所示,FPGA先发送命令数据 8’hF4,鼠标接收完毕以后便会反馈8’hFA,事后鼠标就成为闭嘴状态,大事小事再也不会烦人。如果读者觉得寂寞,读者可以再度发送命令数据 8’hF4,让鼠标再度唱歌。

图11.5 命令F2,读取鼠标ID。

为了区分鼠标是普通鼠标还是扩展鼠标,期间我们必须使用命令8’hF2,即 Get Device ID。如图11.5所示,FPGA发送命令数据 8’hF2,鼠标接收以后先反馈 8’hFA,再来便发送鼠标ID。如果内容是8’h00,则表示该鼠标只是普通鼠标 ... 反之,如果内容是 8’h03,那么该鼠标就是扩展鼠标。因为如此,我们需要更改一下伪函数,结果如代码11.1所示:

1.             32: // Press low PS2_CLK 100us
2.            if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
3.            else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
4.                          
5.            33: // release PS2_CLK and set in ,PS2_DAT set out
6.            begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
7.                          
8.            34: // start bit 1
9.            begin rDAT <= 1'b0; i <= i + 1'b1; end
10.                          
11.            35,36,37,38,39,40,41,42,43:  // data bit 9
12.            if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
13.                          
14.            44: // stop bit 1
15.            if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
16.                          
17.            45: // Ack bit
18.            if( isH2L ) begin i <= i + 1'b1; end
19.                          
20.            46: // PS2_DAT set in
21.            begin isQ2 <= 1'b0; i <= i + 1'b1; end
22.                          
23.            /***********/ // Receive 1st Frame
24.                         
25.            47,48,49,50,51,52,53,54,55,56,57: // Ingnore 
26.            if( isH2L ) i <= i + 1'b1;
27.                          
28.             58: // Check comd F2
29.             if( T[7:0] == 8'hF2 ) i <= i + 1'b1;
30.             else i <= Go;
31.                          
32.             /***********/ // Receive 2nd Frame
33.                          
34.             59:  // Start bit 1
35.             if( isH2L ) i <= i + 1'b1; 
36.                          
37.             60,61,62,63,64,65,66,67,68: // Data bit 9
38.             if( isH2L ) begin T[i-60] <= PS2_DAT; i <= i + 1'b1; end
39.                          
40.             69: // Stop bit 1
41.             if( isH2L ) i <= Go;

代码11.1

如代码11.1所示,步骤32~57则是发送一帧数据又忽略一帧反馈,基本上与实验十一模一样。至于第58行则是用来判断,FPGA所发送的命令是否是 8’hF2即 Get Device ID

?如果是,步骤则继续读取操作,因为命令8’hF2令鼠标反馈8’hFA之余,还会导致鼠标会发送一帧ID数据。否则的话,即表示其他命令,步骤返回。步骤59~69是用来读取下一帧ID数据,期间步骤60~68用来读取 8位数据位,还有1位校验位。完后,步骤便返回。

小时候的笔者很爱假扮刺客,笔者与近邻的小孩就总是瞎着玩,其它小朋友则扮演秘密商人。刺客为了与秘密商人进行交易,两者之间必须经过暗语核对,例如:

“阳光的男孩赤裸裸 ... ”,对方问道。

“对面的女来看过来 ... ”,笔者答道。

滚轮鼠标也是扩展鼠标,上电以后也不会立即变成扩展鼠标,如果扩展鼠标不经过核对暗语,扩展鼠标也是一只普通的3键鼠标而已 ... 反之,如果完成暗语核对,扩展鼠标才会发挥滚轮功能。

图11.6 设置扩展鼠标的暗语。

如图11.6所示,那是设置扩展鼠标的暗语:

发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’hC8,在接收反馈8’hFA;

发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’h64,在接收反馈8’hFA;

发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’h50,在接收反馈8’hFA;

发送命令数据 8’hF2,接收反馈8’hFA,再接收鼠标ID8’h03。

完后,鼠标便成为扩展鼠标,内部也自动初始化,然后进入默认模式。

图11.7 扩展鼠标标示的位置。

普通鼠标相较扩展鼠标,它多了滚轮功能,即鼠标除了标示左键,中键,右键,X还有Y以外,扩展还会标示Z。如图11.7所示,X与Y可以看成面积,至于Z则可以看成上下。当鼠标向西移动,X呈现正直,反之负值;当鼠标向北移动,Y呈现正直,反之负值;当滚动向下活动,Z呈现正直,反之负值。

图11.8 扩展鼠标的报告长度。

为此,扩展鼠标相较普通鼠标,报告长度则多了一个字节。如图11.8所示,当鼠标察觉变化以后,鼠标便会发送4个字节长度的报告,然而字节之间的位分配如表11.1所示:

表11.1 Device ID 为 8’h03 的报告内容。

字节/位

[7]

[6]

[5]

[4]

[3]

[2]

[1]

[0]

字节一

Y溢出位

X溢出位

Y[8]符号位

X[8]符号位

保留

中键

右键

左键

字节二

X[7:0]

字节三

Y[7:0]

字节四

保留

保留

保留

保留

Z[3]符号位

Z[2]

Z[1]

Z[0]

笔者需要补充一下 ... 由于早期Intel 称王,所以扩展鼠标标准都是Intel说话算话,Device ID 为 8’h03 就是其中一种扩展标准。如表11.1所示,字节一至字节三基本上变化不大,反之字节四则稍微不同。字节四的[2..0]位是 Z[2:0],字节四的[3]是Z[3],也是Z的符号位。换句话说,寄存器Z有4位,内容用补码表示。

图11.9 扩展鼠标的位置范围。

图11.9表示扩展鼠标的位置范围,X与Y与普通鼠标一样,Z比较畸形一点,因为Z向上不是正直而是负值,反之亦然。Z的有效范围是 4’b1001~4’b0111或者 -7~7,也就是说滚轮向下活动,寄存器Z就递增,向上滚动,寄存器Z就递减。

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

图11.10 实验十一的建模图。

如图11.10所示,组合模块 ps2_demo 包含的内容与实验十相差不了多少,不过却少了正直化的即时操作。期间,PS/2初始化功能模块的 oEn 有两位,oEn[1] 拉高表示鼠标为扩展鼠标,oEn[0] 拉高表示鼠标为普通鼠标。PS/2读取功能模块的 oData[2:0] 直接驱动LED资源, oData[27:4]则驱动数码管基础模块的 iData。

ps2_init_funcmod.v

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

如图11.11所示,PS/2初始化功能模块有两位oEn,[1]拉高表示鼠标为扩展鼠标,[0]拉高则表示鼠标为普通鼠标。

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

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

11.         /*******************************/ // sub1
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 );

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

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

以上内容是相关的寄存器声明,第33~39行则是这群寄存器的复位操作。其中isEn有两位,isEx为扩展鼠标的立旗。

42.                    case( i )
43.                     
44.                         /***********/ // INIT Mouse 
45.                          
46.                          0: // Send F3  1111_0011
47.                          begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
48.                          
49.                          1: // Send C8  1100_1000
50.                          begin T <= { 1'b0, 8'hC8 }; i <= FF_Write; Go <= i + 1'b1; end
51.                          
52.                          2: // Send F3 1111_0011
53.                          begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
54.                          
55.                          3: // Send 64 0110_1000
56.                          begin T <= { 1'b0, 8'h64 }; i <= FF_Write; Go <= i + 1'b1; end
57.                          
58.                          4: // Send F3 1111_0011
59.                          begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
60.                          
61.                          5: // Send 50 0101_0000
62.                          begin T <= { 1'b1, 8'h50 }; i <= FF_Write; Go <= i + 1'b1; end
63.                          
64.                          6: // Send F2  1111_0010
65.                          begin T <= { 1'b0, 8'hF2 }; i <= FF_Write; Go <= i + 1'b1; end
66.                          
67.                          7: // Check Mouse ID 00(normal), 03(extend)
68.                          if( T[7:0] == 8'h03 ) begin isEx <= 1'b1; i <= i + 1'b1; end
69.                          else if( T[7:0] == 8'h00 ) begin isEx <= 1'b0; i <= i + 1'b1; end
70.                        
71.                          8: // Send F4 1111_0100
72.                          begin T <= { 1'b0, 8'hF4 }; i <= FF_Write; Go <= i + 1'b1; end
73.                          
74.                          9:
75.                          if( isEx ) isEn[1] <= 1'b1;
76.                          else if( !isEx ) isEn[0] <= 1'b1;
77.                          

以上内容是核心操作。步骤0~9是主操作,步骤0~6则是发送用来开启扩展鼠标的暗语,步骤7用来判断鼠标返回的 Device ID 是否为 8’h03,如果是 isEx 立旗,否则 isEx 消除立旗。步骤8用来使能鼠标。步骤9根据 isEx 的状态再来决定 isEn的结果, 如果isEx为1 isEn[1] 便拉高,否则 isEx 拉高,完后步骤停留。

78.                          /****************/ // PS2 Write Function
79.                          
80.                          32: // Press low PS2_CLK 100us
81.                          if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
82.                          else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
83.                          
84.                          33: // release PS2_CLK and set in ,PS2_DAT set out
85.                          begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
86.                          
87.                          34: // start bit 1
88.                          begin rDAT <= 1'b0; i <= i + 1'b1; end
89.                          
90.                          35,36,37,38,39,40,41,42,43:  // data bit 9
91.                          if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
92.                          
93.                          44: // stop bit 1
94.                          if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
95.                          
96.                          45: // Ack bit
97.                          if( isH2L ) begin i <= i + 1'b1; end
98.                          
99.                          46: // PS2_DAT set in
100.                          begin isQ2 <= 1'b0; i <= i + 1'b1; end
101.                          
102.                          /***********/ // Receive 1st Frame
103.                         
104.                          47,48,49,50,51,52,53,54,55,56,57: // Ingnore 
105.                          if( isH2L ) i <= i + 1'b1;
106.                          
107.                          58: // Check comd F2
108.                          if( T[7:0] == 8'hF2 ) i <= i + 1'b1;
109.                          else i <= Go;
110.                          

以上内容是部分核心操作。步骤32~58是部分伪函数,内容则是发送一帧数据,再读取一帧反馈,完后便进入步骤58判断,发送的命令是否为 8’hF2,如果是便继续步骤,否则便返回步骤。

111.                          /***********/ // Receive 2nd Frame
112.                          
113.                          59:  // Start bit 1
114.                          if( isH2L ) i <= i + 1'b1; 
115.                          
116.                          60,61,62,63,64,65,66,67,68: // Data bit 9
117.                          if( isH2L ) begin T[i-60] <= PS2_DAT; i <= i + 1'b1; end
118.                          
119.                          69: // Stop bit 1
120.                          if( isH2L ) i <= Go;
121.                                              
122.                     endcase
123.         

以上内容是部分核心操作。步骤59~69也是部分伪函数,主要用来读取下一帧数据的字节内容,在此是针对命令8’hF2,也就是Device ID。读完一帧数据以后便返回步骤。

124.         assign PS2_CLK = isQ1 ? rCLK : 1'bz;
125.         assign PS2_DAT = isQ2 ? rDAT : 1'bz;
126.         assign oEn = isEn;
127.      
128.    endmodule

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

ps2_read_funcmod.v

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

实验十一的PS/2读功能模块与实验十相比,左边的 iEn出入多出一位以外,右边的oData也多出一个字节。

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

以上内容是相关的出入端声明。第9行是伪函数的入口。

10.    
11.         /*******************************/ // sub1
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 );

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

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

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

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

以上内容为部分核心操作。第37行的 if( iEn[1] ) 表示下面所有内容都是扩展鼠标的核心操作。步骤0~7则是读取4个字节的数据,步骤8~9用来产生完成信号以示一次性的报告已经接收完毕。

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

以上内容为部分核心操作。步骤32~42是读取一帧数据的伪函数。

87.                else if( iEn[0] )  
88.                    case( i )
89.                     
90.                         /***********/ // Normal Mouse Read Data  
91.                          
92.                          0: // Read Data 1st byte
93.                          begin i <= FF_Read; Go <= i + 1'b1; end
94.                          
95.                          1: // Store Data 1st byte
96.                          begin D1[7:0] <= T; i <= i + 1'b1; end
97.                          
98.                          2: // Read Data 2nd byte
99.                          begin i <= FF_Read; Go <= i + 1'b1; end
100.                          
101.                          3: // Store Data 2nd byte
102.                          begin D1[15:8] <= T; i <= i + 1'b1; end
103.                          
104.                          4: // Read Data 3rd byte
105.                          begin i <= FF_Read; Go <= i + 1'b1; end
106.                          
107.                          5: // Store Data 3rd byte
108.                          begin D1[23:16] <= T; i <= i + 1'b1; end
109.                          
110.                          6:
111.                          begin isDone <= 1'b1; i <= i + 1'b1; end
112.                          
113.                          7:
114.                          begin isDone <= 1'b0; i <= 7'd0; end
115.                          

以上内容为部分核心操作。第87行的 if( iEn[0] ) 表示下面的内容均为普通鼠标的核心操作。步骤0~5用来读取3个字节的内容,步骤6~7则用来产生完成信号以示一次性的报告已经读取完毕。

116.                          /****************/ // PS2 Write Function
117.                          
118.                          32: // Start bit
119.                          if( isH2L ) i <= i + 1'b1; 
120.                          
121.                          33,34,35,36,37,38,39,40:  // Data byte
122.                          if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1;  end
123.                          
124.                          41: // Parity bit
125.                          if( isH2L ) i <= i + 1'b1;
126.                          
127.                          42: // Stop bit
128.                          if( isH2L ) i <= Go;
129.                            
130.                     endcase
131.         

以上内容为部分核心操作。步骤32~42是读取一帧数据的伪函数。

132.         assign oTrig = isDone;
133.         assign oData = D1;
134.      
135.    endmodule

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

ps2_demo.v

笔者就不重复粘贴实验十一的建模图了,具体内容我们还是来看代码吧。

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

上诉内容的连线部署基本上与图11.10差不了多少,期间第39行的 2’d0,DataU2[5],DataU2[4] 表示数码管的第一位显示 X 与 Y的符号位;DataU2[27:24] 表示数码管的第二位显示 Z的内容;DataU2[23:16] 表示数码管的第三至第四位显示 Y 的内容;DataU2[15:8] 表示数码管的第五至第六位显示 X 的内容。第42行则表示 LED[2]显示右键,LED[1]显示中键,LED[0]显示左键。

编译完毕并且下载程序。当鼠标向西南方移动的时候,第一位数码管便会显示 4’h3,即 4’b0011,也就是说 X 与 Y 的符号位都是拉高状态(负值)。当滚轮向上滚动的时候,第二位数码管便会显示 4’hF,即4’b1111,也就是Z为负值 -1(只要滚动速度够快,负值还能更小)。至于数码管第3~4显示Y的内容(补码形式),数码管5~6则显示X的内容(补码形式)。

细节一: 两个人,两把汤匙

1.       else if( iEn[1] ) 
2.           case( i )
3.              扩展鼠标的核心操作;
4.              伪函数;
5.           endcase
6.       else if(isEn[0])
7.          case(i)
8.               普通鼠标的核心操作;
9.               伪函数;
10.          endcase

代码11.2

PS/2 读取功能模块有一个有趣的现象,即资源多义性的问题。如代码11.2所示,PS/2读取功能模块用 if( iEn[1] ) 与 if( iEn[0] ) 表示该模块针对两种鼠标的读取操作。这种感觉好比一对兄弟在吃饭 ... 正常情况下,当然是一个人一把汤匙才对,这种比喻完全对应代码11.2的内容。

PS/2读取功能模块负责两种鼠标的读取操作之际,里边好比有一对兄弟,一个人负责扩展鼠标的读取操作,另一个人则针对普通鼠标的读取操作。期间,伪函数就是某种操作资源,也可以看成是汤匙。为了不让两位兄弟争用一把汤匙而吵架,身为设计者的我们,应该为每个人分配一把汤匙。

对此,我们必须多花一些钱买另一把汤匙,这样做我们可能多消耗一些逻辑资源。不过,家和为贵,为使模块可以和谐共处以致提高表达能力,要笔者多消耗一些逻辑资源,笔者也觉得值得。

细节二:完整的个体模块

图11.13 PS/2鼠标基础模块的建模图。

图11.13是PS/2鼠标基础模块的建模图。

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

【黑金原创教程】【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 】实验十:PS/2模块④ — 普通鼠标

    实验十:PS/2模块④ - 普通鼠标 学习PS/2键盘以后,接下来就要学习 PS/2 鼠标.PS/2鼠标相较PS/2键盘,驱动难度稍微高了一点点,因为FPGA(从机)不仅仅是从PS/2鼠标哪里读取数据 ...

随机推荐

  1. 系统目录结构/ls命令/文件类型/alias命令

    2.1/2.2 系统目录结构 2.3 ls命令 2.4 文件类型 2.5 alias命令 linux文件目录结构 linux文件结构 / 系统跟目录 root  root用户主目录,存放启动linux ...

  2. VS2010 正则批量替换头文件路径

        最近在项目实践中,需要统一对工程头文件进行重构,具体要求是,将之前 #include "../../abc/def.h" 类似的头文件引用路径 替换为#include &q ...

  3. winform利用itextsharp.dll实现图片文件转换PDF格式文件

    1.利用itextsharp.dll实现单个图片文件转换为PDF格式文件, 可以使用以下类: void ConvertJPG2PDF(string jpgfile, string pdf) { var ...

  4. 【WP8】线程安全的StorageHelper

    14-08-29 12:32更新:修复StorageHelper部分bug WP8以后提供了StorageFile的方式访问文件,StorageFile对文件的操作只提供了异步的支持,包括WP8.1 ...

  5. 2013——M笔试南京——程序

    迄今只参加了M南京笔试,可惜自己不是计算机出身,还有好多东西得学啊…… M的最后一题是编程: 输入:单链表L0.L1.L2……Ln-1.Ln,将链表变为:L0.Ln.L1.Ln-1.L2…… 算法: ...

  6. 【python】并行化的又一种思路

    https://segmentfault.com/a/1190000000414339

  7. jQuery实现自动调用和触发某个事件的方法

    1.比如我们通过jquery定义了一个点击事件,我们如何自动触发他: $(function(){    $('#button').click(function(){      alert('butto ...

  8. mysql中json_object函数的使用?

    需求说明: 今天看了json_object函数的使用,在此记录下使用过程 操作过程: 1.使用json_object函数将一个键值对列表转换成json对象 mysql> select json_ ...

  9. python的初始化运行了哪些?

    下面的3个print一个是在模块下面,一个是函数里面,一个是类名下面(不在方法里面) 1. 运行这段代码可以发现第3行和11行可以打印出来.第7行没有打印出来.所以可以放心,函数或者方法里面就算有错误 ...

  10. [Algorithm] Deferred Acceptance Algorithm

    约会配对问题 一.立即接受算法: 对于约会的配对,大家都去追自己最心仪的女生.而这个女生面对几位追求者,要立刻做个决定. 被拒绝的男生们调整一下心情,再去追求心中的 No. 2.以此类推. 这样做法有 ...