PS2键盘,LCD1602显示:此处只简单处理了大写字母和数字(Veilog)
新近写的,水平有限,欢迎拍砖。PS2键盘部分参考了特权的例程,这里表示感谢。
http://blog.ednchina.com/ilove314/153929/message.aspx
ps2_keyboard_test.v
----------------------------------------------------------
module ps2_keyboard_test(
input CLOCK_50,
input Q_KEY,
// PS2 Interface
input PS2_CLK,
input PS2_DAT,
// LCD1602 Interface
output LCD1602_DATA, // LCD1602数据总线
output LCD1602_E, // LCD1602使能
output LCD1602_RS, // LCD1602指令数据选择
output LCD1602_RW // LCD1602读写选择
);
//++++++++++++++++++++++++++++++++++++++
// PS2键盘采集 开始
//++++++++++++++++++++++++++++++++++++++
wire key_val; // 单一键值ASCII码
wire key_pressed; // 键盘按下标志
ps2_keyboard_drive ps2_u0(
.clk(CLOCK_50),
.rst_n(Q_KEY),
//
.ps2_clk(PS2_CLK),
.ps2_dat(PS2_DAT),
//
.key_pressed(key_pressed),
.key_val(key_val)
);
//--------------------------------------
// PS2键盘采集 结束
//--------------------------------------
//++++++++++++++++++++++++++++++++++++++
//LCD1602显示 开始
//++++++++++++++++++++++++++++++++++++++
// 0 ~ (8*16-1) = 128
reg row1_val; // LCD1602 第1行字符
reg row2_val; // LCD1602 第2行字符
always @ (posedge CLOCK_50, negedge Q_KEY)
if (!Q_KEY)
begin
// 16bits-> 0123456789ABCDEF <-
row1_val = "PS2 rcved is ";
row2_val = "Amy-studio Pub";
end
else
row1_val <= key_val;
// 例化LCD1602驱动
lcd1602_drive lcd_1602_u0(
.clk (CLOCK_50),
.rst_n (Q_KEY),
// LCD1602 Input Value
.row1_val (row1_val),
.row2_val (row2_val),
// LCD1602 Interface
.lcd_data (LCD1602_DATA),
.lcd_e (LCD1602_E),
.lcd_rs (LCD1602_RS),
.lcd_rw (LCD1602_RW)
);
//--------------------------------------
//LCD1602显示 结束
//--------------------------------------
endmodule
---------------------------------------------------------- lcd1602_drive.v
----------------------------------------------------------
module lcd1602_drive(
input clk, // 50MHz时钟
input rst_n, // 复位信号
// LCD1602 Input Value
input row1_val, // 第一行字符
input row2_val, // 第二行字符
// LCD1602 Interface
output reg lcd_data, // 数据总线
output lcd_e, // 使能信号
output reg lcd_rs, // 指令、数据选择
output lcd_rw // 读、写选择
);
// +++++++++++++++++++++++++++++++++++++
// 分频模块 开始
// +++++++++++++++++++++++++++++++++++++
reg cnt; // 计数子
always @ (posedge clk, negedge rst_n)
if (!rst_n)
cnt <= 0;
else
cnt <= cnt + 1'b1;
// 500Khz ~ 1MHz 皆可
wire lcd_clk = cnt; // (2^16 / 50M) = 1.31ms
// -------------------------------------
// 分频模块 结束
// -------------------------------------
// +++++++++++++++++++++++++++++++++++++
// LCD1602驱动模块 开始
// +++++++++++++++++++++++++++++++++++++
// 格雷码编码:共40个状态
parameter IDLE = 8'h00;
// 写指令,初始化
parameter DISP_SET = 8'h01; // 显示模式设置
parameter DISP_OFF = 8'h03; // 显示关闭
parameter CLR_SCR = 8'h02; // 显示清屏
parameter CURSOR_SET1= 8'h06; // 显示光标移动设置
parameter CURSOR_SET2= 8'h07; // 显示开及光标设置
// 显示第一行
parameter ROW1_ADDR = 8'h05; // 写第1行起始地址
parameter ROW1_0 = 8'h04;
parameter ROW1_1 = 8'h0C;
parameter ROW1_2 = 8'h0D;
parameter ROW1_3 = 8'h0F;
parameter ROW1_4 = 8'h0E;
parameter ROW1_5 = 8'h0A;
parameter ROW1_6 = 8'h0B;
parameter ROW1_7 = 8'h09;
parameter ROW1_8 = 8'h08;
parameter ROW1_9 = 8'h18;
parameter ROW1_A = 8'h19;
parameter ROW1_B = 8'h1B;
parameter ROW1_C = 8'h1A;
parameter ROW1_D = 8'h1E;
parameter ROW1_E = 8'h1F;
parameter ROW1_F = 8'h1D;
// 显示第二行
parameter ROW2_ADDR = 8'h1C; // 写第2行起始地址
parameter ROW2_0 = 8'h14;
parameter ROW2_1 = 8'h15;
parameter ROW2_2 = 8'h17;
parameter ROW2_3 = 8'h16;
parameter ROW2_4 = 8'h12;
parameter ROW2_5 = 8'h13;
parameter ROW2_6 = 8'h11;
parameter ROW2_7 = 8'h10;
parameter ROW2_8 = 8'h30;
parameter ROW2_9 = 8'h31;
parameter ROW2_A = 8'h33;
parameter ROW2_B = 8'h32;
parameter ROW2_C = 8'h36;
parameter ROW2_D = 8'h37;
parameter ROW2_E = 8'h35;
parameter ROW2_F = 8'h34;
reg current_state, next_state; // 现态、次态
// FSM: always1
always @ (posedge lcd_clk, negedge rst_n)
if(!rst_n)current_state <= IDLE;
else current_state <= next_state;
// FSM: always2
always
begin
case(current_state)
IDLE : next_state = DISP_SET;
// 写指令,初始化
DISP_SET : next_state = DISP_OFF;
DISP_OFF : next_state = CLR_SCR;
CLR_SCR : next_state = CURSOR_SET1;
CURSOR_SET1 : next_state = CURSOR_SET2;
CURSOR_SET2 : next_state = ROW1_ADDR;
// 显示第一行
ROW1_ADDR : next_state = ROW1_0;
ROW1_0 : next_state = ROW1_1;
ROW1_1 : next_state = ROW1_2;
ROW1_2 : next_state = ROW1_3;
ROW1_3 : next_state = ROW1_4;
ROW1_4 : next_state = ROW1_5;
ROW1_5 : next_state = ROW1_6;
ROW1_6 : next_state = ROW1_7;
ROW1_7 : next_state = ROW1_8;
ROW1_8 : next_state = ROW1_9;
ROW1_9 : next_state = ROW1_A;
ROW1_A : next_state = ROW1_B;
ROW1_B : next_state = ROW1_C;
ROW1_C : next_state = ROW1_D;
ROW1_D : next_state = ROW1_E;
ROW1_E : next_state = ROW1_F;
ROW1_F : next_state = ROW2_ADDR;
// 显示第二行
ROW2_ADDR : next_state = ROW2_0;
ROW2_0 : next_state = ROW2_1;
ROW2_1 : next_state = ROW2_2;
ROW2_2 : next_state = ROW2_3;
ROW2_3 : next_state = ROW2_4;
ROW2_4 : next_state = ROW2_5;
ROW2_5 : next_state = ROW2_6;
ROW2_6 : next_state = ROW2_7;
ROW2_7 : next_state = ROW2_8;
ROW2_8 : next_state = ROW2_9;
ROW2_9 : next_state = ROW2_A;
ROW2_A : next_state = ROW2_B;
ROW2_B : next_state = ROW2_C;
ROW2_C : next_state = ROW2_D;
ROW2_D : next_state = ROW2_E;
ROW2_E : next_state = ROW2_F;
ROW2_F : next_state = ROW1_ADDR;
//
default : next_state = IDLE ;
endcase
end
// FSM: always3
always @ (posedge lcd_clk, negedge rst_n)
begin
if(!rst_n)
begin
lcd_rs <= 0;
lcd_data <= 8'hxx;
end
else
begin
// 写lcd_rs
case(next_state)
IDLE : lcd_rs <= 0;
// 写指令,初始化
DISP_SET : lcd_rs <= 0;
DISP_OFF : lcd_rs <= 0;
CLR_SCR : lcd_rs <= 0;
CURSOR_SET1 : lcd_rs <= 0;
CURSOR_SET2 : lcd_rs <= 0;
// 写数据,显示第一行
ROW1_ADDR : lcd_rs <= 0;
ROW1_0 : lcd_rs <= 1;
ROW1_1 : lcd_rs <= 1;
ROW1_2 : lcd_rs <= 1;
ROW1_3 : lcd_rs <= 1;
ROW1_4 : lcd_rs <= 1;
ROW1_5 : lcd_rs <= 1;
ROW1_6 : lcd_rs <= 1;
ROW1_7 : lcd_rs <= 1;
ROW1_8 : lcd_rs <= 1;
ROW1_9 : lcd_rs <= 1;
ROW1_A : lcd_rs <= 1;
ROW1_B : lcd_rs <= 1;
ROW1_C : lcd_rs <= 1;
ROW1_D : lcd_rs <= 1;
ROW1_E : lcd_rs <= 1;
ROW1_F : lcd_rs <= 1;
// 写数据,显示第二行
ROW2_ADDR : lcd_rs <= 0;
ROW2_0 : lcd_rs <= 1;
ROW2_1 : lcd_rs <= 1;
ROW2_2 : lcd_rs <= 1;
ROW2_3 : lcd_rs <= 1;
ROW2_4 : lcd_rs <= 1;
ROW2_5 : lcd_rs <= 1;
ROW2_6 : lcd_rs <= 1;
ROW2_7 : lcd_rs <= 1;
ROW2_8 : lcd_rs <= 1;
ROW2_9 : lcd_rs <= 1;
ROW2_A : lcd_rs <= 1;
ROW2_B : lcd_rs <= 1;
ROW2_C : lcd_rs <= 1;
ROW2_D : lcd_rs <= 1;
ROW2_E : lcd_rs <= 1;
ROW2_F : lcd_rs <= 1;
endcase
// 写lcd_data
case(next_state)
IDLE : lcd_data <= 8'hxx;
// 写指令,初始化
DISP_SET : lcd_data <= 8'h38;
DISP_OFF : lcd_data <= 8'h08;
CLR_SCR : lcd_data <= 8'h01;
CURSOR_SET1 : lcd_data <= 8'h06;
CURSOR_SET2 : lcd_data <= 8'h0C;
// 写数据,显示第一行
ROW1_ADDR : lcd_data <= 8'h80;
ROW1_0 : lcd_data <= row1_val;
ROW1_1 : lcd_data <= row1_val;
ROW1_2 : lcd_data <= row1_val;
ROW1_3 : lcd_data <= row1_val;
ROW1_4 : lcd_data <= row1_val[ 95: 88];
ROW1_5 : lcd_data <= row1_val[ 87: 80];
ROW1_6 : lcd_data <= row1_val[ 79: 72];
ROW1_7 : lcd_data <= row1_val[ 71: 64];
ROW1_8 : lcd_data <= row1_val[ 63: 56];
ROW1_9 : lcd_data <= row1_val[ 55: 48];
ROW1_A : lcd_data <= row1_val[ 47: 40];
ROW1_B : lcd_data <= row1_val[ 39: 32];
ROW1_C : lcd_data <= row1_val[ 31: 24];
ROW1_D : lcd_data <= row1_val[ 23: 16];
ROW1_E : lcd_data <= row1_val[ 15:8];
ROW1_F : lcd_data <= row1_val;
// 写数据,显示第二行
ROW2_ADDR : lcd_data <= 8'hC0;
ROW2_0 : lcd_data <= row2_val;
ROW2_1 : lcd_data <= row2_val;
ROW2_2 : lcd_data <= row2_val;
ROW2_3 : lcd_data <= row2_val;
ROW2_4 : lcd_data <= row2_val[ 95: 88];
ROW2_5 : lcd_data <= row2_val[ 87: 80];
ROW2_6 : lcd_data <= row2_val[ 79: 72];
ROW2_7 : lcd_data <= row2_val[ 71: 64];
ROW2_8 : lcd_data <= row2_val[ 63: 56];
ROW2_9 : lcd_data <= row2_val[ 55: 48];
ROW2_A : lcd_data <= row2_val[ 47: 40];
ROW2_B : lcd_data <= row2_val[ 39: 32];
ROW2_C : lcd_data <= row2_val[ 31: 24];
ROW2_D : lcd_data <= row2_val[ 23: 16];
ROW2_E : lcd_data <= row2_val[ 15:8];
ROW2_F : lcd_data <= row2_val;
endcase
end
end
assign lcd_e= lcd_clk; // 数据在时钟高电平被锁存
assign lcd_rw = 1'b0; // 只写
// -------------------------------------
// LCD1602驱动模块 结束
// -------------------------------------
endmodule
---------------------------------------------------------- http://cache.amobbs.com/bbs_upload782111/files_27/ourdev_537576.png
(原文件名:PS2 键盘值.png)
ps2_keyboard_drive.v
----------------------------------------------------------
module ps2_keyboard_drive(
input clk, // 50M时钟信号
input rst_n, // 复位信号
//
input ps2_clk, // PS2接口时钟信号
input ps2_dat, // PS2接口数据信号
//
output reg key_pressed, // 键盘按下标志
output reg key_val // 单一键值ASCII码
);
//++++++++++++++++++++++++++++++++++++++
// 检测PS2_CLK的下降沿 开始
//++++++++++++++++++++++++++++++++++++++
reg ps2_clk_r0, ps2_clk_r1;
always @ (posedge clk, 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
/* _____ _____ _
*ps2_clk_r0: |_____| |_____|
* _____ _____
* ~ps2_clk_r0: _____| |_____| |_
* _____ _____
* ps2_clk_r1 : _| |_____| |_____
* _ _
* ps2_clk_n: _____| |_________| |_____
*/
wire ps2_clk_n = (~ps2_clk_r0) & ps2_clk_r1;
//--------------------------------------
// 检测PS2_CLK的下降沿 结束
//--------------------------------------
//++++++++++++++++++++++++++++++++++++++
// 从PS2采集数据 开始
//++++++++++++++++++++++++++++++++++++++
reg cnt; // 计数子 0x0 ~ 0xA
always @ (posedge clk, negedge rst_n)
begin
if (!rst_n)
cnt <= 0;
else if (ps2_clk_n) // ps2_clk的下降沿
begin
if (cnt >= 4'hA)
cnt <= 0;
else
cnt <= cnt + 1'b1;
end
end
reg ps2_byte_buf; // 采集到的字节的缓存
always @ (posedge clk, negedge rst_n)
begin
if(!rst_n)
ps2_byte_buf <= 8'h0;
else if (ps2_clk_n) // ps2_clk的下降沿
case (cnt)
4'h1 : ps2_byte_buf <= ps2_dat;// bit0
4'h2 : ps2_byte_buf <= ps2_dat;// bit1
4'h3 : ps2_byte_buf <= ps2_dat;// bit2
4'h4 : ps2_byte_buf <= ps2_dat;// bit3
4'h5 : ps2_byte_buf <= ps2_dat;// bit4
4'h6 : ps2_byte_buf <= ps2_dat;// bit5
4'h7 : ps2_byte_buf <= ps2_dat;// bit6
4'h8 : ps2_byte_buf <= ps2_dat;// bit7
default : ;
endcase
end
//--------------------------------------
// 从PS2采集数据 结束
//--------------------------------------
//++++++++++++++++++++++++++++++++++++++
// 键值处理 开始
//++++++++++++++++++++++++++++++++++++++
reg key_released; // 接收到段码F0后,松开标志
reg ps2_byte; // 采集到的字节
// 处理断码标志
always @ (posedge clk, negedge rst_n)
begin
if (!rst_n)
key_released <= 0;
else if (cnt == 4'hA) // 采集完一个字节?
begin
if (ps2_byte_buf == 8'hF0) // 接收到段码F0后
key_released <= 1; // 松开标志置一
else
key_released <= 0; // 松开标志清零
end
end
// 采集键值
always @ (posedge clk, negedge rst_n)
begin
if (!rst_n)
key_pressed<= 0;
else if (cnt == 4'hA) // 采集完一个字节?
begin
if (!key_released) // 有键按过?
begin
ps2_byte <= ps2_byte_buf; // 锁存当前键值
key_pressed <= 1; // 按下标志置一
end
else
key_pressed <= 0; // 按下标志清零
end
end
//--------------------------------------
// 键值处理 结束
//--------------------------------------
//++++++++++++++++++++++++++++++++++++++
// PS2键值-->ASCII 开始
// 此处只处理大写字母和数字
//++++++++++++++++++++++++++++++++++++++
always @ (*)
case (ps2_byte)
8'h16 : key_val <= "1";
8'h1E : key_val <= "2";
8'h26 : key_val <= "3";
8'h25 : key_val <= "4";
8'h2E : key_val <= "5";
8'h36 : key_val <= "6";
8'h3D : key_val <= "7";
8'h3E : key_val <= "8";
8'h46 : key_val <= "9";
8'h45 : key_val <= "0";
8'h15 : key_val <= "Q";
8'h1D : key_val <= "W";
8'h24 : key_val <= "E";
8'h2D : key_val <= "R";
8'h2C : key_val <= "T";
8'h35 : key_val <= "Y";
8'h3C : key_val <= "U";
8'h43 : key_val <= "I";
8'h44 : key_val <= "O";
8'h4D : key_val <= "P";
8'h1C : key_val <= "A";
8'h1B : key_val <= "S";
8'h23 : key_val <= "D";
8'h2B : key_val <= "F";
8'h34 : key_val <= "G";
8'h33 : key_val <= "H";
8'h3B : key_val <= "J";
8'h42 : key_val <= "K";
8'h4B : key_val <= "L";
8'h1A : key_val <= "Z";
8'h22 : key_val <= "X";
8'h21 : key_val <= "C";
8'h2A : key_val <= "V";
8'h32 : key_val <= "B";
8'h31 : key_val <= "N";
8'h3A : key_val <= "M";
default : key_val <= " ";
endcase
//--------------------------------------
// 键盘值-->ASCII 结束
//--------------------------------------
endmodule
---------------------------------------------------------- mark 学习下 学习。 M 不错 mark mark mark 学习了。。。 mark mark 注释很给力 回复【15楼】avrpicarm
-----------------------------------------------------------------------
呵呵,这就是我的风格。 学习了 学习了。 mark 谢谢楼主的共享,总算搞得动1602了 mark 回复【楼主位】tear086 .COM 缺氧
-----------------------------------------------------------------------
我将代码下载到DE2-70上,发现有时候LCD没有显示,有时候又显示,但是代码初始给的是
PS2 rcved is
Amy-studio Pub 当我通过键盘输入一个字母时 PS2 rcved is
Amy-studio Pub 这个还在,我觉得应该通过代码把他清除掉。 mark! 回复【2楼】tear086 .COM 缺氧
-----------------------------------------------------------------------
超牛啊,领教了 mark~ mark 您好,冒昧问下,能将整个工程上传吗?对开发cpld整个流程还不是很熟悉,谢谢,楼主威武! mark~~ mark~ mark mark: ps/2 keyboard mark mark MARK 深奥。。 我用VHDL也写过 不会Veilog 感觉还好 M{:biggrin:}
页:
[1]