SAMD21电容触摸原理:基于DAC+AC的FreeTouch实现
1. Adafruit FreeTouch 库概述Adafruit FreeTouch 库是一个面向 Arduino 生态的轻量级、硬件抽象层友好的 QTouch 兼容电容式触摸感应库专为基于 ARM Cortex-M0 架构的 SAMD21 微控制器如 ATSAMD21G18广泛应用于 Adafruit Metro M0、Feather M0、Circuit Playground Express 等开发板深度优化。该库并非 Atmel/Microchip 官方 QTouch Library 的直接移植而是采用“兼容协议 硬件加速复用”的工程化设计思路在保留 QTouch 核心算法逻辑如自电容扫描、噪声抑制、去抖、滑条/轮识别的同时完全绕过官方闭源固件模块转而利用 SAMD21 片上外设——特别是ACAnalog Comparator和DACDigital-to-Analog Converter——构建一个可配置、可调试、全开源的电容感应前端。其核心价值在于将原本依赖专用 QTouch 芯片或 Atmel ASF 框架的复杂触摸方案下沉至通用 MCU 的标准模拟外设能力层面。开发者无需额外采购 QT60x 系列专用 IC仅需在 PCB 上布置简单的铜箔电极pad、滑条slider或轮wheel配合少量无源元件典型为 1–10 nF 隔直电容与 1–10 MΩ 限流电阻即可实现工业级鲁棒性的触摸检测。该设计显著降低了 BOM 成本与 PCB 布局复杂度同时赋予开发者对底层时序、参考电压、采样策略的完全控制权——这正是嵌入式底层工程师在量产项目中所必需的可追溯性与可调优性。库名中的 “FreeTouch” 并非指“免费触摸”而是强调其Free from proprietary QTouch firmware lock-in的技术立场所有关键算法包括基线跟踪、扩散滤波、阈值自适应均以 C 源码形式开放可被静态链接、条件编译、甚至针对特定干扰环境如 LCD 噪声耦合、电源纹波进行定制化修改。这种开源透明性使其成为教育、原型验证及中低复杂度消费类 HMI人机界面项目的理想选择。2. 硬件原理与 SAMD21 外设协同机制FreeTouch 库的物理层实现完全依托 SAMD21 的两个关键模拟外设DAC与AC并辅以PORT引脚配置和TCTimer Counter提供精确时序基准。其工作原理并非传统 RC 充放电测量而是一种电荷转移Charge Transfer 比较器过零检测的混合模式本质是将电极电容变化转化为可被数字电路精确捕获的电压跳变事件。2.1 电极建模与信号链每个触摸电极pad在电气上等效为一个对地电容 $C_{\text{elec}}$典型值 5–30 pF其值随手指接近而增大$\Delta C \approx 0.1–2$ pF。FreeTouch 将该电容置于一个由 DAC 输出电压 $V_{\text{REF}}$通常设为 VDD/2 1.65 V驱动的 RC 网络中。具体连接方式如下电极通过一个外部串联电阻 $R_{\text{CHARGE}}$推荐 1–4.7 MΩ连接至 DAC 输出电极另一端接地GND但通过一个外部隔直电容 $C_{\text{COUP}}$推荐 1–10 nF与 AC 的正输入端AINP相连AC 的负输入端AINN连接至一个稳定的参考电压 $V_{\text{BIAS}}$该电压由另一个 DAC 通道或内部 VREF 提供通常设为略低于 $V_{\text{REF}}$例如 $V_{\text{REF}} - 100$ mV。此结构构成一个高增益、高阻抗的差分比较器前端。当 DAC 向电极注入电荷时$C_{\text{elec}}$ 充电其电压 $V_{\text{elec}}$ 按指数规律上升一旦 $V_{\text{elec}}$ 超过 $V_{\text{BIAS}}$AC 输出即发生翻转。AC 输出翻转所需的时间 $t_{\text{TOGGLE}}$ 与 $C_{\text{elec}}$ 成正比这是整个测量的核心物理依据。2.2 SAMD21 外设协同流程整个测量周期由软件严格控制分为四个原子阶段由 TC 定时器触发状态切换阶段DAC 输出AC 配置PORT 状态目的1. 复位Reset$V_{\text{REF}}$ → GND (0 V)AINP 断开AINN 接 $V_{\text{BIAS}}$电极引脚设为 OUTPUT LOW强制电极放电至 0 V清除残余电荷2. 充电Charge$V_{\text{REF}}$ (1.65 V)AINP 连接电极AINN 接 $V_{\text{BIAS}}$电极引脚设为 INPUT高阻通过 $R_{\text{CHARGE}}$ 向 $C_{\text{elec}}$ 充电$V_{\text{elec}}$ 上升3. 检测Sense保持 $V_{\text{REF}}$AINP/AINN 正常使能AC 中断使能保持 INPUT监听 AC 输出翻转中断记录翻转时刻 $t_{\text{TOGGLE}}$4. 保持Hold$V_{\text{REF}}$AC 关闭电极引脚设为 INPUT维持电极电压为下一次复位做准备关键点在于AC 翻转时间 $t_{\text{TOGGLE}}$ 并非直接读取而是通过 TC 捕获寄存器Capture Register在 AC 中断触发瞬间锁存当前计数值。由于 TC 以固定频率如 1 MHz计数$t_{\text{TOGGLE}}$ 可直接换算为计数值TC-COUNT16.CC[0].reg。该值即为原始触摸数据Raw Count其大小与 $C_{\text{elec}}$ 正相关。手指靠近时 $C_{\text{elec}}$ 增大充电变慢$t_{\text{TOGGLE}}$ 变长Raw Count 值增大。2.3 抗噪设计要点SAMD21 的 AC 模块本身具备可编程迟滞Hysteresis和中断去抖Debounce功能FreeTouch 库充分利用了这两点迟滞配置通过AC-CTRLA.bit.ENABLE 0; AC-WINCTRL.bit.ENALTS 1;启用窗口比较模式并设置AC-AVGCTRL.bit.SAMPLENUM AC_AVGCTRL_SAMPLENUM_16_Val;进行 16 次采样平均有效抑制高频噪声。硬件去抖AC-INTFLAG.bit.COMP0中断仅在连续 N 次N2–4由AC-CTRLB.bit.DBGPR设置采样结果一致后才触发避免因瞬态干扰导致误触发。此外库强制要求所有电极使用独立的 DAC 通道与 AC 通道SAMD21G18 提供 2 个 DAC 和 2 个 AC杜绝通道间串扰。PCB 布局上电极走线必须全程包地Ground Guard Ring且与高速数字线如 USB D/D-、SPI CLK保持 3 mm 间距这是工程实践中保证信噪比SNR 20 dB的硬性约束。3. 核心 API 接口详解与参数解析FreeTouch 库以Adafruit_FreeTouch类为核心所有功能均通过其实例方法调用。其设计遵循“配置即构造”的原则绝大多数参数在对象初始化时即固化确保运行时零开销。以下为关键 API 的完整解析基于Adafruit_FreeTouch.hv1.2.0 源码。3.1 构造函数与初始化Adafruit_FreeTouch(AdcChannel adc, AcChannel ac, DacChannel dac, uint8_t pin, uint8_t threshold 12, uint8_t minDiff 2, uint8_t maxDiff 20);参数类型取值范围说明adcAdcChannel枚举ADC_CHANNEL_0,ADC_CHANNEL_1仅作占位实际未使用。早期版本曾尝试 ADC 采样现已被弃用传入任意值即可acAcChannel枚举AC_CHANNEL_0,AC_CHANNEL_1指定用于检测的比较器通道。必须与dac通道配对AC_CHANNEL_0对应DAC_CHANNEL_0AC_CHANNEL_1对应DAC_CHANNEL_1dacDacChannel枚举DAC_CHANNEL_0,DAC_CHANNEL_1指定用于驱动电极的 DAC 通道。SAMD21 的 DAC0/DAC1 输出分别映射到 PA02/PA05 引脚必须将电极物理连接至此引脚pinuint8_tArduino 引脚编号如A0,A1此引脚为电极焊盘的 GPIO 引脚非 DAC 输出引脚库会将其配置为 INPUT 模式并通过PORT-Group[gpioPort].PINCFG[pinNum].bit.PULLEN 0;禁用上拉/下拉确保高阻态thresholduint8_t1–255触摸判定阈值。当(current_raw - baseline) threshold时判定为触摸。默认 12 是针对典型 10 pF 电极的经验值强干扰环境需上调至 20–30minDiffuint8_t1–255基线更新最小差值。仅当abs(current_raw - baseline) minDiff时才允许基线缓慢更新。防止手指悬停时基线漂移过快默认 2maxDiffuint8_t1–255基线更新最大步长。每次基线更新的绝对值不超过此值避免突变。默认 20适用于快速环境变化场景工程提示pin参数易被误解为 DAC 引脚。正确接线是DAC 输出PA02/PA05→ 串联电阻 $R_{\text{CHARGE}}$ → 电极焊盘 → 隔直电容 $C_{\text{COUP}}$ → AC 的 AINP 输入PA04/PA06。电极焊盘本身需连接一个 Arduino GPIO如 A0该 GPIO 仅用于配置高阻态不参与信号路径。3.2 主要成员函数bool measure(void)执行单次完整测量周期复位→充电→检测→保持返回true表示测量成功AC 中断正常触发false表示超时未在预设最大时间内检测到翻转通常指示电极开路或 $R_{\text{CHARGE}}$ 过大。此函数是所有后续操作的前提必须在loop()中周期性调用。uint16_t value(void)返回本次测量的原始计数值Raw Count即 TC 捕获的t_{\text{TOGGLE}}。该值未经任何滤波反映最真实的电容状态适用于需要自定义算法的高级用户。uint16_t filteredValue(void)返回经库内建一阶 IIR 滤波后的值filtered filtered * 0.75 value() * 0.25。此滤波牺牲少量响应速度约 20 ms 时间常数换取显著的噪声抑制是大多数应用的推荐读取接口。bool touched(void)核心触摸判定函数。其内部逻辑为int16_t diff (int16_t)filteredValue() - (int16_t)_baseline; if (diff _threshold) { // 触摸成立启动基线冻结防止触摸中更新 _baselineFrozen true; return true; } else if (_baselineFrozen diff (_threshold 1)) { // 触摸释放解冻基线 _baselineFrozen false; } // 基线更新逻辑仅在未冻结时执行 if (!_baselineFrozen) { int16_t update diff 2; // 1/4 速率更新 if (update _maxDiff) update _maxDiff; if (update -_maxDiff) update -_maxDiff; if (abs(update) _minDiff) { _baseline update; } } return false;该设计实现了触摸去抖Touch Debounce与基线自适应Baseline Tracking的紧耦合是 QTouch 协议的精髓所在。void setThreshold(uint8_t t)动态修改触摸阈值。在多档灵敏度设计如设备设置菜单中非常实用。注意阈值修改后当前filteredValue()与_baseline的差值会立即重新评估。void resetBaseline(void)强制将_baseline重置为当前filteredValue()。适用于设备上电初始化或用户主动执行“校准”操作如长按某键 3 秒。4. 多电极与复合手势支持FreeTouch 库原生支持最多4 个独立电极受限于 SAMD21G18 的 DAC/AC 通道数通过创建多个Adafruit_FreeTouch实例实现。更强大的是其对滑条Slider和旋转轮Wheel的原生支持这并非简单地对多个 pad 值做插值而是实现了符合 QTouch 标准的重心计算Center of Gravity, CoG算法。4.1 滑条Slider实现一个滑条由 3 或 4 个线性排列的电极pad0,pad1,pad2,pad3构成。库提供FreeTouch_Slider类其构造函数为FreeTouch_Slider(Adafruit_FreeTouch *pads[], uint8_t numPads);其中pads[]是指向已初始化的Adafruit_FreeTouch实例的指针数组。滑条位置计算公式为 $$ \text{Position} \frac{\sum_{i0}^{n-1} (i \times \text{value}i)}{\sum{i0}^{n-1} \text{value}_i} $$ 结果归一化到 0.0–1.0 范围。例如4-pad 滑条中若pad0100,pad1200,pad250,pad310则 Position ≈ (0×100 1×200 2×50 3×10) / (1002005010) ≈ 330 / 360 ≈ 0.92表示滑块位于最右端。4.2 旋转轮Wheel实现一个轮由 4 或 8 个环形排列的电极构成。FreeTouch_Wheel类的构造与滑条类似。其角度计算采用改进的反正切法以克服线性插值在 0°/360° 交界处的不连续性float x value[0] - value[2]; // 0° vs 180° float y value[1] - value[3]; // 90° vs 270° float angle atan2(y, x) * 180.0f / PI; // 返回 -180° 到 180° if (angle 0) angle 360.0f; // 归一化到 0°–360°对于 8-pad 轮value[0]到value[7]分别对应 0°, 45°, ..., 315° 位置的电极。4.3 多实例资源管理SAMD21 的 DAC/AC 资源是全局的因此多个Adafruit_FreeTouch实例必须严格错开测量时间避免外设冲突。库未内置调度器需由用户保证所有实例的measure()调用间隔 ≥ 10 ms典型测量周期或采用 FreeRTOS 任务为每个实例分配独立任务通过vTaskDelay(10)实现轮询。// FreeRTOS 示例 void touchTask0(void *pvParameters) { Adafruit_FreeTouch *ft0 (Adafruit_FreeTouch*)pvParameters; for(;;) { ft0-measure(); vTaskDelay(10 / portTICK_PERIOD_MS); } } // 创建任务xTaskCreate(touchTask0, Touch0, 128, ft0_instance, 1, NULL);5. 实战配置与代码示例以下是一个完整的、可直接烧录到 Feather M0 上的双电极触摸示例包含 LED 反馈与串口调试输出。5.1 硬件连接Feather M0功能SAMD21 引脚连接方式DAC0 输出PA02 (A0)→ 2.2 MΩ → 电极 PAD0 → 4.7 nF → AC0 AINP (PA04)DAC1 输出PA05 (A1)→ 2.2 MΩ → 电极 PAD1 → 4.7 nF → AC1 AINP (PA06)PAD0 GPIOA2直连电极焊盘仅配置高阻PAD1 GPIOA3直连电极焊盘仅配置高阻LED13板载 LED指示触摸状态5.2 完整 Arduino Sketch#include Adafruit_FreeTouch.h #include Wire.h // 创建两个 FreeTouch 实例 Adafruit_FreeTouch ft0(ADC_CHANNEL_0, AC_CHANNEL_0, DAC_CHANNEL_0, A2, 15); Adafruit_FreeTouch ft1(ADC_CHANNEL_1, AC_CHANNEL_1, DAC_CHANNEL_1, A3, 15); void setup() { Serial.begin(115200); while (!Serial); // 等待串口监视器打开 pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); // 初始化 FreeTouch 实例 if (!ft0.begin()) { Serial.println(Failed to initialize ft0!); while (1) yield(); // 挂起 } if (!ft1.begin()) { Serial.println(Failed to initialize ft1!); while (1) yield(); } Serial.println(FreeTouch initialized. Touch pads to test.); } uint32_t lastPrint 0; void loop() { // 执行测量必须先调用 ft0.measure(); ft1.measure(); // 读取滤波后值与触摸状态 uint16_t val0 ft0.filteredValue(); uint16_t val1 ft1.filteredValue(); bool touch0 ft0.touched(); bool touch1 ft1.touched(); // LED 反馈PAD0 亮红PAD1 亮绿双触亮蓝需共阴 RGB LED if (touch0 touch1) { digitalWrite(LED_BUILTIN, HIGH); } else if (touch0) { digitalWrite(LED_BUILTIN, HIGH); } else if (touch1) { digitalWrite(LED_BUILTIN, HIGH); } else { digitalWrite(LED_BUILTIN, LOW); } // 串口调试每 200ms 输出一次 if (millis() - lastPrint 200) { lastPrint millis(); Serial.print(PAD0: ); Serial.print(val0); Serial.print( (); Serial.print(touch0 ? TOUCH : IDLE); Serial.print() | PAD1: ); Serial.print(val1); Serial.print( (); Serial.print(touch1 ? TOUCH : IDLE); Serial.println()); } // 关键加入最小延时确保 DAC/AC 有足够恢复时间 delay(5); }5.3 关键配置参数调优指南场景问题现象推荐调整灵敏度不足手指需紧贴才触发↑threshold如从 12→20↑R_{\text{CHARGE}}如从 2.2M→4.7M误触发频繁无触摸时随机触发↑threshold↓minDiff如从 2→1让基线更快适应环境检查 PCB 包地是否完整响应迟钝触摸后需 1–2 秒才响应↓threshold↑maxDiff如从 20→30加快基线追赶确认delay(5)未被误删滑条/轮跳变位置值剧烈抖动在FreeTouch_Slider::position()后追加移动平均滤波static float posHistory[5] {0};for(int i4; i0; i--) posHistory[i] posHistory[i-1];posHistory[0] slider.position();float smoothed 0; for(int i0; i5; i) smoothed posHistory[i]; smoothed / 5;6. 与 HAL/LL 库及 FreeRTOS 的深度集成FreeTouch 库的设计天然契合 STM32 HAL尽管其目标是 SAMD21但思想可迁移与 FreeRTOS。在大型项目中常需将其纳入更复杂的系统框架。6.1 HAL 风格封装概念性若在 STM32 平台上实现类似功能可借鉴其架构FreeTouch_HandleTypeDef结构体封装 DAC_Handle、AC_Handle、TC_HandleHAL_FreeTouch_Init()完成所有外设时钟使能、GPIO 配置、DAC/AC/TC 初始化HAL_FreeTouch_Start_IT()启动基于 TC 触发的 DAC 波形生成与 AC 中断HAL_FreeTouch_IRQHandler()在中断中调用HAL_FreeTouch_MeasureCallback()实现事件驱动。6.2 FreeRTOS 高级集成为实现低功耗与高实时性可将测量任务与触摸事件队列结合// 定义触摸事件结构 typedef struct { uint8_t pad_id; // 0 or 1 bool state; // true touch down, false touch up uint32_t timestamp; } touch_event_t; QueueHandle_t touch_queue; void touchTask(void *pvParameters) { touch_event_t evt; for(;;) { // 测量并生成事件 ft0.measure(); ft1.measure(); if (ft0.touched() ! lastState0) { evt.pad_id 0; evt.state ft0.touched(); evt.timestamp xTaskGetTickCount(); xQueueSend(touch_queue, evt, 0); lastState0 evt.state; } if (ft1.touched() ! lastState1) { evt.pad_id 1; evt.state ft1.touched(); evt.timestamp xTaskGetTickCount(); xQueueSend(touch_queue, evt, 0); lastState1 evt.state; } vTaskDelay(10 / portTICK_PERIOD_MS); } } // 在主任务中处理事件 void mainTask(void *pvParameters) { touch_event_t evt; for(;;) { if (xQueueReceive(touch_queue, evt, portMAX_DELAY) pdTRUE) { if (evt.state) { // 执行触摸按下逻辑播放音效、点亮 UI 元素... } else { // 执行触摸释放逻辑提交表单、执行命令... } } } }此模式将底层硬件驱动与业务逻辑彻底解耦符合现代嵌入式软件工程的最佳实践。7. 故障排查与生产测试要点在量产导入阶段FreeTouch 方案的稳定性高度依赖于前期的系统级验证。以下是基于真实产线经验的 checklist电极电容测试使用 LCR 表实测每个电极对地电容必须在 5–30 pF 范围内。超出则需调整电极面积或介质厚度。DAC 输出验证用示波器探头10×直接测量 PA02/PA05确认其能稳定输出 0 V 和 1.65 V且跳变沿无过冲/振铃。AC 中断确认在AC_Handler中置位一个 GPIO并用逻辑分析仪捕获确保中断频率与预期测量周期~100 Hz一致。基线漂移监控在无触摸状态下连续记录_baseline值 24 小时其波动应 ±5 counts。若超标检查电源纹波需 10 mVpp与 PCB 地平面完整性。ESD 鲁棒性测试依据 IEC 61000-4-2 Level 3±8 kV 接触放电在电极上施加 ESD 脉冲后系统必须能在 1 秒内自动恢复触摸功能。这要求在R_{\text{CHARGE}}$前增加一个 TVS 二极管如 SMAJ5.0A。一位资深硬件工程师曾总结“FreeTouch 不是一个‘拿来即用’的黑盒而是一套需要你亲手调教的精密仪器。它的强大恰恰源于你对每一个电阻、每一个寄存器位的掌控。” 这正是嵌入式底层开发的魅力所在——在硅片与铜箔之间构建出可靠、优雅、可触摸的真实世界交互。