第二十一章:触摸按键控制LED实验
本章对应原书第 434~445 页,属于「第五篇 实战篇」。
本章在讲什么?(先看这个)
前两章用的都是机械按键——要用力按、会抖动、有寿命限制。这一章换一种全新的输入方式:触摸按键。手机屏幕、楼道感应灯、家电触控面板用的都是这类技术。本章你会了解电容式触摸按键为什么能"隔空"感知手指,并且学到 Verilog 中最重要的基础电路之一:打两拍 + 取边沿的经典写法。代码本身不长,但这个边沿检测电路几乎是所有 FPGA 输入处理的标准开场。
核心概念拆解
21.1 触摸按键简介
触摸按键主要分四大类:
- 电阻式:多块导电薄膜按按键位置印制,耐用性差、维护复杂,现在用得少;
- 红外感应式:通过红外扫描识别按键位置,适合较恶劣的环境;
- 表面声波式:利用声波扫描识别按压,寿命长,常用于 POS 机、无人售货机;
- 电容式:通过检测电极电容变化来识别触控,感应区域可穿透 20mm 以上的绝缘材料(玻璃、塑料都行),灵敏稳定、防水、抗干扰、寿命长——是目前应用最广的方案。开发板用的就是它。
电容式触摸按键的原理:任何两个导电物体之间都存在感应电容,环境不变时这个电容值是固定的。当手指靠近触摸按键时,手指和按键之间会产生寄生电容,使按键的总电容值增加。触摸 IC 检测到电容变化超过阈值,就输出有效电平表示"有人摸了"。


可以把它想象成:按键是一块"电子皮肤",平时安静无事,手指一靠近就改变了它的"体感",触摸芯片把这个变化翻译成数字信号告诉 FPGA。
21.2 硬件设计
开发板右下侧板载 1 个触摸按键,连接至 ZYNQ 的 PL 引脚(F16)。

触摸 IC 型号是 AR101(或完全兼容的 JL223B)。它有两个模式选择引脚,决定了输出行为:
- OP1:拉低时 OUT 输出高电平有效;拉高时输出低电平有效;
- OP2:拉低时工作在同步模式(类似非自锁的轻触按键——摸着才有效,松开就无效);拉高时工作在保持模式(类似自锁按键——摸一下电平翻转并保持,再摸一下再翻转)。
开发板上 OP1、OP2 均拉低,所以行为是:手指按在触摸按键上时 TOUT 输出高电平,松开后输出低电平。注意这和机械按键正好相反(机械按键按下是低电平)。
21.3 实验任务
用触摸按键控制 LED:上电后 LED 点亮;摸一下,LED 熄灭;再摸一下,LED 点亮——每触摸一次翻转一次。
21.4 程序设计
设计只有一个模块 touch_led:输入系统时钟、复位、触摸按键 touch_key,输出 led。核心逻辑是检测触摸按键信号的上升沿,每检测到一次上升沿就翻转 LED。

边沿检测详解:打两拍取上升沿
这一节是本章的精华。怎么用 Verilog 判断"信号刚刚从 0 变成 1"?
第一步:打拍(延迟)。把原信号 raw_data 延迟一个时钟周期,得到 raw_data_d0:
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
raw_data_d0 <= 1'b0;
else
raw_data_d0 <= raw_data;
end
第二步:组合逻辑取沿。当前值和上一拍的值做与运算:
// 取上升沿:现在是1,上一拍是0
assign posedge = (~raw_data_d0) & raw_data;
// 取下降沿:现在是0,上一拍是1
assign negedge = raw_data_d0 & (~raw_data);
用波形理解上升沿检测:只有当 raw_data 刚从 0 跳到 1 的那一拍,~raw_data_d0(上一拍是 0,取反为 1)和 raw_data(现在是 1)才同时为高,输出一个时钟周期宽的脉冲。

为什么要打两拍? 上面的单拍取沿有一个隐患:touch_key 是外部输入的异步信号,它的跳变时刻和系统时钟没有关系,直接采样可能违反寄存器的建立/保持时间,产生亚稳态(输出在 0 和 1 之间摇摆不定)。解决办法是把信号打两拍:第一拍允许它"稳定下来",第二拍之后再用稳定的信号取沿。
所以实际电路是:touch_key → touch_key_d0(第一拍)→ touch_key_d1(第二拍),然后用 d0 和 d1 做取沿:
assign touch_en = (~touch_key_d1) & touch_key_d0;
完整代码
module touch_led(
input sys_clk, //时钟信号50MHz
input sys_rst_n, //复位信号
input touch_key, //触摸按键
output reg led //LED灯
);
reg touch_key_d0; //触摸按键延迟一个时钟周期
reg touch_key_d1; //触摸按键延迟两个时钟周期
wire touch_en; //触摸有效脉冲信号
//捕获触摸按键的上升沿,得到一个时钟周期的脉冲
assign touch_en = (~touch_key_d1) & touch_key_d0;
//对触摸按键打两拍(消除亚稳态)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
touch_key_d0 <= 1'b0;
touch_key_d1 <= 1'b0;
end
else begin
touch_key_d0 <= touch_key;
touch_key_d1 <= touch_key_d0;
end
end
//每次触摸翻转LED状态
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led <= 1'b1; //默认点亮LED
else if(touch_en) //检测到触摸
led <= ~led;
end
endmodule
注意本章不需要消抖——触摸 IC 内部已经做了信号处理,输出是干净的电平跳变,不像机械按键那样抖动。对比上一章:机械按键需要"打两拍 + 消抖 + 取下降沿"三步,触摸按键只需"打两拍 + 取上升沿"两步。
仿真验证
Testbench 中 touch_key 未触摸时为低电平、触摸时为高电平,用多段脉冲模拟多次触摸:
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
touch_key <= 1'b0;
#200
sys_rst_n <= 1'b1;
#30 touch_key <= 1'b1; //触摸
#40 touch_key <= 1'b0; //松开
#40 touch_key <= 1'b1; //再触摸
#40 touch_key <= 1'b0;
//……依次模拟多次触摸
end

波形验证了设计的正确性:touch_key_d0、touch_key_d1 依次延迟一拍,touch_en 只在上升沿处产生单周期脉冲,每来一个脉冲 led 翻转一次。
21.5 下载验证
引脚约束:
| 信号名 | 方向 | 管脚 | 说明 |
|---|---|---|---|
| sys_clk | input | U18 | 系统时钟 50MHz |
| sys_rst_n | input | N16 | PL复位按键,低有效 |
| touch_key | input | F16 | 触摸按键信号 |
| led | output | H15 | PL LED0 |
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN F16 IOSTANDARD LVCMOS33} [get_ports touch_key]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports led]
下载 bit 文件后:PL_LED0 上电点亮;手指触碰一次触摸按键,LED 熄灭;再触碰一次,LED 点亮。
初学者容易踩的坑
- 电平极性记混:触摸按键是"触摸时高电平",和机械按键"按下时低电平"正好相反。直接套用上一章的下降沿检测会完全失效——本章要用上升沿。
- 只打一拍就取沿:单拍取沿虽然逻辑正确,但面对异步输入存在亚稳态风险。必须打两拍,第一拍是"牺牲"的同步拍,第二拍才参与逻辑。
- 取沿表达式写错对象:用打两拍后的信号取沿时,比较对象是 touch_key_d0(第一拍)和 touch_key_d1(第二拍),不是原始 touch_key。写成
(~touch_key_d1) & touch_key混用了异步信号,等于没做同步。 - 复位值与现象对不上:led 复位值是 1'b1(上电点亮)。若实验现象是"上电灭、触摸才亮",先检查是不是把复位值写成了 1'b0。
- 以为是触摸 IC 直接控制 LED:触摸 IC 只是输出电平信号给 FPGA,翻转逻辑完全在 Verilog 代码里实现。分不清"芯片做了什么"和"代码做了什么"会导致排查问题时方向跑偏。
小结 & 下一步
本章认识了电容式触摸按键的工作原理(手指引入寄生电容,触摸 IC 检测电容变化),重点掌握了"打两拍 + 取上升沿"的经典边沿检测电路——这是处理一切异步输入信号的标配套路。触摸 IC 已在芯片内部完成去抖,所以代码比机械按键更简洁。下一步建议做拓展训练:用触摸按键控制流水灯——上电流水灯运行,触摸一次暂停,再触摸一次继续。之后我们将进入本章预告的下一站:PWM 呼吸灯,学习如何用数字电路"调"出模拟的亮度渐变。