本节将和大家一起使用FPGA驱动底板上的PS2接口的键盘外设。


硬件说明

我们的STEP-BaseBoard底板上集成了PS2键盘的接口,可以供大家连接PS2键盘或PS2鼠标完成相应设计,接下来我们来了解PS2接口的硬件连接及PS2键盘的驱动方法。
PS2接口连线非常简单,只需接4根线:

  • 4号引脚VCC接供电电源,一般为5V供电,后经测试3.3V也可以
  • 3号引脚GND接地即可
  • 5号引脚时钟线和1号引脚数据线为两条双向的信号线
  • 2号引脚和6号引脚为保留引脚,不需要连接

当PS2键盘上有按键按动或操作的时候,键盘会发信号给主机,PS2接口的时钟信号和数据信号的时序如下图: FPGA或主机接收键盘发回的数据,通过键盘的编码规则判定键盘当前的操作,扫描码有两种不同的类型:通码(make code)和断码(break code)。当一个键被按下或持续按住时,键盘会将该键的通码发送给主机;而当一个键被释放时,键盘会将该键的断码发送给主机。
根据键盘按键扫描码的不同,在此可将按键分为如下几类:

  • 第一类按键,通码为1字节,断码为0xF0+通码形式。如A键,其通码为0x1C,断码为0xF0 0x1C。
  • 第二类按键,通码为2字节0xE0+0xXX形式,断码为0xE0+0xF0+0xXX形式。如right ctrl键,其通码为0xE0 0x14,断码为0xE0 0xF0 0x14。
  • 第三类特殊按键有两个,print screen键通码为0xE0 0x12 0xE0 0x7C,断码为0xE0 0xF0 0x7C 0xE0 0xF0 0x12; pause键通码为0x E1 0x14 0x77 0xE1 0xF0 0x14 0xF0 0x77,断码为空。

组合按键的扫描码发送按照按键发生的次序,如以下面顺序按左SHIFT+A键:1按下左SHIFT键,2按下A键,3释放A键,4释放左SHIFT键,那么计算机上接收到的一串数据为0x12 0x1C 0xF0 0x1C 0xF0 0x12。
在驱动程序设计中,就是根据这样的分类来对不同的按键进行不同处理的,当前简单程序只支持第一类按键的操作。
键盘中不同按键的编码如下:

Verilog代码

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: Keyboard_PS2
// 
// Author: Step
// 
// Description: PS2 keyboard driver
// 
// Web: www.stepfapga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2016/04/20   |Initial ver
// --------------------------------------------------------------------
module Keyboard_PS2
(
input					clk_in,				//系统时钟
input					rst_n_in,			//系统复位,低有效
input					key_clk,			//PS2键盘时钟输入
input					key_data,			//PS2键盘数据输入
output	reg				key_state,			//键盘的按下状态,按下为1,松开为0
output	reg		[7:0]	key_ascii			//按键键值对应的ASCII编码
);
 
/*
这个模块为FPGA驱动PS2键盘的简单程序,只能支持键盘中第一类按键的单键按动,不支持多个按键同时按动
*/
 
reg		key_clk_r0 = 1'b1,key_clk_r1 = 1'b1; 
reg		key_data_r0 = 1'b1,key_data_r1 = 1'b1;
//对键盘时钟数据信号进行延时锁存
always @ (posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in) begin
		key_clk_r0 <= 1'b1;
		key_clk_r1 <= 1'b1;
		key_data_r0 <= 1'b1;
		key_data_r1 <= 1'b1;
	end else begin
		key_clk_r0 <= key_clk;
		key_clk_r1 <= key_clk_r0;
		key_data_r0 <= key_data;
		key_data_r1 <= key_data_r0;
	end
end
 
//键盘时钟信号下降沿检测
wire	key_clk_neg = key_clk_r1 & (~key_clk_r0); 
 
reg				[3:0]	cnt; 
reg				[7:0]	temp_data;
//根据键盘的时钟信号的下降沿读取数据,详细参考PS2键盘数据的传输格式及时序
always @ (posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in) begin
		cnt <= 4'd0;
		temp_data <= 8'd0;
	end else if(key_clk_neg) begin 
		if(cnt >= 4'd10) cnt <= 4'd0;
		else cnt <= cnt + 1'b1;
		case (cnt)
			4'd0: ;	//起始位
			4'd1: temp_data[0] <= key_data_r1;  //数据位bit0
			4'd2: temp_data[1] <= key_data_r1;  //数据位bit1
			4'd3: temp_data[2] <= key_data_r1;  //数据位bit2
			4'd4: temp_data[3] <= key_data_r1;  //数据位bit3
			4'd5: temp_data[4] <= key_data_r1;  //数据位bit4
			4'd6: temp_data[5] <= key_data_r1;  //数据位bit5
			4'd7: temp_data[6] <= key_data_r1;  //数据位bit6
			4'd8: temp_data[7] <= key_data_r1;  //数据位bit7
			4'd9: ;	//校验位
			4'd10:;	//结束位
			default: ;
		endcase
	end
end
 
reg						key_break = 1'b0;   
reg				[7:0]	key_byte = 1'b0;
//根据通码和断码判定按键的当前是按下还是松开
always @ (posedge clk_in or negedge rst_n_in) begin 
	if(!rst_n_in) begin
		key_break <= 1'b0;
		key_state <= 1'b0;
		key_byte <= 1'b0;
	end else if(cnt==4'd10 && key_clk_neg) begin 
		if(temp_data == 8'hf0) key_break <= 1'b1;	//收到段码(8'hf0)表示按键松开,设置断码标示为1
		else if(!key_break) begin 	//当断码标示为0时,表示当前数据为按下数据,输出键值并设置按下标示为1
			key_state <= 1'b1;
			key_byte <= temp_data; 
		end else begin	//当断码标示为1时,标示当前数据为松开数据,断码标示和按下标示都清零
			key_state <= 1'b0;
			key_break <= 1'b0;
		end
	end
end
 
//将键盘返回的有效键值转换为按键字母对应的ASCII码值
always @ (key_byte) begin
	case (key_byte)    //translate key_byte to key_ascii
		8'h15: key_ascii = "Q";//8'h51;   //Q
		8'h1d: key_ascii = "W";//8'h57;   //W
		8'h24: key_ascii = "E";//8'h45;   //E
		8'h2d: key_ascii = "R";//8'h52;   //R
		8'h2c: key_ascii = "T";//8'h54;   //T
		8'h35: key_ascii = "Y";//8'h59;   //Y
		8'h3c: key_ascii = "U";//8'h55;   //U
		8'h43: key_ascii = "I";//8'h49;   //I
		8'h44: key_ascii = "O";//8'h4f;   //O
		8'h4d: key_ascii = "P";//8'h50;   //P
		8'h1c: key_ascii = "A";//8'h41;   //A
		8'h1b: key_ascii = "S";//8'h53;   //S
		8'h23: key_ascii = "D";//8'h44;   //D
		8'h2b: key_ascii = "F";//8'h46;   //F
		8'h34: key_ascii = "G";//8'h47;   //G
		8'h33: key_ascii = "H";//8'h48;   //H
		8'h3b: key_ascii = "J";//8'h4a;   //J
		8'h42: key_ascii = "K";//8'h4b;   //K
		8'h4b: key_ascii = "L";//8'h4c;   //L
		8'h1a: key_ascii = "Z";//8'h5a;   //Z
		8'h22: key_ascii = "X";//8'h58;   //X
		8'h21: key_ascii = "C";//8'h43;   //C
		8'h2a: key_ascii = "V";//8'h56;   //V
		8'h32: key_ascii = "B";//8'h42;   //B
		8'h31: key_ascii = "N";//8'h4e;   //N
		8'h3a: key_ascii = "M";//8'h4d;   //M
		default: ;
	endcase
end
 
endmodule



小结

本节主要为大家讲解了PS2接口电路、PS2键盘编码规则及使用FPGA简单驱动PS2键盘的方法,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。
如果你对Diamond软件的使用不了解,请参考这里:Diamond的使用


相关资料


使用STEP-MXO2第二代的PS2键盘驱动程序: 后续会有下载连接 待更新
使用STEP-MAX10的PS2键盘驱动程序: 后续会有下载连接 待更新