连云港市网站建设_网站建设公司_Linux_seo优化
2026/1/14 5:48:34 网站建设 项目流程

EGO1开发板实战:用Vivado搭建PS2键盘输入解析系统

你有没有试过在FPGA上“听懂”一个键盘的低语?

在EGO1开发板的大作业中,我们常被要求完成一个完整的数字系统设计。而当你面对“如何让FPGA读取PS2键盘”这个问题时,其实是在挑战一连串嵌入式逻辑的核心技能——异步信号同步、状态机建模、帧结构解析、跨时钟域处理。这不仅是作业,更是一次从“写代码”到“造系统”的跃迁。

本文将带你一步步实现一个稳定可靠的PS2键盘输入解析模块,全程基于Xilinx Vivado工具链,在Artix-7 FPGA上落地。不讲空话,只讲你能跑通、能调试、能扩展的实战内容。


为什么是PS2?它真的还没过时吗?

USB HID协议复杂、需要固件枚举;I²C和SPI又得主控发起通信——相比之下,PS2像一位老派绅士:自己掌控节奏,主动发数据,格式清晰,两根线搞定。

更重要的是:PS2协议足够简单,却包含了串行通信的所有关键要素

  • 起始位 + 数据位 + 校验位 + 停止位(标准UART式帧结构)
  • 设备主导时钟(类似SPI slave mode)
  • 边沿采样 + 异步输入
  • 有错误检测机制(奇偶校验)

这些特性让它成为FPGA初学者练手外设通信的绝佳选择。尤其在EGO1这类教学型开发板上,没有复杂的操作系统干扰,你可以直接看到“按键按下”是如何一步步变成FPGA内部的一个字节。


PS2协议的本质:一场由键盘主导的对话

PS2使用两条信号线:
-CLK:时钟线,由键盘输出,频率约10–16.7kHz(典型值11–12kHz)
-DATA:双向数据线,平时高电平,传输时由键盘拉低或释放

虽然理论上支持双向通信(例如发送命令给键盘),但在大多数FPGA应用中,我们只做被动接收——即监听键盘发来的扫描码。

一帧数据长什么样?

每次按键动作会触发一次10位的数据传输:

[起始位: 0] [D0] [D1] ... [D7] [奇偶校验位] [停止位: 1]
  • LSB先行:最低位D0最先发送
  • 奇校验:整个9位(起始+8数据)中“1”的个数为奇数
  • 下降沿有效:每个CLK下降沿对应一位数据更新

⚠️ 注意:这里的“下降沿有效”是指键盘在CLK下降沿改变DATA,所以我们必须在上升沿采样,以确保建立/保持时间!

这意味着我们的FPGA不能依赖PS2_CLK作为驱动时钟,因为它既不稳定也不属于我们的时钟域。正确做法是:用FPGA的高速主时钟(如100MHz)去“观察”PS2_CLK的变化,并从中提取出精确的采样时机。


FPGA怎么“听”懂这个慢速信号?三大关键技术突破

要在100MHz系统时钟下准确捕获一个~12kHz的外部时钟事件,必须解决三个核心问题:

1. 跨时钟域同步(CDC)——防止亚稳态的第一道防线

由于PS2_CLK和PS2_DATA都是异步输入,直接进入逻辑可能导致触发器处于亚稳态(metastability)。解决方法很简单但至关重要:

// 双触发器同步 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin ps2_clk_sync1 <= 1'b1; ps2_clk_sync2 <= 1'b1; end else begin ps2_clk_sync1 <= ps2_clk; ps2_clk_sync2 <= ps2_clk_sync1; end end

这样做的目的是让信号变化“落在”系统时钟的采样窗口内,极大降低亚稳态概率。所有外部输入都应经过此处理。

2. 下降沿检测——找到数据采样的黄金时刻

既然不能用PS2_CLK做时钟,那就把它当成一个事件信号来检测边沿:

wire clk_falling = (ps2_clk_sync2 == 1'b1) && (ps2_clk_sync1 == 0);

这个布尔表达式只有在前一拍为高、当前为低时才成立,正好捕捉到下降沿。后续的状态转移和数据移位都将以此信号为使能条件。

3. 三段式状态机控制——让逻辑清晰可控

我们将接收过程划分为五个阶段:

状态动作
IDLE等待起始位(CLK下降且DATA=0)
START确认起始位有效,准备进入数据采集
DATA每次CLK下降沿,采集一位,共8次
PARITY读取第9位(奇偶校验)
STOP检查第10位是否为高(停止位)

这种分步设计的好处是:每一步职责明确,易于仿真验证,也方便后期加入错误恢复机制。


核心模块实现:精简高效的Verilog代码

下面是经过实战打磨的ps2_receiver模块,已在EGO1开发板实测可用:

module ps2_receiver ( input clk, // 100MHz 系统时钟 input rst_n, // 低电平复位 input ps2_clk, // 外部 CLK input ps2_data, // 外部 DATA output reg data_valid, // 数据有效标志 output reg [7:0] rx_data // 接收到的扫描码 ); // 同步寄存器 reg ps2_clk_sync1, ps2_clk_sync2; reg ps2_data_sync1, ps2_data_sync2; wire clk_falling; // 状态机定义 typedef enum logic [2:0] { IDLE, START, DATA, PARITY, STOP } state_t; state_t state, next_state; reg [3:0] bit_count; reg [7:0] shift_reg; reg parity_error, stop_error; // === 同步链 === always @(posedge clk or negedge rst_n) begin if (!rst_n) begin ps2_clk_sync1 <= 1'b1; ps2_clk_sync2 <= 1'b1; ps2_data_sync1 <= 1'b1; ps2_data_sync2 <= 1'b1; end else begin ps2_clk_sync1 <= ps2_clk; ps2_clk_sync2 <= ps2_clk_sync1; ps2_data_sync1 <= ps2_data; ps2_data_sync2 <= ps2_data_sync1; end end // === 下降沿检测 === assign clk_falling = ps2_clk_sync2 & ~ps2_clk_sync1; // === 组合逻辑:状态转移判断 === always @(*) begin next_state = state; case (state) IDLE: if (clk_falling && !ps2_data_sync2) next_state = START; START: if (clk_falling) next_state = DATA; DATA: if (clk_falling && bit_count == 7) next_state = PARITY; PARITY: if (clk_falling) next_state = STOP; STOP: if (clk_falling) next_state = IDLE; default: next_state = IDLE; endcase end // === 时序逻辑:状态执行与数据操作 === always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; bit_count <= 0; shift_reg <= 0; data_valid <= 0; rx_data <= 0; parity_error <= 0; stop_error <= 0; end else begin state <= next_state; data_valid <= 0; // 默认无效 case (next_state) START: begin bit_count <= 0; shift_reg <= 0; end DATA: begin if (clk_falling) begin shift_reg <= {ps2_data_sync2, shift_reg[7:1]}; bit_count <= bit_count + 1; end end PARITY: begin // 计算预期奇校验结果:总共有奇数个1 parity_error <= (^shift_reg ^ ps2_data_sync2) != 1'b1; end STOP: begin stop_error <= (ps2_data_sync2 != 1'b1); if (!parity_error && !stop_error) begin rx_data <= shift_reg; data_valid <= 1; // 仅当无误时置有效 end end endcase end end endmodule

关键点说明:

  • 双同步链保安全:所有输入先打两拍再使用。
  • 移位寄存器LSB先行{new_bit, shift_reg[7:1]}实现低位优先拼接。
  • 奇偶校验公式^shift_reg是对8位数据求异或(即奇偶性),再与接收到的校验位异或,结果应为1(奇校验)。
  • 错误丢弃机制:只要校验失败或停止位不对,就不置data_valid,避免脏数据污染下游。

如何集成进你的大作业系统?

别忘了,这只是第一步。真正的价值在于把它和其他模块连起来,形成完整的人机交互闭环。

典型系统架构示意图:

[PS2 Keyboard] ↓ [FPGA] ├─ [ps2_receiver] → 扫描码接收 ├─ [scan_code_decoder] → 映射为ASCII或键名 ├─ [display_driver] → 驱动LED/数码管/串口 └─ [optional MicroBlaze] → 软核处理高级逻辑
示例:简单显示方案(适合大作业初期验证)
// 将扫描码直接送到8个LED assign led[7:0] = rx_data;

或者用七段数码管显示低4位:

wire [3:0] seg_data = rx_data[3:0]; seg7_decoder u_seg (.data(seg_data), .segments(seg), .anode(an));
进阶玩法:通过UART上传至上位机

搭配一个简单的UART TX模块,就可以把扫描码实时打印到串口助手:

uart_tx #(.BAUD_RATE(115200)) u_uart ( .clk(clk), .rst_n(rst_n), .tx_en(data_valid), .tx_data(rx_data), .tx(txd) );

你会发现按下“A”,串口就输出1C;按下“1”,输出16……这一刻,你真正“读懂”了键盘的语言。


调试经验谈:那些手册不会告诉你的坑

我在EGO1上调试时踩过的几个典型坑,现在告诉你怎么绕过去:

❌ 问题1:根本收不到任何数据

可能原因:PS2接口没供电 / 线序接反 / 键盘不兼容
✅ 解法:
- 检查键盘是否正常工作(插电脑试试)
- 确认EGO1板载PS2接口是否有5V供电(部分键盘需要)
- 使用带电源引脚的PS2转接线

❌ 问题2:偶尔收到乱码

可能原因:未做同步 / 采样时机错误
✅ 解法:
- 必须使用双触发器同步CLK和DATA
- 不要用ps2_clk作为任何时钟源!只能作为事件信号处理

❌ 问题3:连续按键丢失数据

可能原因:缺乏缓冲机制
✅ 解法:
- 加一级FIFO缓存多个扫描码
- 或提高下游处理速度(比如用中断而非轮询)

✅ 调试技巧推荐:

  • state信号接到LED:不同状态亮不同灯,一眼看出卡在哪
  • data_valid接到LED:每次成功接收闪一下,直观反馈
  • 使用Vivado在线逻辑分析仪(ILA)抓波形,查看实际CLK/DATA时序

约束文件别忘了!XDC怎么写?

在Vivado中必须添加正确的物理引脚和电气标准约束。假设你接的是EGO1板上的PS2接口:

# PS2 接口引脚约束 set_property PACKAGE_PIN J1 [get_ports {ps2_clk}] set_property IOSTANDARD LVCMOS33 [get_ports {ps2_clk}] set_property PACKAGE_PIN H1 [get_ports {ps2_data}] set_property IOSTANDARD LVCMOS33 [get_ports {ps2_data}] # 其他建议 set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets ps2_clk_IBUF] # 允许非专用时钟引脚(因为PS2_CLK不是时钟输入引脚)

💡 提示:如果你发现CLK无法被捕获,可能是工具默认禁止非时钟引脚进时钟网络,加上最后一行即可解除限制。


总结:这不是结束,而是开始

当你第一次看到LED随着按键闪烁出对应的二进制码时,你就已经跨越了一个重要的门槛:你不再只是在配置逻辑门,而是在构建一个能感知外部世界的数字系统

这个PS2接收模块的价值远不止于大作业得分。它教会你:

  • 如何驯服异步信号
  • 如何用状态机拆解复杂流程
  • 如何设计容错机制提升鲁棒性
  • 如何与真实世界设备对话

而且,这套思路完全可以迁移到其他协议:UART、I²C、红外遥控、甚至自定义传感器通信。

下一步你可以尝试:
- 支持Break Code识别按键释放
- 实现Shift/A/Ctrl等功能键组合判断
- 构建简易键盘映射表输出ASCII字符
- 连接OLED屏做一个迷你终端输入系统

如果你正在做EGO1的大作业,不妨把这个模块作为你的“输入前端”。它够扎实、够稳定、够教学,是你展示工程能力的绝佳起点。


动手吧,让键盘的声音,在你的FPGA里响起。

如果你在实现过程中遇到了具体问题(比如波形不对、始终收不到数据),欢迎留言交流,我可以帮你一起看波形、调代码、找bug。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询