写在前面的话
我们从小就开始接触电脑,曾经多么羡慕那些在键盘上洋洋洒洒的人,手指轻柔的飞舞,刻画出一章章美丽的篇幅…那么作为工程师的我们,同样拥有着属于我们的情怀。如果曾经的向往变成我们喜欢的玩具;如果曾经的神秘变成我们夜以继日的痴迷。那么,一切又将如何?梦翼师兄携手大家一起来欣赏、来品味。
项目需求
设计一个ps2键盘的接口驱动电路。
原理分析
ps2的接口如下图所示:
其中,1是数据线DATA;
2是预留N/C;
3是GND;
4是VCC(+5V);
5是时钟信号线CLK;
6是预留N/C;
数据传输的时序图如下图所示:
一般的ps2接口,都是ps2产生时钟信号,而且是在上升沿的时候把数据发送出去,而在下降沿的时候数据被采样,大多数的ps2设备发送数据的时钟频率是15Khz-20Khz。每一帧的数据有11位或者12位数据,其中包括:
1位起始位:总为逻辑0;
8位数据位:低位在前;
1位奇偶校验位;
1位停止位:总为逻辑1;
1位答应位:仅用于主机对设备通信中(在本次键盘接口设计中不用)
当键盘的某一个按键被按下的时候,键盘会向外发送那一个按键的通码,当按键松开的时候,键盘就会向外发送那一个按键的断码,需要注意的是,如果按着一个按键不放的话,键盘会以一定的频率发送那一个按键的通码。
右侧小键盘的0-9的通码与断码如下图所示:
现在我们具体举一个例子来说明ps2接口的工作原理,假设我现在按下小键盘中的0键,再按下按键9,然后把按键0松开,最后再松开按键9,ps2往FPGA发送的数据就会如下,先发0按键的通码8’h70,再发9按键的通码8’h7d,接着发0按键的断码8’hf0 8’h70,接着再发9按键的断码8’hf0 8’h7d,发送数据的顺序如下: 8’h70→8’h7d→8’hf0→8’h70→8’hf0→8’h7d。
系统架构
当ps2_data_out信号有效的时候,valid会拉高一个周期(valid可用于同其他级联模块的握手)。
模块功能介绍
模块名
功能描述
ps2_scan
将ps2接口传输过来数据转成通码或者断码
顶层模块端口描述
端口名
端口说明
clk
系统时钟输入
rst_n
系统复位
Ps2_clk
时钟信号线
Ps2_data_in
数据线
valid
通、断码有效信号(高电平有效)
Ps2_data_out
通、断码信号
用signaltap ii 分析波形
打开signaltap ii ,将采样时钟设置为clk,采样深度为64K。将ps2_clk和ps2_data_in两个输入信号引出来,并将ps2_clk的的触发条件改为下降沿(我们是在ps2_clk为下降沿的时候采集数据),之后进行全编译,并将编译好的sof文件下载到开发板中。
按下数字键“1”(数字小键盘),波形图上出现如下波形:
在ps2_clk每个下降沿,我们进行读数据,分别是:“01001011011”。第一位是起始位“0”,后面连续的8位是低位在前的有效数据:“10010110”,改成高位在前就是“01101001”,也就是我们的8‘h69(1的通码就是8’h69)。第十位“1”为奇偶校验位,第十一位“1”是停止位,这两位不需要我们关心。
放开按键“1”,出现了如下的波形:
在ps2_clk每个下降沿,我们进行读数据,分别是:“00000111111”。第一位是起始位“0”,后面连续的8位是低位在前的有效数据:“00001111”,改成高位在前就是“11110000”,也就是我们的8‘hf0(1的断码就是8‘hf0和8’h69 ,8’h69由于采样深度的原因显示不出来波形,不过我们也能分析出来它是怎么来的)。第十位“1”为奇偶校验位,第十一位“1”是停止位,这两位不需要我们关心。
根据ps2接口的原理和下板实测,我们得知了ps2接口传输数据的方式,那么就可以很容易地编写出我们的代码。
代码解释
Ps2_scan模块代码
/****************************************************
* Engineer : 梦翼师兄
* QQ : 761664056
* The module function: 将ps2接口传输过来的数据译成通、断码
*****************************************************/
000 module ps2_scan (
001 clk, //系统输入时钟
002 rst_n,//系统复位
003 ps2_data_in,//ps2的数据
004 ps2_data_out,//按键的通、断码
005 ps2_clk,//ps2的时钟
006 valid//通、断码有效信号
007 );
008 //系统输入
009 input clk;//系统输入时钟
010 input rst_n;//系统复位
011 input ps2_clk;//ps2的时钟
012 input ps2_data_in;//ps2的数据
013 //系统输出
014 output reg [7:0] ps2_data_out;//按键的通、断码
015 output reg valid;//通、断码有效信号
016
017 wire neg;//下降沿标志线
018 reg ps2_clk_temp;//时钟寄存器
019
020 always @ (posedge clk or negedge rst_n)
021 begin
022 if (!rst_n)
023 ps2_clk_temp <= 0;
024 else
025 ps2_clk_temp <= ps2_clk;//时钟寄存器中的值永远比ps2_clk晚一拍
026 end
027
028
029 //当寄存器里面是1,ps2_clk为0的时候
030 assign neg = ps2_clk_temp && (~ps2_clk);//正好就是ps2_clk为下降沿。
031
032 reg [3:0] num;//接收到数据线上数据的个数
033
034 always @ (posedge clk or negedge rst_n)
035 begin
036 if (!rst_n)
037 begin
038 num <= 0;
039 valid <= 0;
040 ps2_data_out <= 0;
041 end
042 else
043 begin
044 if (neg)
045 begin
046 case (num)
047 0 : num <= num + 1;//起始位
048
049 1 : begin//有效数据的第0位
050 num <= num + 1;
051 ps2_data_out[0] <= ps2_data_in;
052 end
053
054 2 : begin//有效数据的第1位
055 num <= num + 1;
056 ps2_data_out[1] <= ps2_data_in;
057 end
058
059 3 : begin//有效数据的第2位
060 num <= num + 1;
061 ps2_data_out[2] <= ps2_data_in;
062 end
063
064 4 : begin//有效数据的第3位
065 num <= num + 1;
066 ps2_data_out[3] <= ps2_data_in;
067 end
068
069 5 : begin//有效数据的第4位
070 num <= num + 1;
071 ps2_data_out[4] <= ps2_data_in;
072 end
073
074 6 : begin//有效数据的第5位
075 num <= num + 1;
076 ps2_data_out[5] <= ps2_data_in;
077 end
078
079 7 : begin//有效数据的第6位
080 num <= num + 1;
081 ps2_data_out[6] <= ps2_data_in;
082 end
083
084 8 : begin//有效数据的第7位
085 num <= num + 1;
086 ps2_data_out[7] <= ps2_data_in;
087 end
088
089 9 : begin//奇偶校验位
090 num <= num + 1;
091 valid <= 1;//拉高通、断码有效标志
092 end
093
094 10 : num <= 0;//停止位
095
096 default : ;
097 endcase
098 end
099 else
100 begin
101 valid <= 0;//拉低通、断码有效标志
102 end
103 end
104 end
105
106 endmodule
代码中第47行,我们知道第一个是起始位是”0“,故并没有判断,而是直接跳转到下一个状态。
代码中第89行,ps2_data_in发送的是奇偶校验位,在此我们并没有做出判断,而是直接跳转到下一个状态。
代码中第91行,直接拉高通、断码的有效标志信号。
代码中第101行,直接拉低通、断码的有效标志信号。
代码中第94行,ps2_data_in发送的是停止位“0”。
测试代码
/****************************************************
* Engineer : 梦翼师兄
* QQ : 761664056
* The module function: 测试ps2_scan模块
*****************************************************/
000 `timescale 1ns/1ps //定义时间单位和精度
001
002 module ps2_scan_tb;
003 //系统输入
004 reg clk;//系统输入时钟
005 reg rst_n;//系统复位
006 reg ps2_clk;//ps2的时钟
007 reg ps2_data_in;//ps2的数据
008 //系统输出
009 wire [7:0] ps2_data_out;//按键的通、断码
010 wire valid;//通、断码有效信号
011
012 initial begin
013 clk = 1;
014 rst_n = 0;
015 ps2_clk = 1;
016 ps2_data_in = 1;
017 #200.1
018 rst_n = 1;
019 //数字“1”的通码
020 ps2_data_in = 0;//起始位“0”
021 #60
022 ps2_clk = 0;
023 #120
024 ps2_clk = 1;
025 #60
026 ps2_data_in = 1;//“1”
027 #60
028 ps2_clk = 0;
029 #120
030 ps2_clk = 1;
031 #60
032 ps2_data_in = 0;//“0”
033 #60
034 ps2_clk = 0;
035 #120
036 ps2_clk = 1;
037 #60
038 ps2_data_in = 0;//“0”
039 #60
040 ps2_clk = 0;
041 #120
042 ps2_clk = 1;
043 #60
044 ps2_data_in = 1;//“1”
045 #60
046 ps2_clk = 0;
047 #120
048 ps2_clk = 1;
049 #60
050 ps2_data_in = 0;//“0”
051 #60
052 ps2_clk = 0;
053 #120
054 ps2_clk = 1;
055 #60
056 ps2_data_in = 1;//“1”
057 #60
058 ps2_clk = 0;
059 #120
060 ps2_clk = 1;
061 #60
062 ps2_data_in = 1;//“1”
063 #60
064 ps2_clk = 0;
065 #120
066 ps2_clk = 1;
067 #60
068 ps2_data_in = 0;//“0”
069 #60
070 ps2_clk = 0;
071 #120
072 ps2_clk = 1;
073 #60
074 ps2_data_in = 1;//奇偶校验位“1”
075 #60
076 ps2_clk = 0;
077 #120
078 ps2_clk = 1;
079 #60
080 ps2_data_in = 1;//停止位“1”
081 #60
082 ps2_clk = 0;
083 #120
084 ps2_clk = 1;
085 #2000
086 //断码中“f0”
087 ps2_data_in = 0;//起始位“0”
088 #60
089 ps2_clk = 0;
090 #120
091 ps2_clk = 1;
092 #60
093 ps2_data_in = 0;//“0”
094 #60
095 ps2_clk = 0;
096 #120
097 ps2_clk = 1;
098 #60
099 ps2_data_in = 0;//“0”
100 #60
101 ps2_clk = 0;
102 #120
103 ps2_clk = 1;
104 #60
105 ps2_data_in = 0;//“0”
106 #60
107 ps2_clk = 0;
108 #120
109 ps2_clk = 1;
110 #60
111 ps2_data_in = 0;//“0”
112 #60
113 ps2_clk = 0;
114 #120
115 ps2_clk = 1;
116 #60
117 ps2_data_in = 1;//“1”
118 #60
119 ps2_clk = 0;
120 #120
121 ps2_clk = 1;
122 #60
123 ps2_data_in = 1;//“1”
124 #60
125 ps2_clk = 0;
126 #120
127 ps2_clk = 1;
128 #60
129 ps2_data_in = 1;//“1”
130 #60
131 ps2_clk = 0;
132 #120
133 ps2_clk = 1;
134 #60
135 ps2_data_in = 1;//“1”
136 #60
137 ps2_clk = 0;
138 #120
139 ps2_clk = 1;
140 #60
141 ps2_data_in = 1;//奇偶校验位“1”
142 #60
143 ps2_clk = 0;
144 #120
145 ps2_clk = 1;
146 #60
147 ps2_data_in = 1;//停止位“1”
148 #60
149 ps2_clk = 0;
150 #120
151 ps2_clk = 1;
152 #2000
153 //数字“1”的通码
154 ps2_data_in = 0;//起始位“0”
155 #60
156 ps2_clk = 0;
157 #120
158 ps2_clk = 1;
159 #60
160 ps2_data_in = 1;//“1”
161 #60
162 ps2_clk = 0;
163 #120
164 ps2_clk = 1;
165 #60
166 ps2_data_in = 0;//“0”
167 #60
168 ps2_clk = 0;
169 #120
170 ps2_clk = 1;
171 #60
172 ps2_data_in = 0;//“0”
173 #60
174 ps2_clk = 0;
175 #120
176 ps2_clk = 1;
177 #60
178 ps2_data_in = 1;//“1”
179 #60
180 ps2_clk = 0;
181 #120
182 ps2_clk = 1;
183 #60
184 ps2_data_in = 0;//“0”
185 #60
186 ps2_clk = 0;
187 #120
188 ps2_clk = 1;
189 #60
190 ps2_data_in = 1;//“1”
191 #60
192 ps2_clk = 0;
193 #120
194 ps2_clk = 1;
195 #60
196 ps2_data_in = 1;//“1”
197 #60
198 ps2_clk = 0;
199 #120
200 ps2_clk = 1;
201 #60
202 ps2_data_in = 0;//“0”
203 #60
204 ps2_clk = 0;
205 #120
206 ps2_clk = 1;
207 #60
208 ps2_data_in = 1;//奇偶校验位“1”
209 #60
210 ps2_clk = 0;
211 #120
212 ps2_clk = 1;
213 #60
214 ps2_data_in = 1;//停止位“1”
215 #60
216 ps2_clk = 0;
217 #120
218 ps2_clk = 1;
219 end
220
221 always # 10 clk = ~clk;//50M的时钟
222
223 ps2_scan ps2_scan (
224 .clk(clk), //系统输入时钟
225 .rst_n(rst_n),//系统复位
226 .ps2_data_in(ps2_data_in),//ps2的数据
227 .ps2_data_out(ps2_data_out),//按键的通、断码
228 .ps2_clk(ps2_clk),//ps2的时钟
229 .valid(valid)//通、断码有效信号
230 );
231
232 endmodule
测试中,我们发送了数字”1“的通码,断码,模仿了数字键”1“的按下和抬起。
仿真分析
在仿真中,测试了数字 “1”的通、断码的接收和发送,每当检测出一个八位有效数据的时候,valid都会出现一个时钟周期的尖峰脉冲。