实验 13:触摸按键检测
🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/I0 (Arora V 系列)
一、实验目的
- 理解电容式触摸检测的基本原理——人体触摸改变 PCB 焊盘的等效电容,从而改变 RC 充放电时间。
- 掌握 FPGA 的 inout 双向端口用法:
assign t_pad = charge ? 1'b1 : 1'bz(输出高电平或高阻态)。 - 学习充放电时间测量法:先充电到高电平,再切换到高阻态让焊盘通过下拉电阻放电,计数放电时间。
- 掌握自动基线校准技术——上电时自动测量未触摸时的放电时间作为基线,运行时与之比较。
二、实验过程
2.1 硬件连接
| 信号 | 方向 | FPGA 引脚 | Bank | 说明 |
|---|---|---|---|---|
| CLK | 输入 | T9 | Bank 4 | 50MHz 系统时钟 |
| T_PAD | inout | M10 | Bank 2 | 触摸焊盘(PCB 铜皮) |
| LED1 | 输出 | G9 | Bank 1 | 触摸指示 LED |
| LED2~7 | 输出 | E7/E6/A5/A4/B4/A3/C5 | Bank 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; // 输入:始终读取焊盘电平
charge | t_pad 行为 | 等效电路 |
|---|---|---|
| 1 | FPGA 输出高电平 | 输出驱动器导通,VCC→焊盘,电容充电 |
| 0 | FPGA 引脚高阻态 | 输出驱动器断开,仅输入 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
端口:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk | input | 1 | 50MHz 系统时钟 |
t_pad | inout | 1 | 双向触摸焊盘引脚 |
led | output | [7:0] | LED 指示 |
关键内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
charge | 1 | 充电控制(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=2:
pad_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 实现除 2 | baseline >> 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