☰
玻璃面板电容触摸按键:二极管充放电与ADC采样判定实践
2026/11/3 14:32:00 网站建设 项目流程

简介:这份文档围绕电容感应式触摸按键的原理与单片机编程实现展开,面向电子爱好者、家电维修人员及嵌入式入门学习者,帮助理解玻璃面板触摸按键的电容变化检测、PWM充放电与ADC阈值判别等关键问题。压缩包内仅含1个docx文件,约344KB,以单篇技术文档形式组织,便于在电脑端阅读和检索。文档结合STC12C4052AD的8位ADC应用,说明弹簧金属片与玻璃面板构成的感应结构、充放电电容的电压变化,并给出C源程序、参数调试及串口联调思路,同时讨论电磁干扰和元件参数离散性对稳定性的影响,可作为搭建触摸按键实验、分析故障与优化程序的参考。目前已有2155人学习,适合希望从原理到程序完整梳理电容感应按键实现方法的读者。

1. 玻璃面板下的 400mV:这块板子到底在解决什么问题

拆一台玻璃面板的电茶壶,按键区没有机械结构,只有几个弹簧顶着金属片贴在玻璃背面,板子上是一颗 8 脚单片机、两只二极管、两只电阻、一只电容。电容感应式触摸按键在家电里铺开很多年了,但真到自己做一个、或者要改一个的时候,能直接抄的完整资料并不多。这份资料的价值在于它把原理和代码连成了一条线:5mm 玻璃、Φ12mm 金属片、560K 电阻、二极管充放电、8 位 ADC 读电压,手指按下时 ADC 端从 2.7V 掉到 2.3V,靠这 400mV 判键。适合手上只有普通单片机、不想上专用触摸芯片、想把按键和其他控制塞进一颗片子的开发者。

2. 硬件链路拆解:两只二极管加一只电容怎么凑成一个按键

这套电路最容易被低估的地方是它太简单了,简单到很多人画完就以为完事,结果焊出来一摸没反应或者隔空乱跳。它本质是一个「用电容充放电时间换电压」的测量电路,所有参数都是互相咬合的,改一个就得回头算另外两个。

2.1 从感应电容到 ADC 电压的完整推导

先把物理过程走一遍。玻璃板一面贴着金属片,另一面是手指触摸区,金属片、玻璃、手指这三者构成一个电容结构。没人的时候,这个电容的介质主要是玻璃加空气,电荷集聚量极小,等效电容可能只有几个 pF;手指按上去之后,人体携带的电荷参与进来,等效电容明显变大。这个变化量通常被记作感应电容 Cp。

关键在于 Cp 并不是直接被测量的,而是被「折算」成电压。电路里 C0 是固定的充放电电容,PWM_IN 是一个方波信号:高电平期间,电源经 R2、D1 给 C0 充电;低电平期间,C0 经 R1、D2 对地放电。D1 和 D2 的作用是让充电和放电流过不同的路径,这样充电时间常数和放电时间常数可以分别由 R2、R1 决定,同时防止两个方向的电流互相串扰。

当 Cp 存在时,它相当于并联在 C0 两端,等效电容变成 C0 + Cp,同样的充放电时间下,C0 上积累的电压会变低。这就是为什么手指按下去 ADC 读数会下降,而不是上升。原文给的实测数据是空载 2.7V、触摸 2.3V,差值 400mV,8 位 ADC 在 5V 参考下分辨率约 20mV,400mV 折合 20 个码值,余量是够的。

这里有个常被忽略的点:2.7V 这个静态电压不是电源电压分压出来的,而是充放电达到动态平衡后的结果。也就是说 PWM 的频率、占空比、R1/R2、C0 这四个量只要有一个变了,静态工作点就会漂。我一般会先用示波器看 C0 上端的波形,确认它在半个周期内基本走平,再去调阈值。

2.2 元件取值与那处 560Ω 的标注纠错

原始资料里有个值得单独说的细节:图上标的是 R=560Ω,后来被读者指出应该是 560K,作者也确认了。这个错误不是笔误级别的,它直接决定电路能不能工作。

算一下就明白。假设 C0 取 220pF,如果 R 是 560Ω,时间常数 τ = 560 × 220e-12 ≈ 0.12µs,PWM 只要几百 Hz,C0 在每个半周期内早就充到顶了,ADC 读到的永远是接近电源轨的电压,手指那点感应电容带来的变化被完全淹没,灵敏度几乎为零。换成 560K,τ = 560e3 × 220e-12 ≈ 123µs,这个量级才和常见 PWM 频率对得上。

参数参考取值作用改动影响
R1 / R2560K限定充放电电流阻值越大越灵敏,但抗干扰变差、响应变慢
C0100pF ~ 1nF固定充放电电容越大越稳,但灵敏度下降
D1 / D2小信号开关二极管分离充放电路径反向漏电流大的管子会让静态电压漂
玻璃厚度5mm介质层越厚灵敏度越低
金属片直径Φ12mm极板面积面积越大灵敏度越高

选值时我一般按这个顺序凑:先定 C0,再算 τ,然后让 PWM 的半周期至少是 5τ,保证电压能走稳;最后再拿串口把实际 ADC 值打出来验证。举个例子,C0 取 220pF、R 取 560K,5τ 约 615µs,那 PWM 周期就得在 1.2ms 以上,也就是 800Hz 左右。频率再高,充放电不完整,读数是抖的。

提示:这张表里的 C0 不是原资料的实测值,原资料只给了电阻和电压。C0 需要按你手上的 PWM 频率反推,别照搬。

2.3 弹簧、金属片和玻璃厚度:机械结构决定灵敏度

很多人调代码调到怀疑人生,最后发现是结构问题。弹簧在这里不是可有可无的连接件,它承担两个功能:把金属片压紧在玻璃背面,保证气隙尽可能小;同时把金属片和 PCB 之间的连接做成弹性接触,避免刚性焊接在装配应力下开裂。

气隙是隐形杀手。金属片和玻璃之间只要有一层几十微米的空气,等效介质常数就掉下来一大截,灵敏度直接打折。我的血泪经验是:装配完之后用手按一下金属片背面,如果它能被按下去一点点,说明弹簧压力不够,这种板子做出来一致性极差。

玻璃厚度也是硬约束。5mm 是原文给的工作点,能做到 4mm 会更灵敏,但家电面板通常有强度和成本要求,减薄不一定可行。补偿办法是加大极板面积或者提高采样分辨率,Φ12mm 的片子在 5mm 玻璃下 400mV 的余量,本质上就是面积和厚度互相妥协的结果。

还有一条:金属片周围尽量不要走其他信号线,尤其是继电器驱动、蜂鸣器、开关电源的走线。这些线上的 dv/dt 会通过寄生电容耦合到感应极板上,表现出来就是「没人碰也在跳」。如果结构上避不开,就在极板背面加一层接地的屏蔽层,但注意屏蔽层不能把极板完全包住,否则感应电场也被挡住了。

3. 单片机侧的实现:8 位 ADC 采样与串口联调怎么搭

硬件搭好之后,单片机要做的事情其实只有两件:把 C0 上的电压采回来,把采回来的值送出去看。原资料用的是 STC12C2052AD 这一类带 8 位 ADC 的 8 位机,思路放到 STM32 上也一样,只是寄存器换成了 HAL 或者标准库,采样通道换成了对应的 ADC 通道。

3.1 端口模式与 ADC 寄存器配置

先说一个容易翻车的地方:ADC 输入引脚必须配置成高阻输入。如果配成推挽输出,片内驱动管会把引脚电压钳住,ADC 读到的就是你的输出电平,跟 C0 上的电压没关系。

原程序的初始化段是这样写的:

#include <STC12C2052AD.H> #include <intrins.h> #define uint unsigned int #define uchar unsigned char /* ADC 相关位定义,头文件缺失时需要手动补 */ #define ADC_POWER 0x80 // ADC 模块电源控制位,必须置 1 #define ADC_SPEEDLL 0x00 // 每次转换 1080 个时钟周期 #define ADC_SPEEDL 0x20 // 810 个时钟周期 #define ADC_SPEEDH 0x40 // 540 个时钟周期 #define ADC_SPEEDHH 0x60 // 270 个时钟周期 #define ADC_FLAG 0x10 // 转换完成标志 #define ADC_START 0x08 // 启动转换 #define ADC_CHS1 0x02 // 通道选择高位 #define ADC_CHS0 0x01 // 通道选择低位 void Delay(uint x) { uint y; while (x--) { y = 5000; while (y--); } } void InitADC() { ADC_DATA = 0; // 数据寄存器清零 ADC_CONTR = ADC_POWER | ADC_SPEEDLL; // 上电 + 最慢转换速度 Delay(2); // 上电后等待稳定 } void PortInit() { /* 原程序写的是 P1M0=0x03; P1M1=0x00; 注释标为高阻 按寄存器组合看这是推挽输出,AD 口建议改成下面这组 */ P1M0 = 0x00; P1M1 = 0x03; // P1.0/P1.1 设为高阻输入,供 ADC0/ADC1 使用 }

ADC_CONTR这个寄存器是复用的,写的时候要把电源位、速度位、通道位、启动位一次性拼好,读的时候又要看标志位。ADC_SPEEDLL选最慢档不是保守,是因为这套电路的源阻抗很高,560K 的电阻串在充放电路径上,采样保持电容需要更长时间才能跟上,转换太快反而读数偏低。12MHz 晶振下 1080 个周期约 90µs,对按键这种慢变量完全够用。

端口那两行我特意改了。原程序注释写的是高阻,但P1M0=0x03, P1M1=0x00这个组合在 STC12 系列里对应的是推挽输出。有可能是作者当时用的头文件定义不同,也有可能是注释写错了但程序碰巧能跑。我的做法是两种配置都烧一遍,用手按住按键看 ADC 值有没有明显变化,变化迟钝或者纹丝不动,就是端口模式配错了。

3.2 一次完整转换的时序与查询写法

转换函数本身很短,但里面藏了几个必须照抄的细节:

uint GetADCResult(uchar ch) { /* 拼装控制字:上电 + 速度 + 通道 + 启动 */ ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ch | ADC_START; /* 启动后必须空转几个周期,等内部采样保持电路跟上 */ _nop_(); _nop_(); _nop_(); _nop_(); while (!(ADC_CONTR & ADC_FLAG)); // 轮询等待转换完成 ADC_CONTR &= ~ADC_FLAG; // 软件清标志,必须做 return ADC_DATA; // 返回 8 位结果,0~255 }

ch是通道号,取 0 或 1 分别对应 ADC0 和 ADC1,最终会被拼进ADC_CONTR的低两位。那四个_nop_()不是玄学,STC 的 ADC 在启动位写入后需要几个时钟让内部电路响应,省掉这几位,第一次读出来的值经常是上一次的残留。

ADC_FLAG必须软件清零,这一点和很多其他单片机不同。如果忘了清,第二次进循环时标志位还是 1,while直接跳过,你读到的永远是第一次的数据,表现出来就是「按键完全没反应」。这个坑我在别的芯片上也踩过,只是那边是硬件自动清,换到这边就翻车了。

返回值的范围是 0 到 255,对应 0 到 VCC。按 5V 参考算,一个码值约 19.5mV,这就是前面说的 400mV 折合 20 个码值的来源。实际使用中不要用满量程去算,要看静态工作点落在哪,2.7V 大约对应 138,2.3V 大约对应 117,中间有 21 个码值的落差,阈值取在 127 附近比较合适。

3.3 串口联调:把 ADC 值送到电脑上看

原资料专门强调了这一环,而且专门反驳了「拿万用表量一下不就行了」的想法。这个反驳是对的:万用表的 ADC 性能和单片机片内 ADC 完全不是一回事,同一个电压两边读数对不上很正常;更重要的是感应按键受布局、元件离散性、周围电磁场影响,只能用即时采样值来标定,静态测量值只能当中心参考。

串口初始化的代码如下,注意波特率和晶振是绑死的:

void InitUart() { SCON = 0x50; // 串口方式 1,允许接收 TMOD = 0x20; // 定时器 1 工作在方式 2,8 位自动重装 TH1 = 0xf3; // 12MHz 晶振下对应 2400bps TL1 = 0xf3; TR1 = 1; // 启动定时器 1 } void SendData(uchar dat) { SBUF = dat; while (!TI); // 等待发送完成 TI = 0; // 软件清标志 } void ShowResult(uchar ch) { SendData(ch); // 先发通道号,方便区分 Delay(50); SendData(GetADCResult(ch)); // 再发 8 位采样值 Delay(50); }

TH1 = 0xf3这个值算一下就知道为什么只能用在 12MHz:方式 2 下波特率 = fosc / 12 / 32 / (256 - TH1),代入 12MHz 和 0xf3(即 243),得到 12e6 / 12 / 32 / 13 ≈ 2402,误差在 2400bps 允许范围内。换成 11.0592MHz,同样的初值算出来是 2212,误差接近 8%,串口助手上一片乱码。所以要么换晶振重算初值,要么老老实实跑 2400。

发送的时候先发通道号再发数据,是因为 8 位数据里可能有 0x00 或者 0xFF,直接看十六进制不好分辨边界。上位机那边用串口助手设成十六进制显示,每两个字节一组看,前面是通道、后面是采样值。按下和松开各看几十组,把最大值和最小值记下来,这就是你这一块板子的真实余量。

STM32 上做同样的事情更省事,直接用 UART 加 DMA 把 ADC 缓冲区的数据整块打出去,或者挂个 SWO 用调试器看变量。但底层逻辑没变:先确认采样值能稳定复现,再谈判定。

4. 从采样值到按键判定:阈值比较为什么一定会失败

拿到一串稳定的采样值之后,很多人的第一反应是写个if (adc < 127) key = 1;,然后烧进去发现能用,就收工了。放到实验室桌面上确实能用,装到整机上、换一批板子、天气潮一点,就开始出问题。

4.1 单次采样和固定阈值的问题在哪

固定阈值的问题来自三个方向。第一个是批次差异,C0 的容差、二极管的漏电流、玻璃厚度、弹簧压力,任何一项偏一点,静态工作点就平移几十毫伏,对应好几个码值。第二个是环境漂移,温度变化会改变介质常数和二极管特性,湿度会在玻璃表面形成微弱的导电膜,都能让基线缓慢移动。第三个是瞬时干扰,继电器吸合、电机启动、人手靠近但没接触,都会在采样值上打出一个尖峰。

这三个问题的解法不一样。批次差异靠上电自校准,漂移靠基线跟踪,尖峰靠多次采样取中值或者剔除异常值。把它们混在一起用一个阈值解决,注定按下去这头那头翘。

还有一个更隐蔽的问题:手指按下去和抬起来的过程不是阶跃的,中间有一段过渡区。如果只有一个阈值,手指停在临界位置时会疯狂抖动,按键状态一秒切换几十次。这就是需要滞回的原因。

4.2 基线跟踪与滞回判定的写法

下面这段是我在这个电路上验证过的判定逻辑,可以直接搬到项目里:

#define KEY_THRESHOLD 12 // 触发阈值,单位是 ADC 码值 #define KEY_HYSTERESIS 5 // 释放回差,防止临界抖动 #define BASELINE_STEP 1 // 基线每次最多挪 1 个码值 static uchar baseline = 0; // 无按键时的参考基线 static uchar key_state = 0; // 0 松开,1 按下 static uchar press_cnt = 0; // 连续超阈值计数 /* 上电自校准:连续采 32 次取平均作为初始基线 */ void Touch_Calibrate(void) { uint sum = 0; uchar i; for (i = 0; i < 32; i++) { sum += GetADCResult(0); Delay(5); } baseline = (uchar)(sum / 32); } /* 每 10ms 调一次 */ void Touch_Scan(void) { uchar sample = GetADCResult(0); uchar delta; /* 手指按下时电压下降,所以是基线减采样值 */ delta = (sample < baseline) ? (baseline - sample) : 0; if (!key_state) { /* 松开状态:连续 3 次超阈值才确认按下,滤掉尖峰 */ if (delta > KEY_THRESHOLD) { if (++press_cnt >= 3) { key_state = 1; press_cnt = 0; } } else { press_cnt = 0; /* 空闲时缓慢跟随基线,抵消温漂和湿度影响 */ if (sample > baseline) baseline += BASELINE_STEP; else if (sample < baseline) baseline -= BASELINE_STEP; } } else { /* 按下状态:回到阈值减回差以内才释放 */ if (delta < (KEY_THRESHOLD - KEY_HYSTERESIS)) { key_state = 0; press_cnt = 0; } } }

KEY_THRESHOLD取 12 是基于前面算的 21 个码值落差,留一半余量。如果你的板子实测落差只有 10 个码值,这个阈值就得往下调,同时把KEY_HYSTERESIS压到 3,否则按下和释放会粘在一起。

baseline的更新只发生在松开状态,而且每次只挪 1 个码值,这是故意的。如果按下时也更新基线,按住不放几秒钟,基线就会慢慢爬到当前采样值,手一松反而触发一次假的释放再按下。这种 bug 在整机上表现为「长按之后按键失灵一次」,很难复现,但只要基线更新条件写错就必然出现。

press_cnt >= 3是去抖。按键扫描周期如果是 10ms,三次就是 30ms,短于这个宽度的干扰会被吃掉。但也不能取太大,取到 10 次就是 100ms,用户会觉得按键迟钝。

4.3 采样周期和 PWM 的配合

判定逻辑跑在什么节奏上,取决于 PWM 和 ADC 的时序。如果 PWM 是 800Hz,周期 1.25ms,那你 10ms 采一次,采到的其实是 8 个 PWM 周期中某一个相位上的值。如果 PWM 和采样之间没有同步关系,每次采到的相位是随机的,读数就会带一个周期性的抖动。

解决办法是让采样点和 PWM 边沿对齐。简单做法是在 PWM 高电平结束前固定一段时间启动 ADC,这时候 C0 上的电压刚好走到稳态末端,重复性最好。复杂一点的做法是用定时器触发 ADC,硬件保证相位关系。原资料没有展开这一层,但从「用万用表量不行、必须即时采样」这个结论推下去,采样相位这件事迟早要面对。

我一般的做法是先把 PWM 关掉,用直流电平直接给 C0 充放电,看 ADC 读数稳不稳;稳了之后再开 PWM,如果抖动变大,就是相位没对上。

5. 避坑与排查:按键乱跳、串口乱码、死活不触发

这一节是我在这个电路上真实踩过的几个坑,按现象、原因、解决三段写,遇到类似症状可以直接对号入座。

5.1 现象:手没碰,按键自己触发

原因通常是两件事之一。一是感应极板附近有强干扰源,继电器、可控硅、开关电源的走线通过寄生电容把电荷灌到极板上;二是基线校准做在了有干扰的时刻,上电瞬间如果正好有个继电器动作,基线就被拉偏了,之后一直偏着。

解决分两步:先做硬件排查,把极板周围 5mm 内的其他走线清掉,清不掉就加地线隔离;再做软件处理,上电校准延时 500ms 再执行,校准期间连续采样并剔除最大值和最小值,避免单次异常拉偏基线。

5.2 现象:手指按上去,读数纹丝不动

先看端口模式。ADC 引脚如果配成推挽输出,片内驱动会把电压钳死,采样值永远是 0x00 或者 0xFF。这个坑我在 3.1 节已经提过,改P1M1那两行就能验证。

如果端口没问题,就看 C0 有没有焊。C0 虚焊的时候,ADC 端变成一个高阻节点,读数会随人手靠近而变化,但变化方向不稳定,有时候升有时候降。用示波器看 C0 上端,如果没有方波形状的充放电波形,就是这颗电容的问题。

再不行就查弹簧。弹簧没有压紧金属片,气隙大,感应电容变化传不过来,读数变化可能只有两三个码值,被噪声淹没了。

5.3 现象:串口助手收到的全是乱码

九成是波特率不匹配。TH1 = 0xf3只在 12MHz 晶振下对应 2400bps,如果你板子上焊的是 11.0592MHz,收到的就是乱码。改法有两种:换晶振,或者按公式重算初值。11.0592MHz 下要 2400bps,TH1应该是 0xF4 附近,但因为分频关系算不整,误差比 12MHz 大,所以这套组合本来就是为 12MHz 设计的。

还有一个小概率情况:SCON = 0x50配的是方式 1,如果你上位机设成了 8 数据位无校验,但单片机这边SMOD位被别的代码改过,波特率会差一倍,表现也是乱码。上电第一件事把SMOD清零。

5.4 现象:换一批板子,同一套程序有的灵有的不灵

元件离散性。C0 的容差常见是 ±10%,560K 电阻用 5% 精度的话,时间常数能差出 15%,静态工作点跟着平移。如果阈值是写死的绝对值,一批板子里总有几个落在阈值边缘。

解决办法是把阈值改成相对值。上电校准拿到基线,阈值用「基线减去 N 个码值」来表示,而不是写死 127。这样每块板子都按自己的基线来判,一致性会好很多。代价是上电时必须保证没人碰按键,所以校准前最好加个几百毫秒的延时,或者用一个明显的指示灯提示。

5.5 现象:按住不放,过几秒自己松开了

这是基线跟踪写错位置的典型症状。如果按下状态也更新基线,基线会以每个扫描周期 1 个码值的速度向当前采样值靠拢,按得越久靠得越近,delta越来越小,最后低于释放阈值,程序就认为手松开了。

修法很简单:把基线更新放进if (!key_state)分支里,按下期间冻结基线。如果确实需要在按下时也跟踪环境变化(比如按键时间特别长),那就把更新步长降到 0 或者每隔几十个周期才更新一次,别让它追上采样值。

6. 二次开发:从 STC 到 STM32,触摸按键怎么搬家

这套电路的价值不在于 STC 这颗片子,而在于「充放电 + ADC 采样 + 基线判定」这个结构。搬到我自己的项目里时,我一般把它拆成三层:硬件抽象层只管采一个原始值,触摸层只管把原始值变成按键状态,应用层完全不知道底下是电容感应还是机械按键。

换到 STM32 上有两条路。一条是继续用外部电路,STM32 只负责 ADC 采样和判定,这时候 3.2 节的GetADCResult换成 HAL 的HAL_ADC_GetValue,判定逻辑一行不用改。另一条是用 STM32 自带的触摸感应外设或者 RC 充放电方案,把外部那两只二极管和 C0 省掉,代价是要重新标定参数,而且不同型号的触摸通道特性差异不小,移植时不能照抄阈值。

方案外围元件移植成本适用场景
本资料的二极管充放电 + ADC2 二极管 + 2 电阻 + 1 电容低,判定逻辑可复用成本敏感、按键数量少
专用触摸芯片极少低,但受限于芯片键位数量产、不想调参
STM32 片内触摸外设几乎为零中,需要重新标定已有 STM32 平台、按键多

移植时最容易忽略的是采样时序。STC 那边 90µs 一次转换,我按 10ms 扫一次很宽松;STM32 的 ADC 快得多,如果还按 10ms 扫,两次采样之间环境已经变了,反而丢掉了原始资料强调的「即时采样」的意义。我的做法是把扫描周期压到 2ms,同时在判定层做 8 次滑动平均,兼顾响应速度和稳定性。

验证方法我固定走三步:先串口打原始值,看静态抖动范围是不是小于阈值的三分之一;再用手指快速点按二十次,数一下有没有漏检和连击;最后把板子放到整机里,开继电器和电机,重复第二步。三步都过,才算这块板子能出厂。

从那以后我每次做触摸按键,都强制先把原始采样值打到串口上看够一百组,再动手写判定。跳过这一步的项目,后面基本都要返工。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询