简介
今天购买了AXLINX AX7020的开发板,从今天开始每一个例程都要做文档记录,为自己加油。
本实验,基于ALINX AX7020开发板,芯片为xc7z020clg400-2。开发板输入时钟为50MHz。
参考文档:verilog语言的ps2键盘驱动设计
PS2键盘控制及短按、长按
概述
PS2键盘也是一个经典的实验,可能很多人接触如何对通信协议、时序编程就是从这个实验开始学习的。USB键盘已经很普及,现在市场上还是有一些USB转PS2的转接头,还有一些转换芯片。这个实验虽然简单,不过不知道您有考虑过单按一次输出一个有效脉冲、短按、长按等这些是如何实现的么?这就涉及到一些时钟、边沿检测等设计问题。
PS2协议实现
我们见到的PS2的接口电路应该都是这样的:
主要内容:常见的PS/2端口有两类:一种5脚的DIN或6脚的mini-DIN,这两种连接器在电气特性上是十分类似的,两者只有一点不同那就是管脚的排列,具有6 脚mini-DIN 的键盘通常被叫做PS/2 键盘,而那些有5 脚DIN 叫做 AT 设备。常见ps2键盘接口如下所示:
由于电源和地都是主机接口提供,主要用于驱动设计的管脚只有两个,data和clock,该clock时钟是键盘产生的,最大的时钟频率是33kHz 而且大多数设备工作在10 20kHz。
PS/2 鼠标和键盘履行一种双向同步串行协议, 换句话说每次数据线上发送一位数据并且每在时钟线上发一个脉冲就被读入,键盘/ 鼠标可以发送数据到主机,而主机也可以发送数据到设备。
从键盘/ 鼠标发送到主机的数据在时钟信号的下降沿, 当时钟从高变到低的时候 被读取 。
从主机发送到键盘/鼠标的数据在上升沿,当时钟从低变到高的时候被读取。
不管通讯的方向怎样键盘/鼠标总是产生时钟信号该同步串行协议的帧结构如下:(从键盘到主机的帧长度为11bit)
一根时钟线、一根数据线完成通信,PS2通信的帧格式如下所示,时钟的下降沿数据有效:
按键在被按下时,会发送一个字节,这个码就是通码;按键在释放时,会发送两个字节,这个码就做断码(当然也有例外)。每一个按键都有唯一的通码和断码,据此进行判断按下的是哪个键,从而执行对应的功能。
代码:
module ps2_control(
//input
input sys_clk, //50Mhz
input rst_n,
input key_clk, //键盘时钟
input key_data,//键盘数据
//output
output [7:0] data_buf //保存要显示的数据
);
wire key_clk_n;
reg key_clk_1;
reg key_clk_2;
//***********************************************
//检测key_clk的下降沿
//***********************************************
always @(posedge sys_clk or negedge rst_n)
if(!rst_n) begin
key_clk_1 <= 1'b1;
key_clk_2 <= 1'b1;
end
else begin
key_clk_1 <= key_clk;
key_clk_2 <= key_clk_1;
end
assign key_clk_n = key_clk_2 & (~key_clk_1);
//***********************************************
//对key_data上的数据进行保存
//***********************************************
reg [3:0] i;
reg [7:0] data_temp;
always @(posedge sys_clk or negedge rst_n)
if(!rst_n)
begin
i <= 4'd0;
data_temp <= 8'h00;
end
else if(key_clk_n)
begin
case(i)
4'd0: i <= i + 1'b1; //起始位不处理
4'd1,4'd2,4'd3,4'd4,4'd5,4'd6,4'd7,4'd8:
begin
i <= i + 1'b1;
data_temp[i-1] <= key_data;
end
4'd9: i <= i + 1'b1; //奇校验位不处理
4'd10:i <= 4'd0; //停止位不处理
default: ;
endcase
end
reg key_f0; //松键标志位,置1表示接收到数据8'hf0,再接收到下一个数据后清零
reg [7:0] ps2_data;
always @(posedge sys_clk or negedge rst_n) //接收数据的相应处理,这里只对1byte的键值进行处理
if(!rst_n)
begin
key_f0 <= 1'b0;
ps2_data <= 8'h00;
end
else if(i==4'd10) //刚传送完一个字节数据
begin
if(data_temp == 8'hf0)
key_f0 <= 1'b1; //说明有键被释放
else if(!key_f0) //说明有键按下
ps2_data <= data_temp; //锁存当前键值
else
key_f0 <= 1'b0;
end
always @ (ps2_data)
begin
case (ps2_data)
8'h15: data_buf = "Q";
8'h1d: data_buf = "W";
8'h24: data_buf = "E";
8'h2d: data_buf = "R";
8'h2c: data_buf = "T";
8'h35: data_buf = "Y";
8'h3c: data_buf = "U";
8'h43: data_buf = "I";
8'h44: data_buf = "O";
8'h4d: data_buf = "P";
8'h1c: data_buf = "A";
8'h1b: data_buf = "S";
8'h23: data_buf = "D";
8'h2b: data_buf = "F";
8'h34: data_buf = "G";
8'h33: data_buf = "H";
8'h3b: data_buf = "J";
8'h42: data_buf = "K";
8'h4b: data_buf = "L";
8'h1a: data_buf = "Z";
8'h22: data_buf = "X";
8'h21: data_buf = "C";
8'h2a: data_buf = "V";
8'h32: data_buf = "B";
8'h31: data_buf = "N";
8'h3a: data_buf = "M";
default: data_buf = 8'h00;
endcase
end
endmodule