设计背景:
PS2接口是一种PC兼容型电脑系统上的接口,可以用来链接键盘及鼠标。PS2的命名来自于1987年时IBM所推出的个人电脑:PS/2系列。PS2的键盘和鼠标在电气特性上十分类似,主要差别在于键盘接口需要双向的沟通。PS2接口不支持热插拔,使用时需要关机插上,目前已逐渐被USB所替代,只有少部分台式机仍然提供PS2接口。
设计原理:
PS2的接口如下图所示:
上图中,1是数据线DATA;2是预留N/C;3是GND;4是VCC(+5V);5是时钟信号线CLK;6是预留N/C。
PS2原理电路图如下:
PS2协议总共由两根线组成,从电路原理图中也可以看出,需要控制的只有PS2_CLK和PS2_SDA,即一根时钟线和一根数据线。PS2设备中的时钟和数据都是集电极开路的,平时都是高电平。本设计将采用PS2接口的键盘称为从机,将控制和解码PS2协议的一方称为主机(FPGA)。PS2协议的时钟线始终由从机(即键盘)产生的,PS2协议发送一个字节数据共有11位。时序如下图所示:
1Bit起始位,总是0;8Bit数据位,低位在前;1Bit校验位,奇校验;1Bit停止位,总是1。
从机(键盘)按照这个时序发送数据,主机(FPGA)只需要实现该协议的解码,即可将其中的8Bit数据位提取出来。根据时序图可以看出,数据在PS2时钟的下降沿是保持稳定的,主机只需在检测到PS2时钟出现下降沿时,去读取数据线上的电平,就可得到正确的数据。
通过上述的内容,已经知道了PS2从机到主机的通信协议,接下来就需要知道从机发送过来的每个字节代表什么?这时就要对照键盘编码表进行查看。键盘上一个按键由按下到释放时,键盘是按照如下的规定向主机发送数据的:
只要一个按键被按下,这个键的通码(MAKE)就会被发送到主机,按键一被释放,断码(BREAK)也会被发送,如果按键被按下不释放的话,键盘会以一定的频率发送那个按键的通码。每个按键都有自己唯一的通码和断码,从而组成键盘编码表,表上的码值为16进制:
例如,如果键盘上‘A’键被按下时,键盘就会向主机发送‘A’键对应的通码‘1C’,直到按键被释放。在按键被释放后,键盘将会向主机发送‘A’键的断码,即首先发送‘F0’,然后下一个字节发送‘1C’。通过观察键盘编码表,可以发现按键的通码与断码存在一定的联系,多数断码的第一个字节是‘F0’,第二个字节则是这个键的通码。
如果按下键盘上的扩展按键时,如‘END’,当‘END’键被按下后,键盘会首先向主机发送‘E0’,然后发送‘F0’,最后再发送‘69’。 根据上述的分析可知,在主机(FPGA)解码一次数据后,还需要对这个数据进行分析判断,判断该数据是否为断码标志‘F0’以及扩展码标志‘E0’。
设计架构图:
本设计将实现在PS2键盘上按下26个字母任一个,在数码管上显示其对应的ASCII码。架构图如下:
ps2scan模块是根据PS2的时序协议,将键盘的按键值译成一个8位的数据(out_data)输出;ASCII模块,根据ASCII表,将数据与字母一一对应;seg_num模块将相应的数据在数码管上显示。
设计代码:
ps2scan模块代码:
0 module ps2scan (clk, rst_n, ps2_sclk, ps2_sda, out_data);
1
2 //端口信号:模块的输入输出接口
3 input clk;//系统时钟
4 input rst_n;//低电平复位
5 input ps2_sclk;//ps2时钟信号(ps2设备自动产生,大约10KHz左右)
6 input ps2_sda;//ps2数据信号
7 output reg [7:0] out_data; //键值数据(采集ps2的一帧信号中间的8位有效位)
8
9 //在发送时序中,数据在ps2_sclk的下降沿采集信号,以下为检测下降沿
10 reg in1,in2;
11 wire en;
12
13 always @ (posedge clk or negedge rst_n)
14 begin
15 if(!rst_n)
16 begin
17 in1 <= 1'b0;
18 in2 <= 1'b0;
19 end
20 else
21 begin
22 in1 <= ps2_sclk;
23 in2 <= in1;
24 end
25 end
26
27 assign en = (~in1) & in2; //当出现ps2_sclk下降沿后拉高一个时钟,以进行数据的采集
28
29
30 //采集ps2的一帧信号中间的8位有效位:每检测一个下降沿算一位数据位,在中间8位采集有效数据
31 reg[7:0] temp_data;
32 reg[3:0] num;
33
34 always @ (posedge clk or negedge rst_n)
35 begin
36 if(!rst_n)
37 begin
38 num <= 4'd0;
39 temp_data <= 8'd0;
40 end
41 else if(en)
42 case (num)
43 4'd0: num <= num+1'b1; //开始位
44 4'd1: begin
45 num <= num+1'b1;
46 temp_data[0] <= ps2_sda; //bit0
47 end
48 4'd2: begin
49 num <= num+1'b1;
50 temp_data[1] <= ps2_sda; //bit1
51 end
52 4'd3: begin
53 num <= num+1'b1;
54 temp_data[2] <= ps2_sda; //bit2
55 end
56 4'd4: begin
57 num <= num+1'b1;
58 temp_data[3] <= ps2_sda; //bit3
59 end
60 4'd5: begin
61 num <= num+1'b1;
62 temp_data[4] <= ps2_sda; //bit4
63 end
64 4'd6: begin
65 num <= num+1'b1;
66 temp_data[5] <= ps2_sda; //bit5
67 end
68 4'd7: begin
69 num <= num+1'b1;
70 temp_data[6] <= ps2_sda; //bit6
71 end
72 4'd8: begin
73 num <= num+1'b1;
74 temp_data[7] <= ps2_sda; //bit7
75 end
76 4'd9: num <= num+1'b1; //结束位
77 4'd10:num <= 4'd0;
78 default: ;
79 endcase
80 end
81
82 //判断是否有键按下:根据通码、断码的特性判断
83 reg key;
84
85 always @ (posedge clk or negedge rst_n)
86 begin
87 if(!rst_n)
88 key <= 1'b0;
89 else if(num==4'd10)
90 begin
91 if(temp_data == 8'hf0)
92 key <= 1'b1;
93 else
94 begin
95 if(!key)
96 out_data <= temp_data;
97 else
98 key <= 1'b0;
99 end
100 end
101 end
102
103 endmodule
ASCII模块代码:
0 module ASCII( out_data, tx_out);
1
2 //端口信号:模块的输入输出接口
3 input [7:0] out_data; //键盘的扫描键值
4 output reg [7:0] tx_out;//通过ASCII码转换之后的值
5
6 //通过查找表的方式,对照ASCII码将键值转换为二进制数值
7 always@(*)
8 case (out_data)
9 8'h1c: tx_out <= 8'h41; //A
10 8'h32: tx_out <= 8'h42; //B
11 8'h21: tx_out <= 8'h43; //C
12 8'h23: tx_out <= 8'h44; //D
13 8'h24: tx_out <= 8'h45; //E
14 8'h2b: tx_out <= 8'h46; //F
15 8'h34: tx_out <= 8'h47; //G
16 8'h33: tx_out <= 8'h48; //H
17 8'h43: tx_out <= 8'h49; //I
18 8'h3b: tx_out <= 8'h4a; //J
19 8'h42: tx_out <= 8'h4b; //K
20 8'h4b: tx_out <= 8'h4c; //L
21 8'h3a: tx_out <= 8'h4d; //M
22 8'h31: tx_out <= 8'h4e; //N
23 8'h44: tx_out <= 8'h4f; //O
24 8'h4d: tx_out <= 8'h50; //P
25 8'h15: tx_out <= 8'h51; //Q
26 8'h2d: tx_out <= 8'h52; //R
27 8'h1b: tx_out <= 8'h53; //S
28 8'h2c: tx_out <= 8'h54; //T
29 8'h3c: tx_out <= 8'h55; //U
30 8'h2a: tx_out <= 8'h56; //V
31 8'h1d: tx_out <= 8'h57; //W
32 8'h22: tx_out <= 8'h58; //X
33 8'h35: tx_out <= 8'h59; //Y
34 8'h1a: tx_out <= 8'h5a; //Z
35 default: tx_out <= 8'h00;
36 endcase
37
38 endmodule
seg_num模块代码:
0 module seg_num (clk, rst_n, num, sel, seg);
1
2 //端口信号:模块的输入输出接口
3 input clk; //系统时钟50MHz
4 input rst_n; //低电平复位
5 input [7:0] num; //输入的数据
6 output reg [2:0] sel; //数码管位选
7 output reg [7:0] seg; //数码管段选
8
9
10 //计数分频,通过选择cnt的相应位的变化来大致分频
11 reg [23:0] cnt;
12 wire clk_r;
13
14 always@(posedge clk or negedge rst_n)
15 begin
16 if(!rst_n)
17 cnt <= 24'd0;
18 else
19 cnt <= cnt + 1'b1;
20 end
21
22 assign clk_r = cnt[15] ; //通过计数cnt的第10位来分频计数,2^10/50M
23 //通过查找表的方式将数据与相应的数码管显示一一对应
24 reg [3:0] data;
25
26 always@(*)
27 case(data)
28 4'h0: seg <= 8'hC0; //8'b1100_0000
29 4'h1: seg <= 8'hF9; //8'b1111_1001
30 4'h2: seg <= 8'hA4; //8'b1010_0100
31 4'h3: seg <= 8'hB0; //8'b1011_0000
32 4'h4: seg <= 8'h99; //8'b1001_1001
33 4'h5: seg <= 8'h92; //8'b1001_0010
34 4'h6: seg <= 8'h82; //8'b1000_0010
35 4'h7: seg <= 8'hF8; //8'b1111_1000
36 4'h8: seg <= 8'h80; //8'b1000_0000
37 4'h9: seg <= 8'h90; //8'b1001_0000
38 4'hA: seg <= 8'h88;
39 4'hB: seg <= 8'h83;
40 4'hC: seg <= 8'hC6;
41 4'hD: seg <= 8'hA1;
42 4'hE: seg <= 8'h86;
43 4'hF: seg <= 8'h8E;
44 default:seg <= 8'hFF; //8'b1111_1111
45 endcase
46
47 //通过查找表的方式,在不同位选下,显示数据的相应位
48 always@(*)
49 begin
50 case(sel)
51 000: data <= num[7:4];
52 001: data <= num[3:0];
53 default:;
54 endcase
55 end
56
57 always@(posedge clk_r or negedge rst_n)
58 begin
59 if(!rst_n)
60 sel <= 3'd0;
61 else if(sel == 3'd1)
62 sel <= 3'd0;
63 else
64 sel <= sel + 1'b1;
65 end
66
67 endmodule
top顶层模块代码:
0 module top (clk, rst_n, ps2_sclk, ps2_sda, sel, seg);
1
2 //外部接口
3 input clk; //系统时钟50MHz
4 input rst_n; //低电平复位
5 input ps2_sclk; //ps2时钟
6 input ps2_sda; //ps2数据
7 output [2:0] sel; //数码管位选
8 output [7:0] seg; //数码管段选
9
10 wire[7:0] out_data, tx_out;
11
12 /*****键盘扫描模块*****/
13 ps2scan ps2scan_inst(
14 .clk(clk),
15 .rst_n(rst_n),
16 .ps2_sclk(ps2_sclk),
17 .ps2_sda(ps2_sda),
18 .out_data(out_data)
19 );
20
21 /*****数据转ASCII码模块*****/
22 ASCII ASCII_inst(
23 .out_data(out_data),
24 .tx_out(tx_out)
25 );
26
27 /*****数码管显示模块*****/
28 seg_num seg_num_inst(
29 .num(tx_out),
30 .clk(clk),
31 .rst_n(rst_n),
32 .sel(sel),
33 .seg(seg)
34 );
35
36 endmodule
top_tb顶层测试代码:
0 `timescale 1ns/1ns
1
2 module top_tb;
3
4 reg clk;
5 reg rst_n;
6 reg ps2_sda;
7 reg ps2_sclk;
8 wire [7:0] seg;
9 wire [2:0] sel;
10
11 top top_dut(
12 .clk(clk),
13 .rst_n(rst_n),
14 .ps2_sclk(ps2_sclk),
15 .ps2_sda(ps2_sda),
16 .sel(sel),
17 .seg(seg)
18 );
19
20 initial begin
21 clk = 1;
22 rst_n = 0;
23 ps2_sda = 1;
24 ps2_sclk = 1;
25
26 #200;
27 rst_n = 1;
28 Key_Event(8'h1A); /* Z */
29 #40000;
30 Key_Event(8'h22); /* X */
31 #80000;
32 Key_Event(8'h44); /* O */
33 #13200;
34 Key_Event(8'h4D); /* P */
35 #25600;
36 Key_Event(8'h24); /* E */
37 #12300;
38 Key_Event(8'h31); /* N */
39
40 #2000000;
41 $stop;
42 end
43
44 /*---------生成工作时钟-----------*/
45 always #10 clk = ~clk;
46
47 /*----任务:以PS2协议发送一个字节的数据-----*/
48 task Send_data;
49 input [7:0]data;
50 begin
51 ps2_sda = 0; /*发送起始位*/
52 #20000;ps2_sclk = 0;
53 #40000;ps2_sclk = 1;
54
55 #20000;ps2_sda = data[0];/*发送第0位*/
56 #20000;ps2_sclk = 0;
57 #40000;ps2_sclk = 1;
58
59 #20000;ps2_sda = data[1];/*发送第1位*/
60 #20000;ps2_sclk = 0;
61 #40000;ps2_sclk = 1;
62
63 #20000;ps2_sda = data[2];/*发送第2位*/
64 #20000;ps2_sclk = 0;
65 #40000;ps2_sclk = 1;
66
67 #20000;ps2_sda = data[3];/*发送第3位*/
68 #20000;ps2_sclk = 0;
69 #40000;ps2_sclk = 1;
70
71 #20000;ps2_sda = data[4];/*发送第4位*/
72 #20000;ps2_sclk = 0;
73 #40000;ps2_sclk = 1;
74
75 #20000;ps2_sda = data[5];/*发送第5位*/
76 #20000;ps2_sclk = 0;
77 #40000;ps2_sclk = 1;
78
79 #20000;ps2_sda = data[6];/*发送第6位*/
80 #20000;ps2_sclk = 0;
81 #40000;ps2_sclk = 1;
82
83 #20000;ps2_sda = data[7];/*发送第7位*/
84 #20000;ps2_sclk = 0;
85 #40000;ps2_sclk = 1;
86
87 #20000;ps2_sda = 0;/*暂时忽略校验位*/
88 #20000;ps2_sclk = 0;
89 #40000;ps2_sclk = 1;
90
91 #20000;ps2_sda = 1;/*停止位*/
92 #20000;ps2_sclk = 0;
93 #40000;ps2_sclk = 1;
94 end
95 endtask
96
97 /*-----任务:模拟按下按键的操作------*/
98 task press_key;
99 input [7:0]Key_Number;
100 begin
101 Send_data(Key_Number);
102 #50000;
103 end
104 endtask
105
106 /*-----任务:模拟释放按键的操作------*/
107 task release_key;
108 input [7:0]Key_Number;
109 begin
110 Send_data(8'hF0);
111 #50000;
112 Send_data(Key_Number);
113 #50000;
114 end
115 endtask
116
117 /*----任务:模拟一次短码的按下和释放操作-----*/
118 task Key_Event;
119 input [7:0]Key_Number;
120 begin
121 press_key(Key_Number);
122 #30000;
123 release_key(Key_Number);
124 end
125 endtask
126
127 endmodule
仿真图:
仿真结果如下图:
测试文件中发送的‘Z’、‘X’、‘O’、‘P’、‘E’、‘N’等字母,在仿真图中显示,通过与ASCII码表对应,得知是正确的。分配引脚,下板后,数码管也得到了与之对应的ASCII码值。
全部0条评论
快来发表一下你的评论吧 !