当前位置: 首页 > news >正文

【特权FPGA】之PS/2键盘解码

 0 故事背景

见过这种接口的朋友们,大概都已经成家立业了吧。不过今天我们不讨论这种接口的历史,只讲讲这种接口的设计。(如果还没有成家的朋友也别生气,做自己想做的事情就对了!)

1 时序分析

数据帧格式如图所示,起始位为低电平,停止位为高电平,应答位仅用在主机对设备的通讯中使用。如果数据位中1的个数为偶数,校验位就为1;如果数据位中1的个数为奇数,校验位就为0;总之,数据位中1的个数加上校验位中1的个数总为奇数,因此总进行奇校验。(是不是发现它的数据传输和串口很像呢!)[1]

(为了简化)当一个键(A~Z)被按下或按住,就发送通码(都是f0);当一个键(A~Z)被释放,就发送断码。

键盘扫描码(实用于标准PC的101、102和104 键的键盘),按下发送通码,弹起发送断码。[2]了解即可。

2 接口定义

信号名称方向接口描述信息
clkinput时钟信号,50MHz
rst_ninput复位信号,低电平有效
ps2k_clkinputPS/2接口时钟信号
ps2k_datainputPS/2接口数据信号
rs232_txinputRS232发送数据信号

3 RTL视图

4 整体代码

Top层代码:

`timescale 1ns / 1ps

// Company: 
// Engineer:
//
// Create Date:    21:21:41 08/07/08
// Design Name:    
// Module Name:    ps2_key
// Project Name:   
// Target Device:  
// Tool versions:  
// Description:
//
// Dependencies:
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 欢迎加入EDN的FPGA/CPLD助学小组一起讨论:http://group.ednchina.com/1375/

module ps2_key(clk,rst_n,ps2k_clk,ps2k_data,rs232_tx);

input clk;			//50M时钟信号
input rst_n;		//复位信号
input ps2k_clk;		//PS2接口时钟信号
input ps2k_data;	//PS2接口数据信号
output rs232_tx;	// RS232发送数据信号


wire[7:0] ps2_byte;	// 1byte键值
wire ps2_state;		//按键状态标志位

wire bps_start;		//接收到数据后,波特率时钟启动信号置位
wire clk_bps;		// clk_bps的高电平为接收或者发送数据位的中间采样点 

ps2scan			ps2scan(		.clk(clk),			  	//按键扫描模块
								.rst_n(rst_n),				
								.ps2k_clk(ps2k_clk),
								.ps2k_data(ps2k_data),
								.ps2_byte(ps2_byte),
								.ps2_state(ps2_state)
								);

speed_select	speed_select(			.clk(clk),
										.rst_n(rst_n),
										.bps_start(bps_start),
										.clk_bps(clk_bps)
										);

my_uart_tx		my_uart_tx(				.clk(clk),
										.rst_n(rst_n),
										.clk_bps(clk_bps),
										.rx_data(ps2_byte),
										.rx_int(ps2_state),
										.rs232_tx(rs232_tx),
										.bps_start(bps_start)
										);

endmodule

ps2scan代码

`timescale 1ns / 1ps

// Company: 
// Engineer:
//
// Create Date:    21:25:06 08/07/08
// Design Name:    
// Module Name:    ps2scan
// Project Name:   
// Target Device:  
// Tool versions:  
// Description:
//
// Dependencies:
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 

module ps2scan(
				clk,
				rst_n,
				ps2k_clk,
				ps2k_data,
				ps2_byte,
				ps2_state
				);

input        clk;		//50M时钟信号
input        rst_n;	//复位信号
input   	 ps2k_clk;	//PS2接口时钟信号
input   	 ps2k_data;		//PS2接口数据信号
output[7:0]  ps2_byte;	// 1byte键值,只做简单的按键扫描
output       ps2_state;		//键盘当前状态,ps2_state=1表示有键被按下 

//------------------------------------------
reg ps2k_clk_r0,ps2k_clk_r1,ps2k_clk_r2;	//ps2k_clk状态寄存器

//wire pos_ps2k_clk; 	// ps2k_clk上升沿标志位
wire neg_ps2k_clk;	// ps2k_clk下降沿标志位

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
			ps2k_clk_r0 <= 1'b0;
			ps2k_clk_r1 <= 1'b0;
			ps2k_clk_r2 <= 1'b0;
		end
	else begin								//锁存状态,进行滤波
			ps2k_clk_r0 <= ps2k_clk;
			ps2k_clk_r1 <= ps2k_clk_r0;
			ps2k_clk_r2 <= ps2k_clk_r1;
		end
end

assign neg_ps2k_clk = ~ps2k_clk_r1 & ps2k_clk_r2;	//下降沿

//------------------------------------------
reg[7:0] ps2_byte_r;		//PC接收来自PS2的一个字节数据存储器
reg[7:0] temp_data;			//当前接收数据寄存器
reg[3:0] num;				//计数寄存器

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
			num <= 4'd0;
			temp_data <= 8'd0;
		end
	else if(neg_ps2k_clk) begin	//检测到ps2k_clk的下降沿
			case (num)
				4'd0:	num <= num+1'b1;
				4'd1:	begin
							num <= num+1'b1;
							temp_data[0] <= ps2k_data;	//bit0
						end
				4'd2:	begin
							num <= num+1'b1;
							temp_data[1] <= ps2k_data;	//bit1
						end
				4'd3:	begin
							num <= num+1'b1;
							temp_data[2] <= ps2k_data;	//bit2
						end
				4'd4:	begin
							num <= num+1'b1;
							temp_data[3] <= ps2k_data;	//bit3
						end
				4'd5:	begin
							num <= num+1'b1;
							temp_data[4] <= ps2k_data;	//bit4
						end
				4'd6:	begin
							num <= num+1'b1;
							temp_data[5] <= ps2k_data;	//bit5
						end
				4'd7:	begin
							num <= num+1'b1;
							temp_data[6] <= ps2k_data;	//bit6
						end
				4'd8:	begin
							num <= num+1'b1;
							temp_data[7] <= ps2k_data;	//bit7
						end
				4'd9:	begin
							num <= num+1'b1;	//奇偶校验位,不做处理
						end
				4'd10: begin
							num <= 4'd0;	// num清零
						end
				default: ;
				endcase
		end	
end

reg key_f0;		    // 松键标志位,置1表示接收到数据8'hf0,再接收到下一个数据后清零
reg ps2_state_r;	// 键盘当前状态,ps2_state_r=1表示有键被按下 

always @ (posedge clk or negedge rst_n)  begin	//接收数据的相应处理,这里只对1byte的键值进行处理
	if(!rst_n) begin
			key_f0 <= 1'b0;
			ps2_state_r <= 1'b0;
		end
	else if(num==4'd10) begin	//刚传送完一个字节数据
			if(temp_data == 8'hf0) key_f0 <= 1'b1;
			else begin
					if(!key_f0) begin	//说明有键按下
							ps2_state_r <= 1'b1;
							ps2_byte_r <= temp_data;	//锁存当前键值
						end
					else begin
							ps2_state_r <= 1'b0;
							key_f0 <= 1'b0;
						end
				end
		end
end

reg[7:0] ps2_asci;	//接收数据的相应ASCII码

always @ (ps2_byte_r) begin
	case (ps2_byte_r)		//键值转换为ASCII码,这里做的比较简单,只处理字母
		8'h15: ps2_asci <= 8'h51;	//Q
		8'h1d: ps2_asci <= 8'h57;	//W
		8'h24: ps2_asci <= 8'h45;	//E
		8'h2d: ps2_asci <= 8'h52;	//R
		8'h2c: ps2_asci <= 8'h54;	//T
		8'h35: ps2_asci <= 8'h59;	//Y
		8'h3c: ps2_asci <= 8'h55;	//U
		8'h43: ps2_asci <= 8'h49;	//I
		8'h44: ps2_asci <= 8'h4f;	//O
		8'h4d: ps2_asci <= 8'h50;	//P				  	
		8'h1c: ps2_asci <= 8'h41;	//A
		8'h1b: ps2_asci <= 8'h53;	//S
		8'h23: ps2_asci <= 8'h44;	//D
		8'h2b: ps2_asci <= 8'h46;	//F
		8'h34: ps2_asci <= 8'h47;	//G
		8'h33: ps2_asci <= 8'h48;	//H
		8'h3b: ps2_asci <= 8'h4a;	//J
		8'h42: ps2_asci <= 8'h4b;	//K
		8'h4b: ps2_asci <= 8'h4c;	//L
		8'h1a: ps2_asci <= 8'h5a;	//Z
		8'h22: ps2_asci <= 8'h58;	//X
		8'h21: ps2_asci <= 8'h43;	//C
		8'h2a: ps2_asci <= 8'h56;	//V
		8'h32: ps2_asci <= 8'h42;	//B
		8'h31: ps2_asci <= 8'h4e;	//N
		8'h3a: ps2_asci <= 8'h4d;	//M
		default: ;
		endcase
end

assign ps2_byte = ps2_asci;	 
assign ps2_state = ps2_state_r;

endmodule

speed_select代码

`timescale 1ns / 1ps

// Company: 
// Engineer:
//
// Create Date:    17:27:40 08/28/08
// Design Name:    
// Module Name:    speed_select
// Project Name:   
// Target Device:  
// Tool versions:  
// Description:
//
// Dependencies:
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 

module speed_select(
				clk,rst_n,
				bps_start,clk_bps
			);

input clk;	// 50MHz主时钟
input rst_n;	//低电平复位信号
input bps_start;	//接收到数据后,波特率时钟启动信号置位
output clk_bps;	// clk_bps的高电平为接收或者发送数据位的中间采样点 

/*
parameter 		bps9600 	= 5207,	//波特率为9600bps
			 	bps19200 	= 2603,	//波特率为19200bps
				bps38400 	= 1301,	//波特率为38400bps
				bps57600 	= 867,	//波特率为57600bps
				bps115200	= 433;	//波特率为115200bps

parameter 		bps9600_2 	= 2603,
				bps19200_2	= 1301,
				bps38400_2	= 650,
				bps57600_2	= 433,
				bps115200_2 = 216;  
*/

	//以下波特率分频计数值可参照上面的参数进行更改
`define		BPS_PARA		5207	//波特率为9600时的分频计数值
`define 	BPS_PARA_2		2603	//波特率为9600时的分频计数值的一半,用于数据采样

reg[12:0] cnt;			//分频计数
reg clk_bps_r;			//波特率时钟寄存器

//----------------------------------------------------------
reg[2:0] uart_ctrl;	// uart波特率选择寄存器
//----------------------------------------------------------

always @ (posedge clk or negedge rst_n)
	if(!rst_n) cnt <= 13'd0;
	else if((cnt == `BPS_PARA) || !bps_start) cnt <= 13'd0;	//波特率计数清零
	else cnt <= cnt+1'b1;			//波特率时钟计数启动

always @ (posedge clk or negedge rst_n)
	if(!rst_n) clk_bps_r <= 1'b0;
	else if(cnt == `BPS_PARA_2) clk_bps_r <= 1'b1;	// clk_bps_r高电平为接收数据位的中间采样点,同时也作为发送数据的数据改变点
	else clk_bps_r <= 1'b0;

assign clk_bps = clk_bps_r;

endmodule



my_uart_tx代码

`timescale 1ns / 1ps

// Company: 
// Engineer:
//
// Create Date:    17:11:32 08/28/08
// Design Name:    
// Module Name:    my_uart_rx
// Project Name:   
// Target Device:  
// Tool versions:  
// Description:
//
// Dependencies:
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 

module my_uart_tx(
					clk,
					rst_n,
					clk_bps,
					rx_data,
					rx_int,
					rs232_tx,
					bps_start
					);

input clk;			// 50MHz主时钟
input rst_n;		//低电平复位信号
input clk_bps;		// clk_bps的高电平为接收或者发送数据位的中间采样点
input[7:0] rx_data;	//接收数据寄存器
input rx_int;		//接收数据中断信号,接收到数据期间始终为高电平,在此利用它的上升沿来启动发送数据
output rs232_tx;	// RS232发送数据信号
output bps_start;	//接收或者要发送数据,波特率时钟启动信号置位

//---------------------------------------------------------
reg rx_int0,rx_int1,rx_int2;	//rx_int信号寄存器,捕捉下降沿滤波用
wire pos_rx_int;				// rx_int下降沿标志位

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
			rx_int0 <= 1'b0;
			rx_int1 <= 1'b0;
			rx_int2 <= 1'b0;
		end
	else begin
			rx_int0 <= rx_int;
			rx_int1 <= rx_int0;
			rx_int2 <= rx_int1;
		end
end

assign pos_rx_int =  rx_int1 & ~rx_int2;	//捕捉到上升沿后,neg_rx_int拉地保持一个主时钟周期

//---------------------------------------------------------
reg[7:0] tx_data;	//待发送数据的寄存器
//---------------------------------------------------------
reg bps_start_r;
reg tx_en;	//发送数据使能信号,高有效
reg[3:0] num;

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
			bps_start_r <= 1'bz;
			tx_en <= 1'b0;
			tx_data <= 8'd0;
		end
	else if(pos_rx_int) begin	//接收数据完毕,准备把接收到的数据发出去
			bps_start_r <= 1'b1;
			tx_data <= rx_data;	//把接收到的数据存入发送数据寄存器
			tx_en <= 1'b1;		//进入发送数据状态中
		end
	else if(num==4'd11) begin	//数据发送完成,复位
			bps_start_r <= 1'b0;
			tx_en <= 1'b0;
		end
end

assign bps_start = bps_start_r;

//---------------------------------------------------------
reg rs232_tx_r;

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
			num <= 4'd0;
			rs232_tx_r <= 1'b1;
		end
	else if(tx_en) begin
			if(clk_bps)	begin
					num <= num+1'b1;
					case (num)
						4'd0:	rs232_tx_r <= 1'b0; 	//发送起始位
						4'd1:	rs232_tx_r <= tx_data[0];	//发送bit0
						4'd2:	rs232_tx_r <= tx_data[1];	//发送bit1
						4'd3: rs232_tx_r <= tx_data[2];	//发送bit2
						4'd4: rs232_tx_r <= tx_data[3];	//发送bit3
						4'd5: rs232_tx_r <= tx_data[4];	//发送bit4
						4'd6: rs232_tx_r <= tx_data[5];	//发送bit5
						4'd7:	rs232_tx_r <= tx_data[6];	//发送bit6
						4'd8: rs232_tx_r <= tx_data[7];	//发送bit7
						4'd9: rs232_tx_r <= 1'b1;	//发送结束位
					 	default: rs232_tx_r <= 1'b1;
						endcase
				end
			else if(num==4'd11) num <= 4'd0;	//复位
		end
end

assign rs232_tx = rs232_tx_r;

endmodule


5 总结

代码中有详细的解释,有问题随时讨论。

知识是相互贯通的,夯实基础,才能筑高楼。欢迎大家批评指正!

参考文献

[1]特权FPGA PS2键盘解码实验

[2]PS2键盘扫描码:通码与断码 - JustXIII - 博客园

相关文章:

  • 小白学习java第12天(下):网络编程
  • 第1个小脚本:英语单语按字母个数进行升序排序
  • Spark Core(2)
  • Java学习打卡-Day25-注解和反射、Class类
  • 除了 `task_type=“SEQ_CLS“`(序列分类),还有CAUSAL_LM,QUESTION_ANS
  • 分布式系统-脑裂,redis的解决方案
  • 2025年蓝桥杯B组题解
  • 深度剖析:架构评估的常用方法与应用
  • 当Browser Use遇见A2A:浏览器自动化与智能体协作的“冰与火之歌“
  • WindowsPE文件格式入门06.手写最小PE
  • 向上取整,向下取整和四舍五入
  • 安卓AssetManager【一】- 资源的查找过程
  • 【Java学习】Spring AI集成指南
  • 深入理解 v-if 指令及其使用方法
  • C++在Linux上生成动态库并调用接口测试
  • 七、Qt框架编写的多线程应用程序
  • JDK的卸载与安装
  • 【JavaScript——页面渲染】课程列表(蓝桥杯真题-2457)【合集】
  • 《从零搭建Vue3项目实战》(AI辅助搭建Vue3+ElemntPlus后台管理项目)零基础入门系列第十二篇(完结篇):数据统计功能实现
  • Java高性能并发利器-VarHandle
  • wordpress做购物网站/学新媒体运营最好的培训学校
  • 专业微网站营销/百度一下电脑版首页
  • 网站 备案/怎么做推广比较成功
  • 开公司怎么找客户/seo点击排名工具
  • 网贷代理平台/西安百度seo
  • 成都工业学院文献检索在哪个网站做/俄罗斯搜索引擎yandex推广