|
基于USB协议的键盘即插即用,目前笔记本电脑基本使用的都是这种键盘。一帧包含8字节数据,2字节为状态标记位,6字节记录当前被按下或弹起的键盘扫描编码,所以USB一次最多只能描述6个按键的状态。而PS/2协议数据位并不受限,帧长度由操作系统本身决定,从理论上来说可以做到无限按键无冲。
PS/2协议是一种双向同步串行协议,即每一次发送数据,时钟脉冲有效即读取数据。主机与鼠标/键盘可以相互发送数据,不过主机在总线上有主控权,可以随时抑制来自设备的数据。 数据帧格式如图所示,键盘发送数据到主机时,总是在时钟下降沿被主机读取。帧起始位为低电平,停止位为高电平,应答位仅用在主机对设备的通讯中。PC通过PS/2接口与从设备通信时,总是在时钟下降沿读取数据。
FPGA笔记3:基于PS2键盘编解码实验
键盘扫描码有两种:通码和断码。当键被按下时,键盘处理器发送通码;当键被释放时,键盘处理器发送断码。每个按键具有唯一的通码和断码。本实验采用的是第二套扫描码集。通常通码只有一个字节(短码),也有少部分扩展按键的通码是两字节或是四字节(长码,长码的通码第一字节是8'he0)。多数断码有两字节,第一个字节是8'hf0,第二个字节是按键的通码。扩展按键的断码有三字节,前两个字节是8'he0,8'hf0,最后一字节是按键的通码。
本模块的主要功能是将键盘的串行数据转换成并行数据。
module sp2(clk,rst_n,
ps2_clk,ps2_data,
ps2_byte,ps2_state,key_off
);
input clk;
input rst_n;
input ps2_clk;//PS/2设备时钟
input ps2_data;//键盘输出的串行数据
output[7:0] ps2_byte;//串行数据转换成并行数据
output ps2_state;//按键按下标志位,1表示有键被按下,0表示没有键被摁下
output key_off;
reg ps2_0,ps2_1,ps2_2;
always @(posedge clk or negedge rst_n)
if (!rst_n)begin
ps2_0<=1'b0;
ps2_1<=1'b0;
ps2_2<=1'b0;
end
else begin
ps2_0<=ps2_clk;
ps2_1<=ps2_0;
ps2_2<=ps2_1;
end
wire ps2_r;
assign ps2_r=ps2_2&~ps2_1;//检测PS/2设备时钟下降沿
reg[3:0] num;
reg[7:0]temp_data;
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
num<=4'b0;
temp_data<=8'b0; end
else if (ps2_r)begin
case(num)
4'd0:num<=num+1'b1;//去除起始位
4'd1:begin num<=num+1'b1;
temp_data[0]<=ps2_data; end
4'd2:begin num<=num+1'b1;
temp_data[1]<=ps2_data; end
4'd3:begin num<=num+1'b1;
temp_data[2]<=ps2_data; end
4'd4:begin num<=num+1'b1;
temp_data[3]<=ps2_data; end
4'd5:begin num<=num+1'b1;
temp_data[4]<=ps2_data; end
4'd6:begin num<=num+1'b1;
temp_data[5]<=ps2_data;end
4'd7:begin num<=num+1'b1;
temp_data[6]<=ps2_data;end
4'd8:begin num<=num+1'b1;
temp_data[7]<=ps2_data; end
4'd9:num<=num+1'b1;//本实验没有校验位
4'd10: num<=4'b0;//串行数据转换成并行数据完成
default: ;
endcase end
reg key_off;//按键松开标志位,1表示按键松开,0表示按键未松开
reg[7:0] temp_byte_r;
reg ps2_state_r;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
key_off<=1'b0;
temp_byte_r<=8'b0;
ps2_state_r<=1'b0;
end
else if (num==10)begin
if (temp_data==8'hf0)begin
key_off<=1'b1;//判断为断码,释放按键
ps2_state_r<=1'b0; end
else if(!key_off) begin //判断为通码,按键按下
temp_byte_r<=temp_data;
ps2_state_r<=1'b1; end
else begin //若是长码按键,则无效
key_off<=1'b0;
ps2_state_r<=1'b0;end
end
assign ps2_state=ps2_state_r;
assign ps2_byte=temp_byte_r;
endmodule
ps2设备的最大时钟频率为33Khz,大多数情况下工作在10-20Khz,推荐频率是15Khz。为测试上述程序是否正确,编写了testbench,仿真其正确性。其中键盘数据发送过程如下:
1) 等待Clock线为高电平,即等待主机释放Clock线;
2) 延时50us;
3) 判断Clock线是否为高电平? No—跳到第1步;
4) Data线是否为高电平?No—放弃(跳到从主机读取字节的程序中);
5) 延迟20us,输出起始位(0),然后延迟20us,再拉低Clock线保持40us后释放Clock线,形成一个脉冲;
6) 延时20us,测试Clock线是否为高电平?No— 跳到第1步;
7) 输出第1个数据位,然后延时20us,再拉低Clock线保持40us后释放Clock线,形成一个脉冲;
8) 重复6-7步发送剩下的7个数据位和校验位;
9) 延时20us,测试Clock线是否为高电平? No— 跳到第1步;
FPGA笔记3:基于PS2键盘编解码实验
从图中可以看出,改测试文件先后模拟了“Z”和“X”键按下与释放过程,键盘发送的串行数据ps2_data变成了并行数据ps2_byte。
`timescale 1 ns/ 1 ps
module sp2_vlg_tst();
reg clk;
reg ps2_clk;
reg ps2_data;
reg rst_n;
wire key_off;
wire [7:0] ps2_byte;
wire ps2_state;
sp2 i1 (
.clk(clk),
.ps2_byte(ps2_byte),
.ps2_clk(ps2_clk),
.ps2_data(ps2_data),
.ps2_state(ps2_state),
.rst_n(rst_n),
.key_off(key_off)
);
initial begin
clk=1;
forever #10 clk=~clk;
end
initial begin
rst_n=0;
ps2_data=1;
ps2_clk=1;
#200;
rst_n=1;//撤销复位
key_event(8'h1A);//发送Z
#400;
key_event(8'h35);//发送X
#800;
terminate;
end
task press_key;
input [7:0]key_number;
begin
data_send(8'hf0);
#50000;
data_send(key_number);
#50000;
end
endtask
task release_key;
input [7:0]key_number;
begin data_send(key_number);
#50000;
end
endtask
task key_event;
input [7:0]key_number;
begin press_key(key_number);
#30000;
release_key(key_number);
end
endtask
task data_send;
input [7:0]data;
begin
ps2_data=0;//发送起始位
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=data[0];//发送第0位数据
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=data[1];//发送第1位数据
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=data[2];//发送第2位数据
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=data[3];//发送第3位数据
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=data[4];//发送第4位数据
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=data[5];//发送第5位数据
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=data[6];//发送第6位数据
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=data[7];//发送第7位数据
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=0;//发送校验位,目前没有考虑
#20000;ps2_clk=0;
#40000;ps2_clk=1;
#20000;ps2_data=1;//发送停止位
#20000;ps2_clk=0;
#40000;ps2_clk=1;
end
endtask
task terminate;
begin
$write("Simulation Successful");
$stop;
end
endtask
endmodule
|
|