【特权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) beginif(!rst_n) beginps2k_clk_r0 <= 1'b0;ps2k_clk_r1 <= 1'b0;ps2k_clk_r2 <= 1'b0;endelse begin								//锁存状态,进行滤波ps2k_clk_r0 <= ps2k_clk;ps2k_clk_r1 <= ps2k_clk_r0;ps2k_clk_r2 <= ps2k_clk_r1;end
endassign 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) beginif(!rst_n) beginnum <= 4'd0;temp_data <= 8'd0;endelse if(neg_ps2k_clk) begin	//检测到ps2k_clk的下降沿case (num)4'd0:	num <= num+1'b1;4'd1:	beginnum <= num+1'b1;temp_data[0] <= ps2k_data;	//bit0end4'd2:	beginnum <= num+1'b1;temp_data[1] <= ps2k_data;	//bit1end4'd3:	beginnum <= num+1'b1;temp_data[2] <= ps2k_data;	//bit2end4'd4:	beginnum <= num+1'b1;temp_data[3] <= ps2k_data;	//bit3end4'd5:	beginnum <= num+1'b1;temp_data[4] <= ps2k_data;	//bit4end4'd6:	beginnum <= num+1'b1;temp_data[5] <= ps2k_data;	//bit5end4'd7:	beginnum <= num+1'b1;temp_data[6] <= ps2k_data;	//bit6end4'd8:	beginnum <= num+1'b1;temp_data[7] <= ps2k_data;	//bit7end4'd9:	beginnum <= num+1'b1;	//奇偶校验位,不做处理end4'd10: beginnum <= 4'd0;	// num清零enddefault: ;endcaseend	
endreg 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) beginkey_f0 <= 1'b0;ps2_state_r <= 1'b0;endelse if(num==4'd10) begin	//刚传送完一个字节数据if(temp_data == 8'hf0) key_f0 <= 1'b1;else beginif(!key_f0) begin	//说明有键按下ps2_state_r <= 1'b1;ps2_byte_r <= temp_data;	//锁存当前键值endelse beginps2_state_r <= 1'b0;key_f0 <= 1'b0;endendend
endreg[7:0] ps2_asci;	//接收数据的相应ASCII码always @ (ps2_byte_r) begincase (ps2_byte_r)		//键值转换为ASCII码,这里做的比较简单,只处理字母8'h15: ps2_asci <= 8'h51;	//Q8'h1d: ps2_asci <= 8'h57;	//W8'h24: ps2_asci <= 8'h45;	//E8'h2d: ps2_asci <= 8'h52;	//R8'h2c: ps2_asci <= 8'h54;	//T8'h35: ps2_asci <= 8'h59;	//Y8'h3c: ps2_asci <= 8'h55;	//U8'h43: ps2_asci <= 8'h49;	//I8'h44: ps2_asci <= 8'h4f;	//O8'h4d: ps2_asci <= 8'h50;	//P				  	8'h1c: ps2_asci <= 8'h41;	//A8'h1b: ps2_asci <= 8'h53;	//S8'h23: ps2_asci <= 8'h44;	//D8'h2b: ps2_asci <= 8'h46;	//F8'h34: ps2_asci <= 8'h47;	//G8'h33: ps2_asci <= 8'h48;	//H8'h3b: ps2_asci <= 8'h4a;	//J8'h42: ps2_asci <= 8'h4b;	//K8'h4b: ps2_asci <= 8'h4c;	//L8'h1a: ps2_asci <= 8'h5a;	//Z8'h22: ps2_asci <= 8'h58;	//X8'h21: ps2_asci <= 8'h43;	//C8'h2a: ps2_asci <= 8'h56;	//V8'h32: ps2_asci <= 8'h42;	//B8'h31: ps2_asci <= 8'h4e;	//N8'h3a: ps2_asci <= 8'h4d;	//Mdefault: ;endcase
endassign 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,	//波特率为9600bpsbps19200 	= 2603,	//波特率为19200bpsbps38400 	= 1301,	//波特率为38400bpsbps57600 	= 867,	//波特率为57600bpsbps115200	= 433;	//波特率为115200bpsparameter 		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) beginif(!rst_n) beginrx_int0 <= 1'b0;rx_int1 <= 1'b0;rx_int2 <= 1'b0;endelse beginrx_int0 <= rx_int;rx_int1 <= rx_int0;rx_int2 <= rx_int1;end
endassign 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) beginif(!rst_n) beginbps_start_r <= 1'bz;tx_en <= 1'b0;tx_data <= 8'd0;endelse if(pos_rx_int) begin	//接收数据完毕,准备把接收到的数据发出去bps_start_r <= 1'b1;tx_data <= rx_data;	//把接收到的数据存入发送数据寄存器tx_en <= 1'b1;		//进入发送数据状态中endelse if(num==4'd11) begin	//数据发送完成,复位bps_start_r <= 1'b0;tx_en <= 1'b0;end
endassign bps_start = bps_start_r;//---------------------------------------------------------
reg rs232_tx_r;always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginnum <= 4'd0;rs232_tx_r <= 1'b1;endelse if(tx_en) beginif(clk_bps)	beginnum <= num+1'b1;case (num)4'd0:	rs232_tx_r <= 1'b0; 	//发送起始位4'd1:	rs232_tx_r <= tx_data[0];	//发送bit04'd2:	rs232_tx_r <= tx_data[1];	//发送bit14'd3: rs232_tx_r <= tx_data[2];	//发送bit24'd4: rs232_tx_r <= tx_data[3];	//发送bit34'd5: rs232_tx_r <= tx_data[4];	//发送bit44'd6: rs232_tx_r <= tx_data[5];	//发送bit54'd7:	rs232_tx_r <= tx_data[6];	//发送bit64'd8: rs232_tx_r <= tx_data[7];	//发送bit74'd9: rs232_tx_r <= 1'b1;	//发送结束位default: rs232_tx_r <= 1'b1;endcaseendelse if(num==4'd11) num <= 4'd0;	//复位end
endassign rs232_tx = rs232_tx_r;endmodule

5 总结

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

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

参考文献

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/76573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DAPP实战篇:使用web3.js实现前端输入钱包地址查询该地址的USDT余额—操作篇

专栏:区块链入门到放弃查看目录-CSDN博客文章浏览阅读396次。为了方便查看将本专栏的所有内容列出目录,按照顺序查看即可。后续也会在此规划一下后续内容,因此如果遇到不能点击的,代表还没有更新。声明:文中所出观点大多数源于笔者多年开发经验所总结,如果你想要知道区块…

高中生学习数据隐私保护的“技术-制度-文化”协同机制研究

一、引言 1.1 研究背景与意义 在数字化时代的浪潮下&#xff0c;教育领域正经历着深刻的变革&#xff0c;智能教育平台如雨后春笋般涌现&#xff0c;为高中教育带来了新的活力与机遇。这些平台借助先进的信息技术&#xff0c;能够实时收集、分析大量的高中生学习数据&#xf…

【Java多线程】告别线程混乱!深度解析Java多线程4大实现方式(附实战案例)

一、继承Thread类 实现步骤&#xff1a; 1.继承Thread类 2.重写run()方法 3.创建线程对象并调用start()方法 示例&#xff1a; class MyThread extends Thread {Overridepublic void run() {for (int i 0; i < 5; i) {System.out.println(Thread.currentThread().getNam…

全国产V7-690T核心板/算法验证板/FPGA开发板

UD SOM-404全国产化信号处理模块既可以作为核心板使用&#xff0c;也可以单独使用。FPGA对外有80组GTY通过两个FMC连接器全部引出&#xff0c;多个模块可以级联使用&#xff0c;扩展信号处理能力。FMC连接器也满足标准规范&#xff0c;可以插入标准的FMC或FMC子板。模块为100%国…

STM32_HAL库提高中断执行效率

目录 中断流程分析我的解决办法优缺点 大家都在说STM32 HAL 库中断效率低下。具体哪里不行&#xff1f;如何优化&#xff1f; 我手里的项目要用到多个定时器TIM6、TIM7、TIM9、TIM10、TIM11、TIM12、TIM13&#xff0c;在处理这些定时器中断的时候&#xff0c;也发现了这个问题。…

RabbitMQ惰性队列的工作原理、消息持久化机制、同步刷盘的概念、延迟插件的使用方法

惰性队列工作原理 惰性队列通过尽可能多地将消息存储到磁盘上来减少内存的使用。与传统队列相比&#xff0c;惰性队列不会主动将消息加载到内存中&#xff0c;而是尽量让消息停留在磁盘上&#xff0c;从而降低内存占用。尽管如此&#xff0c;它并不保证所有操作都是同步写入磁…

Spark Core(二)

Spark-Core编程&#xff08;二&#xff09; RDD转换算子 RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value 类型 Value类型 1&#xff09;map 将处理的数据逐条进行映射转换&#xff0c;这里的转换可以是类型的转换&#xff0c;也可以是…

C#打开文件及目录脚本

如果每天开始工作前都要做一些准备工作&#xff0c;比如打开文件或文件夹&#xff0c;我们可以使用代码一键完成。 using System.Diagnostics; using System.IO;namespace OpenFile {internal class Program{static void Main(string[] args){Console.WriteLine("Hello, …

Python生成exe

其中的 -w 参数是 PyInstaller 用于窗口模式&#xff08;Windowed mode&#xff09;&#xff0c;它会关闭命令行窗口的输出&#xff0c;这通常用于 图形界面程序&#xff08;GUI&#xff09;&#xff0c;比如使用 PyQt6, Tkinter, PySide6 等。 所以&#xff1a; 如果你在没有…

【大模型微调】如何解决llamaFactory微调效果与vllm部署效果不一致如何解决

以下个人没整理太全 一、生成式语言模型的对话模板介绍 使用Qwen/Qwen1.5-0.5B-Chat训练 对话模板不一样。回答的内容就会不一样。 我们可以看到例如qwen模型的tokenizer_config.json文件&#xff0c;就可以看到对话模板&#xff0c;一般同系列的模型&#xff0c;模板基本都…

Linux网络编程——详解网络层IP协议、网段划分、路由

目录 一、前言 二、IP协议的认识 1、什么是IP协议&#xff1f; 2、IP协议报头 三、网段划分 1、初步认识IP与路由 2、IP地址 I、DHCP动态主机配置协议 3、IP地址的划分 I、CIDR设计 II、子网数目的计算 III、子网掩码的确定 四、特殊的IP地址 五、IP地址的数量限…

ansible+docker+docker-compose快速部署4节点高可用minio集群

目录 github项目地址 示例服务器列表 安装前 修改变量文件group_vars/all.yml 修改ansible主机清单 修改setup.sh安装脚本 用法演示 安装后验证 github项目地址 https://github.com/sulibao/ansible_minio_cluster.git 示例服务器列表 安装前 修改变量文件group_var…

MySql主从相关概念

想象一下&#xff0c;你的业务飞速增长&#xff0c;用户请求如潮水般涌来&#xff0c;突然数据库主库宕机&#xff0c;数据丢失&#xff0c;服务瘫痪——这简直是开发者的噩梦&#xff01;MySQL主从复制就像一张安全网&#xff0c;通过主库写、从库读的协作模式&#xff0c;不仅…

机械臂只有位置信息是否可以进行手眼标定?

平常我在做手眼标定时&#xff0c;一般都是通过OpenCV的cv::calibrateHandEye函数进行求解&#xff0c;需要输入多组不同的机械臂位姿。今天遇到了一款舵机机器人&#xff0c;只能获取位置&#xff0c;得不到姿态信息&#xff0c;想着那就把姿态都设为0&#xff0c;结果求不出来…

华为数字芯片机考2025合集2已校正

单选 1. 题目内容 关于亚稳态的描述错误的是&#xff08; &#xff09;。 1. 解题步骤 1.1 理解亚稳态&#xff08;Metastability&#xff09;的核心特性 亚稳态是指触发器无法在指定时间内稳定输出有效逻辑电平&#xff08;0或1&#xff09;的状态&#xff0c;其关键特点…

T-Box车载系统介绍及其应用

定义 T-Box汽车系统&#xff0c;全称为Telematics - BOX&#xff0c;也常简称为车载T - BOX&#xff0c;是汽车智能系统及车联网系统中的核心组成部分&#xff0c;是安装在车辆上的一种高科技远程信息处理器。 工作原理 T-Box的核心功能主要通过MPU和MCU实现。MPU负责应用程序功…

[redis进阶一]redis的持久化(1)RDB篇章

目录 一 认识持久化 (1)先看总结图 (2)什么是持久化? (3)redis是怎么进行持久化的呢 (4)简单分析一下RDB持久化和AOF持久化的不同 二 RDB持久化 (1)RDB的触发机制 (2)RDB的bgsave执行流程 (3)RDB文件的处理 (4)RDB的优缺点 (5)RDB效果演示板书 三 温习Linux文件…

uniapp日常总结--uniapp页面跳转方式

uniapp日常总结--uniapp页面跳转方式_uniapp 跳转-CSDN博客

《汽车电器与电子技术》实验报告

SRS系统结构原理与故障检测诊断 车辆上为什么要配安全气囊&#xff1f;——解析汽车被动安全的关键防线 一、安全气囊的核心作用&#xff1a;应对高速碰撞的“救命缓冲垫” 车辆在高速碰撞时&#xff08;如正面碰撞、侧面碰撞&#xff09;&#xff0c;人体会因惯性以极高速度…

ffmpeg编解码器相关函数

文章目录 &#x1f3af; 你需要理解的核心结构体&#xff1a;&#x1f4e6; 常用函数及使用顺序&#xff08;以解码为例&#xff09;1️⃣ avcodec_find_decoder() / avcodec_find_encoder()2️⃣ avcodec_alloc_context3()3️⃣ avcodec_parameters_to_context()4️⃣ avcodec…