跳到主要内容

实验 13:触摸按键检测

🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/I0 (Arora V 系列)


一、实验目的

  1. 理解电容式触摸检测的基本原理——人体触摸改变 PCB 焊盘的等效电容,从而改变 RC 充放电时间。
  2. 掌握 FPGA 的 inout 双向端口用法:assign t_pad = charge ? 1'b1 : 1'bz(输出高电平或高阻态)。
  3. 学习充放电时间测量法:先充电到高电平,再切换到高阻态让焊盘通过下拉电阻放电,计数放电时间。
  4. 掌握自动基线校准技术——上电时自动测量未触摸时的放电时间作为基线,运行时与之比较。

二、实验过程

2.1 硬件连接

信号方向FPGA 引脚Bank说明
CLK输入T9Bank 450MHz 系统时钟
T_PADinoutM10Bank 2触摸焊盘(PCB 铜皮)
LED1输出G9Bank 1触摸指示 LED
LED2~7输出E7/E6/A5/A4/B4/A3/C5Bank 0/1辅助指示(全亮/全灭)

2.2 电容触摸原理

EK301 板载一个裸露的 PCB 铜皮焊盘(Touch Pad),连接到 FPGA 引脚 M10,并通过约 100KΩ~1MΩ 的下拉电阻到 GND。焊盘本身对地有寄生电容(约 5-20pF)。

等效电路

FPGA(M10) ──┬──── 触摸焊盘 (PCB铜皮)

├── 人体手指 (等效电容 ~100pF, 触摸时并联)

└── R_pulldown (~100KΩ) ── GND

触摸检测原理

1. 充电阶段:FPGA 输出高电平 → 焊盘电容充电到 3.3V
2. 放电阶段:FPGA 切换到高阻态(input) → 电容通过下拉电阻放电
3. 测量放电时间:从高阻态开始到 FPGA 检测到低电平的时钟周期数

未触摸:寄生电容小 → 放电时间短(如 100 个周期)
触摸时:手指电容并联 → 总电容增大 → 放电时间长(如 200+ 个周期)

2.3 FPGA 双向端口(inout)

inout t_pad; // 双向端口声明

assign t_pad = charge ? 1'b1 : 1'bz; // 输出:充电时输出高电平,否则高阻
wire pad_in = t_pad; // 输入:始终读取焊盘电平
charget_pad 行为等效电路
1FPGA 输出高电平输出驱动器导通,VCC→焊盘,电容充电
0FPGA 引脚高阻态输出驱动器断开,仅输入 Buffer 工作

FPGA 内部结构:每个 IO 引脚有独立的输出驱动器(Output Buffer)和输入 Buffer。1'bz 关闭输出驱动器,此时引脚仅通过输入 Buffer 连接到内部逻辑。

2.4 充放电检测状态机

充电完成
┌──────────────┐
│ CHARGE │ charge=1, 输出高电平 → 对焊盘充电
│ (st=1) │
└──────┬───────┘


┌──────────────┐
│ DISCHARGE │ charge=0 (高阻态), 等待电容放电
│ (st=2) │ 每个时钟周期检查 pad_in 是否变低
│ │ cnt 记录放电所需周期数
└──────┬───────┘
│ pad_in==0 (放电到低电平)

┌──────────────┐
│ IDLE │ st=0
│ (st=0) │ 比较 cnt 与 baseline
└──────────────┘ 判断是否触摸

2.5 自动基线校准

上电后前 2 秒(cal_cnt < 100,000,000,即 50MHz × 2s)处于校准模式:

if (cal_cnt < 100_000_000) begin
cal_cnt <= cal_cnt + 1;
// ... 正常充放电测量 ...
baseline <= cnt; // 每次放电完成更新基线
end
  • 校准期间不判断触摸,仅多次测量并更新 baseline(取最后一次的放电时间)。
  • 2 秒后进入正常检测模式,baseline 不再更新。

2.6 触摸判断阈值

if (cnt > baseline + (baseline >> 1)) // cnt > 1.5 × baseline
led[0] <= 1; // 判定为触摸
else
led[0] <= 0; // 未触摸
  • baseline >> 1 = baseline / 2(右移 1 位实现除以 2)。
  • 阈值 = baseline × 1.5
  • 手指触摸使放电时间至少增加 50% 以上才判定为触摸。

💡 阈值的选择需权衡灵敏度抗干扰。1.5 倍是一个经验值,可根据实际焊盘大小和下拉电阻值调整。

2.7 模块设计

模块名touch_detect

端口

端口方向位宽说明
clkinput150MHz 系统时钟
t_padinout1双向触摸焊盘引脚
ledoutput[7:0]LED 指示

关键内部信号

信号位宽说明
charge1充电控制(1=输出高电平,0=高阻)
cnt[15:0]放电周期计数器
baseline[15:0]未触摸时的基线放电时间
st[1:0]状态机状态(0/1/2)
cal_cnt[25:0]上电校准计时器(2 秒)

三、代码逐段讲解

3.1 双向端口控制

inout t_pad;

reg charge;

assign t_pad = charge ? 1'b1 : 1'bz;
wire pad_in = t_pad;
  • charge 为寄存器(reg),由状态机控制。
  • assign t_pad = ...连续赋值——组合逻辑,charge 变化时立即更新。
  • pad_in 始终读取焊盘电平,用于放电检测。

⚠️ inout 端口在顶层模块中使用 assign 连续赋值。不能在 always 块中赋值 inout

3.2 充放电状态机

always @(posedge clk) begin
if (st == 0) begin
charge <= 1; // 开始充电
st <= 1;
cnt <= 0;
end
else if (st == 1) begin
charge <= 0; // 停止充电,切换到高阻态
st <= 2; // 进入放电测量
end
else if (st == 2) begin
if (pad_in)
cnt <= cnt + 1; // 仍为高电平→继续放电→计数
else begin
// 放电到低电平 → 完成一次测量
baseline <= cnt; // (校准期间更新基线)
st <= 0; // 回到 idle
end
end
end
  • st=0→1:设置 charge=1,焊盘开始充电(1 个时钟周期即可充满,因为 FPGA 输出驱动能力强)。
  • st=1→2:设置 charge=0,焊盘进入高阻态,开始通过下拉电阻放电。
  • st=2pad_in==1 说明电容仍未放完电,cnt 继续累加。pad_in==0 说明放电完成。

3.3 上电校准

if (cal_cnt < 100_000_000) begin
cal_cnt <= cal_cnt + 1;
// ... 状态机正常运转 ...
baseline <= cnt; // 每次完成放电,更新基线
end
  • 前 2 秒内,每秒约执行 50MHz / 充放电周期 ≈ 几千次 测量。
  • 每次放电完成就用当前 cnt 更新 baseline
  • 2 秒后 cal_cnt 停止递增,baseline 锁定为最后一次测量的放电时间。

⚠️ 校准期间请勿触摸焊盘!否则基线会被触摸后的长时间放电篡改,导致正常检测时永远不触发。

3.4 触摸判断与 LED 输出

// 主 LED 判断
if (cnt > baseline + (baseline >> 1))
led[0] <= 1; // 触摸 → LED1 亮
else
led[0] <= 0; // 未触摸 → LED1 灭

// 辅助 LED
always @(posedge clk)
if (baseline > 0 && led[0])
led[7:1] <= 7'b1111111; // 触摸时其余 7 灯全亮
else
led[7:1] <= 0; // 未触摸时全灭
  • led[0]:触摸指示,仅在触摸时亮。
  • led[7:1]:辅助视觉反馈——触摸时全部 7 个 LED 亮,增强视觉效果。
  • baseline > 0 防止校准未完成时误判。

四、实验现象

操作预期结果
上电(不触摸焊盘)全部 LED 灭,基线自动校准中(2 秒内)
校准完成后触摸焊盘LED1 亮,其余 7 灯全亮
手指离开焊盘全部 LED 灭
快速触摸/离开LED 响应迅速,无明显延迟
用绝缘物(塑料笔)触碰LED 不亮(绝缘物不改变电容)
用金属物(钥匙)触碰LED 可能亮(金属与人体电容耦合)

验证要点

  • ✅ 校准期间(上电 2 秒内)不要触摸焊盘。
  • ✅ 手指直接接触焊盘 → LED 稳定点亮。
  • ✅ 手指离开 → LED 在 1-2 次测量周期内熄灭(通常 < 1ms)。
  • ✅ 隔着薄纸/胶带触摸 → 仍可触发(电容耦合仍存在)。
  • ✅ 环境温度/湿度变化后可能需重新上电校准。

⚠️ 如果 LED 常亮或不触发,检查 CST 中 t_pad 引脚是否为 M10,且 PULL_MODE=UP(代码中充电用输出高电平,但 CST 中上拉可辅助稳定)。


五、掌握知识点

知识点说明在本实验中的体现
电容触摸人体增大电容,延长 RC 放电放电时间 cnt 的比较
inout 端口FPGA 双向引脚assign t_pad = charge ? 1'b1 : 1'bz
高阻态(Z)输出驱动器关闭,仅输入工作1'bz 实现"断开"输出
RC 充放电τ = R×C,放电时间与电容成正比下拉电阻 + 焊盘/手指电容
自动校准上电自测基线,自适应环境cal_cnt 2 秒 + baseline
阈值判断比较测量值与基线的比例cnt > baseline × 1.5
右移除法>> 1 实现除 2baseline >> 1


附录:源码

top.v

// =============================================================================
// EK301 实验11: 触摸按键 (当普通KEY, 触摸=0, 未触摸=1)
// 触摸焊盘消抖后控制LED: 触摸→LED1翻转, LED2~8跟随
// =============================================================================
module touch_detect (
input wire clk,
inout wire t_pad, // 触摸焊盘 (触摸=0)
output reg [7:0] led
);

// t_pad作为数字输入(带内部上拉, 触摸时人体接地→低电平)
assign t_pad = 1'bz; // 高阻输入模式
wire pad_in = t_pad;

// ---- 消抖 ----
wire pad_flag, pad_state;
key_debounce #(.MS(20)) u_pad (
.clk (clk),
.reset_n (1'b1),
.key_in (pad_in),
.key_flag (pad_flag),
.key_state(pad_state)
);

// ---- 触摸检测 (按下=0, 松手=1) ----
wire touch_press = pad_flag && !pad_state; // 仅触摸脉冲

// ---- LED控制 ----
reg led_on;
always @(posedge clk) begin
if (touch_press)
led_on <= ~led_on; // 触摸→翻转
end

always @(*) begin
if (led_on)
led = 8'b11111111; // 全亮
else
led = 8'b00000000; // 全灭
end

endmodule