前言:
本文主要介绍了基于FPGA的PS2键盘识别+VGA显示键名的显示原理及操作过程。使用的软件是Quartus Ⅱ,使用fpga芯片为cyclone IV EP4CE115F29C7。
1. 项目内容
本实验以通用的 PS2键盘为输入,设计一个能够识别PS2键盘输入编码的电路,当按下 Shift 键时,可以实现大小写的切换显示。
2. PS2接口协议
引脚说明:
数据格式与发送:
与常用的串口类似,不管是上行还是下行,每帧都包含以下内容:
如果数据位中1的个数为偶数,校验位就为1;如果数据位中1的个数为奇数,校验位就为0;总之,数据位中1的个数加上校验位中1的个数总为奇数,因此总进行奇校验。
ps2设备的clock和data都是集电极开路的,平时都是高电平。当ps2设备等待发送数据时,它首先检查clock是否为高。如果为低,则认为PC抑制了通讯,此时它缓冲数据直到获得总线的控制权。如果clock为高电平,ps2则开始向PC发送数据。
一般都是由ps2设备产生时钟信号。发送按帧格式。数据位在clock为高电平时准备好,在clock下降沿被PC读入。
数据从键盘/鼠标发送到主机或从主机发送到键盘/鼠标,时钟都是PS2设备产生.主机对时钟控制有优先权,即主机想发送控制指令给PS2设备时,可以拉低时钟线至少100μS,然后再下拉数据线,最后释放时钟线为高。PS2设备的时钟线和数据线都是集电极开路的,容易实现拉低电平。PC在时钟的下降沿读取数据。
数据发送时序:
键盘返回值介绍:
键盘的处理器如果发现有键被按下或释放将发送扫描码的信息包到计算机。扫描码有两种不同的类型:通码和断码。当一个键被按下就发送通码,当一个键被释放就发送断码。每个按键被分配了唯一的通码和断码。这样主机通过查找唯一的扫描码就可以测定是哪个按键。每个键一整套的通断码组成了扫描码集。有三套标准的扫描码集:分别是第一套,第二套和第三套。所有现代的键盘默认使用第二套扫描码。
虽然多数第二套通码都只有一个字节宽,但也有少数扩展按键的通码是两字节或四字节宽。这类的通码第一个字节总是为E0。
正如键按下通码就被发往计算机一样,只要键一释放断码就会被发送。每个键都有它自己唯一的通码和断码。在通码和断码之间存在着必然的联系。多数第二套断码有两字节长。它们的第一个字节是F0 ,第二个字节是这个键的通码。扩展按键的断码通常有三个字节,它们前两个字节是E0h,F0h ,最后一个字节是这个按键通码的最后一个字节。
下面列出了几个按键的第二套通码和断码:
一个键盘发送值的例子:
通码和断码是以什么样的序列发送到你的计算机从而使得字符G 出现在你的字处理软件里的呢?因为这是一个大写字母,需要发生这样的事件次序:按下Shift 键-按下G键-释放G 键-释放Shift 键。与这些时间相关的扫描码如下:Shift 键的通码12h,G 键的通码34h ,G 键的断码F0h 34h ,Shift 键的断码F0h 12h 。因此发送到你的计算机的数据应该是:
12h 34h F0h 34h F0h 12h
第二套扫描码:
3.代码实现
3.1 PS/2键盘信号采集代码实现
PS2_DAT , //键盘传入pc的数据
这块代码是个状态机,根据ps2协议,用于接收起始位,8位数据位,奇偶校验位,停止位共11位数据,并对接收的8位数据位进行判断,如果接收的是8‘hf0,则需要进行断码接收,否则的话停止接收,并准备接收下一位。
这块代码是对shift键的功能设计一开始没有任何操作的时候,定义一个变量shift置为0,每按一下shift键时,即ps2_key_data == 8’h12时,变量shift的值进行一次翻转,0的时候,字母小写,1的时候,字母大写。
根据ps2协议键盘按键的通码和相应字母的ASCII码,编写如上代码,以便将接收的按键信号传输给其他模块。
3.2 总体代码
module ps2_scan
(
input wire clk ,
input wire rst_n ,
input wire PS2_CLK ,
input wire PS2_DAT , //键盘传入pc的数据
output reg ps2_state , //接收到按键信息,产生一个时钟高电平
output [7:0] ps2_byte
);
reg ps2_clk_r0,ps2_clk_r1;
wire neg_ps2_clk;
//检测PS2_CLK的下降沿
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ps2_clk_r0 <= 1'b0;
ps2_clk_r1 <= 1'b0;
end
else begin
ps2_clk_r0 <= PS2_CLK;
ps2_clk_r1 <= ps2_clk_r0;
end
end
assign neg_ps2_clk = ~ps2_clk_r0 & ps2_clk_r1;
reg [7:0] ps2_key_data; // 来自PS/2的数据寄存器
reg [7:0] temp_data; // 当前接受数据寄存器
reg [4:0] num;
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
ps2_key_data <= 8'd0;
num <= 5'd0;
temp_data <= 8'd0;
end
else if (neg_ps2_clk) begin
case (num)
5'd0: begin
num <= num + 1'b1; //起始位
end
5'd1: begin
num <= num + 1'b1;
ps2_key_data[0] <= PS2_DAT;
end
5'd2: begin
num <= num + 1'b1;
ps2_key_data[1] <= PS2_DAT;
end
5'd3: begin
num <= num + 1'b1;
ps2_key_data[2] <= PS2_DAT;
end
5'd4: begin
num <= num + 1'b1;
ps2_key_data[3] <= PS2_DAT;
end
5'd5: begin
num <= num + 1'b1;
ps2_key_data[4] <= PS2_DAT;
end
5'd6: begin
num <= num + 1'b1;
ps2_key_data[5] <= PS2_DAT;
end
5'd7: begin
num <= num + 1'b1;
ps2_key_data[6] <= PS2_DAT;
end
5'd8: begin
num <= num + 1'b1;
ps2_key_data[7] <= PS2_DAT;//8位有效信号
end
5'd9: begin
num <= num + 1'b1;//奇偶校验位
end
5'd10: begin
if(ps2_key_data == 8'hf0)
num <= num + 1'b1;//接收断码
else
num <= 5'd0;//停止位
end
5'd11: begin
num <= num + 1'b1;
end
5'd12: begin
num <= num + 1'b1;
temp_data[0] <= PS2_DAT;
end
5'd13: begin
num <= num + 1'b1;
temp_data[1] <= PS2_DAT;
end
5'd14: begin
num <= num + 1'b1;
temp_data[2] <= PS2_DAT;
end
5'd15: begin
num <= num + 1'b1;
temp_data[3] <= PS2_DAT;
end
5'd16: begin
num <= num + 1'b1;
temp_data[4] <= PS2_DAT;
end
5'd17: begin
num <= num + 1'b1;
temp_data[5] <= PS2_DAT;
end
5'd18: begin
num <= num + 1'b1;
temp_data[6] <= PS2_DAT;
end
5'd19: begin
num <= num + 1'b1;
temp_data[7] <= PS2_DAT;
end
5'd20: begin
num <= num + 1'b1;
end
5'd21: begin
num <= 5'd0;
end
default: begin
num <= 5'd0;
end
endcase
end
end
reg shift;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
shift <= 1'b0;
else if(ps2_key_data == 8'h12)
shift <= ~shift;
/* else if(temp_data == 8'h12)
shift <= 1'b0; */
end
reg key_valid;
reg [7:0] ps2_temp_data;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
ps2_state <= 1'b0;
key_valid <= 1'b0;
end
else if (num == 5'd10)
begin
if ((ps2_key_data == 8'hf0) || (ps2_key_data == 8'h12)||(ps2_key_data == 8'he0))
key_valid <= 1'b1;
else
begin
if (!key_valid)
begin
ps2_temp_data <= ps2_key_data;
ps2_state <= 1'b1;
end
else
begin
key_valid <= 1'b0;
ps2_state <= 1'b0;
end
end
end
else if (num == 5'd0)
begin
ps2_state <= 1'b0;
end
end
reg [7:0] ps2_ascii;
always @({shift,ps2_temp_data} )
begin
if(shift == 0)
begin
case(ps2_temp_data)
8'h15:ps2_ascii<= 8'h71;//q
8'h1d:ps2_ascii<= 8'h77;//w
8'h24:ps2_ascii<= 8'h65;//e
8'h2d:ps2_ascii<= 8'h72;//r
8'h2c:ps2_ascii<= 8'h74;//t
8'h35:ps2_ascii<= 8'h79;//y
8'h3c:ps2_ascii<= 8'h75;//u
8'h43:ps2_ascii<= 8'h69;//i
8'h44:ps2_ascii<= 8'h6f;//0
8'h4d:ps2_ascii<= 8'h70;//p
8'h1c:ps2_ascii<= 8'h61;//a
8'h1b:ps2_ascii<= 8'h73;//s
8'h23:ps2_ascii<= 8'h64;//d
8'h2b:ps2_ascii<= 8'h66;//f
8'h34:ps2_ascii<= 8'h67;//g
8'h33:ps2_ascii<= 8'h68;//h
8'h3b:ps2_ascii<= 8'h6a;//j
8'h42:ps2_ascii<= 8'h6b;//k
8'h4b:ps2_ascii<= 8'h6c;//l
8'h1a:ps2_ascii<= 8'h7a;//z
8'h22:ps2_ascii<= 8'h78;//z
8'h21:ps2_ascii<= 8'h63;//z
8'h2a:ps2_ascii<= 8'h76;//z
8'h32:ps2_ascii<= 8'h62;//z
8'h31:ps2_ascii<= 8'h6e;//z
8'h3a:ps2_ascii<= 8'h6d;//z
8'h16:ps2_ascii<= 8'h31;//z
8'h1e:ps2_ascii<= 8'h32;//z
8'h26:ps2_ascii<= 8'h33;//z
8'h25:ps2_ascii<= 8'h34;//z
8'h2e:ps2_ascii<= 8'h35;//z
8'h36:ps2_ascii<= 8'h36;//z
8'h3d:ps2_ascii<= 8'h37;//z
8'h3e:ps2_ascii<= 8'h38;//z
8'h46:ps2_ascii<= 8'h39;//z
8'h45:ps2_ascii<= 8'h30;//z
8'h4e:ps2_ascii<= 8'h2d;//z
8'h55:ps2_ascii<= 8'h3d;//z
8'h54:ps2_ascii<= 8'h5b;//z
8'h5b:ps2_ascii<= 8'h5d;//z
8'h5d:ps2_ascii<= 8'h5c;//z
8'h4c:ps2_ascii<= 8'h3b;//z
8'h52:ps2_ascii<= 8'h27;//z
8'h41:ps2_ascii<= 8'h2c;//z
8'h49:ps2_ascii<= 8'h2e;//z
8'h4a:ps2_ascii<= 8'h2f;//z
8'h0e:ps2_ascii<= 8'h60;//z
8'h66:ps2_ascii<= 8'h08;//backspace
8'h5a:ps2_ascii<= 8'h0d;//enter
8'h75:ps2_ascii<= 8'h80;//u
8'h6b:ps2_ascii<= 8'h81;//l
8'h72:ps2_ascii<= 8'h82;//d
8'h74:ps2_ascii<= 8'h83;//r
default:ps2_ascii<= 8'h00;
endcase
end
else if (shift == 1) //大写
begin
case(ps2_temp_data)
8'h15:ps2_ascii<= 8'h51;//Q//shift和相应
8'h1d:ps2_ascii<= 8'h57;//W
8'h24:ps2_ascii<= 8'h45;//E
8'h2d:ps2_ascii<= 8'h52;//R
8'h2c:ps2_ascii<= 8'h54;//T
8'h35:ps2_ascii<= 8'h59;//Y
8'h3c:ps2_ascii<= 8'h55;//U
8'h43:ps2_ascii<= 8'h49;//I
8'h44:ps2_ascii<= 8'h4f;//0
8'h4d:ps2_ascii<= 8'h50;//P
8'h1c:ps2_ascii<= 8'h41;//A
8'h1b:ps2_ascii<= 8'h53;//S
8'h23:ps2_ascii<= 8'h44;//D
8'h2b:ps2_ascii<= 8'h46;//F
8'h34:ps2_ascii<= 8'h47;//G
8'h33:ps2_ascii<= 8'h48;//H
8'h3b:ps2_ascii<= 8'h4a;//J
8'h42:ps2_ascii<= 8'h4b;//K
8'h4b:ps2_ascii<= 8'h4c;//L
8'h1a:ps2_ascii<= 8'h5a;//Z
8'h22:ps2_ascii<= 8'h58;//X
8'h21:ps2_ascii<= 8'h43;//C
8'h2a:ps2_ascii<= 8'h56;//V
8'h32:ps2_ascii<= 8'h42;//B
8'h31:ps2_ascii<= 8'h4e;//N
8'h3a:ps2_ascii<= 8'h4d;//M
8'h16:ps2_ascii<= 8'h21;//z
8'h1e:ps2_ascii<= 8'h40;//z
8'h26:ps2_ascii<= 8'h23;//z
8'h25:ps2_ascii<= 8'h24;//z
8'h2e:ps2_ascii<= 8'h25;//z
8'h36:ps2_ascii<= 8'h5e;//z
8'h3d:ps2_ascii<= 8'h26;//z
8'h3e:ps2_ascii<= 8'h2a;//z
8'h46:ps2_ascii<= 8'h28;//z
8'h45:ps2_ascii<= 8'h29;//z
8'h4e:ps2_ascii<= 8'h5f;//z
8'h55:ps2_ascii<= 8'h2b;//z
8'h54:ps2_ascii<= 8'h7b;//z
8'h5b:ps2_ascii<= 8'h7d;//z
8'h5d:ps2_ascii<= 8'h7c;//z
8'h4c:ps2_ascii<= 8'h3a;//z
8'h52:ps2_ascii<= 8'h22;//z
8'h41:ps2_ascii<= 8'h3c;//z
8'h49:ps2_ascii<= 8'h3e;//z
8'h4a:ps2_ascii<= 8'h3f;//z
8'h0e:ps2_ascii<= 8'h7e;//z
default:ps2_ascii<= 8'h00;
endcase
end
end
assign ps2_byte = ps2_ascii;
endmodule