第二十一章:触摸按键控制LED实验

本章对应原书第 434~445 页,属于「第五篇 实战篇」。

本章在讲什么?(先看这个)

前两章用的都是机械按键——要用力按、会抖动、有寿命限制。这一章换一种全新的输入方式:触摸按键。手机屏幕、楼道感应灯、家电触控面板用的都是这类技术。本章你会了解电容式触摸按键为什么能"隔空"感知手指,并且学到 Verilog 中最重要的基础电路之一:打两拍 + 取边沿的经典写法。代码本身不长,但这个边沿检测电路几乎是所有 FPGA 输入处理的标准开场。

核心概念拆解

21.1 触摸按键简介

触摸按键主要分四大类:

  • 电阻式:多块导电薄膜按按键位置印制,耐用性差、维护复杂,现在用得少;
  • 红外感应式:通过红外扫描识别按键位置,适合较恶劣的环境;
  • 表面声波式:利用声波扫描识别按压,寿命长,常用于 POS 机、无人售货机;
  • 电容式:通过检测电极电容变化来识别触控,感应区域可穿透 20mm 以上的绝缘材料(玻璃、塑料都行),灵敏稳定、防水、抗干扰、寿命长——是目前应用最广的方案。开发板用的就是它。

电容式触摸按键的原理:任何两个导电物体之间都存在感应电容,环境不变时这个电容值是固定的。当手指靠近触摸按键时,手指和按键之间会产生寄生电容,使按键的总电容值增加。触摸 IC 检测到电容变化超过阈值,就输出有效电平表示"有人摸了"。

图21.1.1 触摸按键寄生电容示意图

图21.1.2 触摸过程电容变化示意图

可以把它想象成:按键是一块"电子皮肤",平时安静无事,手指一靠近就改变了它的"体感",触摸芯片把这个变化翻译成数字信号告诉 FPGA。

21.2 硬件设计

开发板右下侧板载 1 个触摸按键,连接至 ZYNQ 的 PL 引脚(F16)。

图21.2.2 触摸按键电路原理图

触摸 IC 型号是 AR101(或完全兼容的 JL223B)。它有两个模式选择引脚,决定了输出行为:

  • OP1:拉低时 OUT 输出高电平有效;拉高时输出低电平有效;
  • OP2:拉低时工作在同步模式(类似非自锁的轻触按键——摸着才有效,松开就无效);拉高时工作在保持模式(类似自锁按键——摸一下电平翻转并保持,再摸一下再翻转)。

开发板上 OP1、OP2 均拉低,所以行为是:手指按在触摸按键上时 TOUT 输出高电平,松开后输出低电平。注意这和机械按键正好相反(机械按键按下是低电平)。

21.3 实验任务

用触摸按键控制 LED:上电后 LED 点亮;摸一下,LED 熄灭;再摸一下,LED 点亮——每触摸一次翻转一次。

21.4 程序设计

设计只有一个模块 touch_led:输入系统时钟、复位、触摸按键 touch_key,输出 led。核心逻辑是检测触摸按键信号的上升沿,每检测到一次上升沿就翻转 LED。

图21.4.1 触摸按键控制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)才同时为高,输出一个时钟周期宽的脉冲。

图21.4.4 获取上升沿

为什么要打两拍? 上面的单拍取沿有一个隐患: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

图21.4.7 触摸按键控制LED灯模块仿真波形图

波形验证了设计的正确性: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 呼吸灯,学习如何用数字电路"调"出模拟的亮度渐变。