差别
这里会显示出您选择的修订版和当前版本之间的差别。
后一修订版 | 前一修订版 | ||
ps2键盘模块 [2017/06/09 14:15] anran 创建 |
ps2键盘模块 [2020/01/18 21:38] (当前版本) gongyu |
||
---|---|---|---|
行 1: | 行 1: | ||
- | ======基于STEP FPGA的矩阵按键驱动====== | + | ### 基于STEP FPGA的PS2键盘驱动 |
- | 本节将和大家一起使用FPGA驱动底板上的4x4矩阵键盘。 | + | 本节将和大家一起使用FPGA驱动底板上的PS2接口的键盘外设。 |
+ | --- | ||
+ | #### 硬件说明 | ||
- | ====硬件说明==== | + | 我们的[[STEP-BaseBoard]]底板上集成了PS2键盘的接口,可以供大家连接PS2键盘或PS2鼠标完成相应设计,接下来我们来了解PS2接口的硬件连接及PS2键盘的驱动方法。 |
- | ------- | + | |
- | 在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式,使用行线和列线分别连接到按键开关的两端,这样我们就可以通过4根行线和4根列线(共8个I/O口)连接16个按键,而且按键数量越多优势越明显。 | + | |
- | + | ||
- | FPGA驱动矩阵按键模块,首先我们来了解矩阵按键的硬件连接: | + | |
\\ | \\ | ||
- | {{ :矩阵按键.jpg?800 |}} | + | PS2接口连线非常简单,只需接4根线: |
+ | * 4号引脚VCC接供电电源,一般为5V供电,后经测试3.3V也可以 | ||
+ | * 3号引脚GND接地即可 | ||
+ | * 5号引脚时钟线和1号引脚数据线为两条双向的信号线 | ||
+ | * 2号引脚和6号引脚为保留引脚,不需要连接 | ||
+ | {{ :ps2接口电路.jpg?600 |}} | ||
+ | 当PS2键盘上有按键按动或操作的时候,键盘会发信号给主机,PS2接口的时钟信号和数据信号的时序如下图: | ||
+ | {{ :ps2键盘时序.jpg?800 |}} | ||
+ | FPGA或主机接收键盘发回的数据,通过键盘的编码规则判定键盘当前的操作,扫描码有两种不同的类型:通码(make code)和断码(break code)。当一个键被按下或持续按住时,键盘会将该键的通码发送给主机;而当一个键被释放时,键盘会将该键的断码发送给主机。 | ||
\\ | \\ | ||
- | 上图为4x4矩阵按键的硬件电路图,可以看到4根行线(ROW1、ROW2、ROW3、ROW4)和4根列线(COL1、COL2、COL3、COL4),同时列线通过上拉电阻连接到VCC电压(3.3V),对于矩阵按键来讲: | + | 根据键盘按键扫描码的不同,在此可将按键分为如下几类: |
- | - 4根行线是输入的,是由FPGA控制拉高或拉低, | + | * 第一类按键,通码为1字节,断码为0xF0+通码形式。如A键,其通码为0x1C,断码为0xF0 0x1C。 |
- | - 4根列线数输出的,是由4根行线的输入及按键的状态决定,输出给FPGA | + | * 第二类按键,通码为2字节0xE0+0xXX形式,断码为0xE0+0xF0+0xXX形式。如right ctrl键,其通码为0xE0 0x14,断码为0xE0 0xF0 0x14。 |
- | 当某一时刻,FPGA控制4根行线分别为ROW1=0、ROW2=1、ROW3=1、ROW4=1时, | + | * 第三类特殊按键有两个,print screen键通码为0xE0 0x12 0xE0 0x7C,断码为0xE0 0xF0 0x7C 0xE0 0xF0 0x12; pause键通码为0x E1 0x14 0x77 0xE1 0xF0 0x14 0xF0 0x77,断码为空。 |
- | * 对于K1、K2、K3、K4按键:按下时对应4根列线输出COL1=0、COL2=0、COL3=0、COL4=0,不按时对应4根列线输出COL1=1、COL2=1、COL3=1、COL4=1, | + | 组合按键的扫描码发送按照按键发生的次序,如以下面顺序按左SHIFT+A键:1按下左SHIFT键,2按下A键,3释放A键,4释放左SHIFT键,那么计算机上接收到的一串数据为0x12 0x1C 0xF0 0x1C 0xF0 0x12。 |
- | * 对于K5~~~K16之间的按键:无论按下与否,对应4根列线输出COL1=1、COL2=1、COL3=1、COL4=1, | + | |
- | 通过上面的描述:在这一时刻只有K1、K2、K3、K4按键被按下,才会导致4根列线输出COL1=0、COL2=0、COL3=0、COL4=0,否则COL1=1、COL2=1、COL3=1、COL4=1,反之当FPGA检测到列线(COL1、COL2、COL3、COL4)中有低电平信号时,对应的K1、K2、K3、K4按键应该是被按下了。 | + | |
- | + | ||
- | 按照扫描的方式,一共分为4个时刻,分别对应4根行线中的一根拉低,4个时刻依次循环,这样就完成了矩阵按键的全部扫描检测,我们在程序中以这4个时刻对应状态机的4个状态。 | + | |
- | 至于循环的周期,根据我们基础教程里可知,按键抖动的不稳定时间在10ms以内,所以对同一个按键采样的周期大于10ms,这同样取20ms时间。20ms时间对应4个状态,每5分钟进行一次状态转换。 | + | |
\\ | \\ | ||
- | {{ :矩阵按键程序框图.jpg?800 |}} | + | 在驱动程序设计中,就是根据这样的分类来对不同的按键进行不同处理的,当前简单程序只支持第一类按键的操作。 |
\\ | \\ | ||
- | {{ :矩阵按键扫描法原理.jpg |}} | + | 键盘中不同按键的编码如下: |
+ | {{ :键盘按键通码.jpg?800 |}} | ||
\\ | \\ | ||
- | ====Verilog代码==== | ||
- | ------ | ||
- | <code verilog> | ||
+ | #### Verilog代码 | ||
+ | <code verilog> | ||
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< | // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< | ||
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
- | // Module: Array_KeyBoard | + | // Module: Keyboard_PS2 |
// | // | ||
// Author: Step | // Author: Step | ||
// | // | ||
- | // Description: Array_KeyBoard | + | // Description: PS2 keyboard driver |
// | // | ||
// Web: www.stepfapga.com | // Web: www.stepfapga.com | ||
- | // | + | // |
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
// Code Revision History : | // Code Revision History : | ||
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
// Version: |Mod. Date: |Changes Made: | // Version: |Mod. Date: |Changes Made: | ||
- | // V1.0 |2015/11/11 |Initial ver | + | // V1.0 |2016/04/20 |Initial ver |
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
- | module Array_KeyBoard # | + | module Keyboard_PS2 |
( | ( | ||
- | parameter NUM_FOR_200HZ = 60000 //定义计数器cnt的计数范围,例化时可更改 | + | input clk_in, //系统时钟 |
- | ) | + | input rst_n_in, //系统复位,低有效 |
- | ( | + | input key_clk, //PS2键盘时钟输入 |
- | input clk_in, //系统时钟 | + | input key_data, //PS2键盘数据输入 |
- | input rst_n_in, //系统复位,低有效 | + | output reg key_state, //键盘的按下状态,按下为1,松开为0 |
- | input [3:0] col, //矩阵按键列接口 | + | output reg [7:0] key_ascii //按键键值对应的ASCII编码 |
- | output reg [3:0] row, //矩阵按键行接口 | + | |
- | output reg [15:0] key_out //消抖后的信号 | + | |
); | ); | ||
+ | |||
/* | /* | ||
- | 因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态 | + | 这个模块为FPGA驱动PS2键盘的简单程序,只能支持键盘中第一类按键的单键按动,不支持多个按键同时按动 |
- | 在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样 | + | */ |
- | 周期采样时每隔20ms采样一次,对应这里状态机每隔20ms循环一次,每个状态对应5ms时间 | + | |
- | 对矩阵按键实现原理不明白的,请去了解矩阵按键实现原理 | + | |
- | */ | + | |
- | localparam STATE0 = 2'b00; | + | |
- | localparam STATE1 = 2'b01; | + | |
- | localparam STATE2 = 2'b10; | + | |
- | localparam STATE3 = 2'b11; | + | |
- | //计数器计数分频实现5ms周期信号clk_200hz | + | reg key_clk_r0 = 1'b1,key_clk_r1 = 1'b1; |
- | reg [15:0] cnt; | + | reg key_data_r0 = 1'b1,key_data_r1 = 1'b1; |
- | reg clk_200hz; | + | //对键盘时钟数据信号进行延时锁存 |
- | always@(posedge clk_in or negedge rst_n_in) begin | + | always @ (posedge clk_in or negedge rst_n_in) begin |
- | if(!rst_n_in) begin //复位时计数器cnt清零,clk_200hz信号起始电平为低电平 | + | if(!rst_n_in) begin |
- | cnt <= 16'd0; | + | key_clk_r0 <= 1'b1; |
- | clk_200hz <= 1'b0; | + | key_clk_r1 <= 1'b1; |
- | end else begin | + | key_data_r0 <= 1'b1; |
- | if(cnt >= ((NUM_FOR_200HZ>>1) - 1)) begin //数字逻辑中右移1位相当于除2 | + | key_data_r1 <= 1'b1; |
- | cnt <= 16'd0; | + | end else begin |
- | clk_200hz <= ~clk_200hz; //clk_200hz信号取反 | + | key_clk_r0 <= key_clk; |
- | end else begin | + | key_clk_r1 <= key_clk_r0; |
- | cnt <= cnt + 1'b1; | + | key_data_r0 <= key_data; |
- | clk_200hz <= clk_200hz; | + | key_data_r1 <= key_data_r0; |
- | end | + | |
- | end | + | |
end | end | ||
+ | end | ||
+ | |||
+ | //键盘时钟信号下降沿检测 | ||
+ | wire key_clk_neg = key_clk_r1 & (~key_clk_r0); | ||
- | reg [1:0] c_state; | + | reg [3:0] cnt; |
- | //状态机根据clk_200hz信号在4个状态间循环,每个状态对矩阵按键的行接口单行有效 | + | reg [7:0] temp_data; |
- | always@(posedge clk_200hz or negedge rst_n_in) begin | + | //根据键盘的时钟信号的下降沿读取数据,详细参考PS2键盘数据的传输格式及时序 |
- | if(!rst_n_in) begin | + | always @ (posedge clk_in or negedge rst_n_in) begin |
- | c_state <= STATE0; | + | if(!rst_n_in) begin |
- | row <= 4'b1110; | + | cnt <= 4'd0; |
- | end else begin | + | temp_data <= 8'd0; |
- | case(c_state) | + | end else if(key_clk_neg) begin |
- | STATE0: begin c_state <= STATE1; row <= 4'b1101; end //状态c_state跳转及对应状态下矩阵按键的row输出 | + | if(cnt >= 4'd10) cnt <= 4'd0; |
- | STATE1: begin c_state <= STATE2; row <= 4'b1011; end | + | else cnt <= cnt + 1'b1; |
- | STATE2: begin c_state <= STATE3; row <= 4'b0111; end | + | case (cnt) |
- | STATE3: begin c_state <= STATE0; row <= 4'b1110; end | + | 4'd0: ; //起始位 |
- | default:begin c_state <= STATE0; row <= 4'b1110; end | + | 4'd1: temp_data[0] <= key_data_r1; //数据位bit0 |
- | endcase | + | 4'd2: temp_data[1] <= key_data_r1; //数据位bit1 |
- | end | + | 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 | ||
- | + | end | |
- | //因为每个状态中单行有效,通过对列接口的电平状态采样得到对应4个按键的状态,依次循环 | + | |
- | always@(negedge clk_200hz or negedge rst_n_in) begin | + | reg key_break = 1'b0; |
- | if(!rst_n_in) begin | + | reg [7:0] key_byte = 1'b0; |
- | key_out <= 16'hffff; | + | //根据通码和断码判定按键的当前是按下还是松开 |
- | end else begin | + | always @ (posedge clk_in or negedge rst_n_in) begin |
- | case(c_state) | + | if(!rst_n_in) begin |
- | STATE0:key_out[3:0] <= col; //采集当前状态的列数据赋值给对应的寄存器位 | + | key_break <= 1'b0; |
- | STATE1:key_out[7:4] <= col; | + | key_state <= 1'b0; |
- | STATE2:key_out[11:8] <= col; | + | key_byte <= 1'b0; |
- | STATE3:key_out[15:12] <= col; | + | end else if(cnt==4'd10 && key_clk_neg) begin |
- | default:key_out <= 16'hffff; | + | if(temp_data == 8'hf0) key_break <= 1'b1; //收到段码(8'hf0)表示按键松开,设置断码标示为1 |
- | endcase | + | 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 | 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 | endmodule | ||
- | |||
</code> | </code> | ||
- | \\ | ||
\\ | \\ | ||
+ | --- | ||
+ | #### 小结 | ||
- | ====小结==== | + | 本节主要为大家讲解了PS2接口电路、PS2键盘编码规则及使用FPGA简单驱动PS2键盘的方法,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。 |
- | ------ | + | |
- | 本节主要为大家讲解了矩阵按键的工作原理及软件设计,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。 | + | |
\\ | \\ | ||
如果你对Diamond软件的使用不了解,请参考这里:[[lattice_diamond的使用|Diamond的使用]]。 | 如果你对Diamond软件的使用不了解,请参考这里:[[lattice_diamond的使用|Diamond的使用]]。 | ||
- | ====相关资料==== | + | --- |
- | ------ | + | #### 相关资料 |
\\ | \\ | ||
- | 使用[[STEP-MXO2第二代]]的矩阵按键程序: 后续会有下载连接 待更新 | + | 使用[[STEP-MXO2第二代]]的PS2键盘驱动程序: 后续会有下载连接 待更新 |
\\ | \\ | ||
- | 使用[[STEP-MAX10]]的矩阵按键程序: 后续会有下载连接 待更新 | + | 使用[[STEP-MAX10]]的PS2键盘驱动程序: 后续会有下载连接 待更新 |
\\ | \\ |