TSIServo:面向Kinetis MCU的轻量级TSI触摸驱动库
1. TSIServo 库概述
TSIServo 是一个面向嵌入式平台的轻量级触摸板(Touchpad)驱动库,其名称中的 “TSI” 明确指向Touch Sensing Input模块,而 “Servo” 并非指代舵机控制,而是取其“伺服”本义——即对触摸输入进行持续、实时、闭环式的采样、滤波、坐标解算与状态服务。该库并非通用型电容触摸 IC 驱动(如 FT5x06、GT911),而是专为集成TSI 模块的 NXP Kinetis 系列 MCU(如 K22F、K64F、KL27Z 等)设计的底层固件支持库。
Kinetis MCU 的 TSI 模块是一种基于电荷转移(Charge Transfer)原理的硬件外设,通过周期性地向触摸电极(通常为 PCB 上的铜箔图案)施加激励脉冲,并测量其返回的电荷量变化,从而感知手指接近或接触引起的寄生电容增量。该机制无需外部传感器芯片,显著降低 BOM 成本与 PCB 布局复杂度,特别适用于对成本敏感、空间受限且需基础手势识别(如滑动、点击)的工业 HMI、家电控制面板及教育开发板等场景。
TSIServo 的核心价值在于:将 TSI 硬件模块的底层寄存器操作、校准逻辑、噪声抑制算法与坐标映射流程封装为可复用、可配置的 C 函数接口,使开发者无需深入研究参考手册第 42 章(TSI 模块)的时序图与状态机,即可快速实现稳定可靠的触摸响应。它不依赖 RTOS,可在裸机(Bare-Metal)环境下运行;亦可无缝集成至 FreeRTOS 任务中,通过队列(Queue)或信号量(Semaphore)向应用层投递触摸事件。
值得注意的是,项目摘要中 “Libreria para manejo de Touchpad”(触摸板管理库)的表述虽简洁,但需结合 Kinetis 硬件特性准确理解:此处的 “Touchpad” 并非笔记本电脑上高精度多点触控板,而是指由 2~8 个独立电极构成的简易二维触摸区域(X/Y 轴各由一组电极阵列实现),典型布局为 4 电极 X 轴 + 4 电极 Y 轴,通过插值算法实现亚电极分辨率的位置定位。
2. 硬件基础与工作原理
2.1 Kinetis TSI 模块架构
TSI 模块本质是一个高度集成的模拟前端(AFE)+ 数字控制器。其关键组件包括:
- TSI 控制器(TSI0/TSI1):负责时序生成、通道切换、电荷积分与结果读取。
- 电极(Electrode):PCB 上的覆铜区域,作为感应单元。每个电极通过一个 GPIO 引脚连接至 TSI 模块的对应通道(TSI_CHn)。
- 基准电容(Cref):片内集成的精密电容,用于电荷转移比较。
- 积分器(Integrator):对电极上感应的电荷进行积分,输出与电容变化成正比的电压。
- ADC(12-bit SAR):将积分器输出电压量化为数字值(0–4095)。
工作流程遵循典型的电荷转移周期(Charge Transfer Cycle):
- 预充电阶段:TSI 控制器将电极通过内部开关连接至 VREFH(如 3.3V),使其充电至基准电压;
- 放电转移阶段:断开 VREFH,将电极通过另一开关连接至 Cref;电极上存储的电荷部分转移到 Cref,导致 Cref 两端电压上升;
- 积分测量阶段:启用积分器对 Cref 的电压上升过程进行积分,积分时间由 TSI 寄存器
TSI0_GENCS[NSCN](扫描次数)决定; - ADC 采样:积分结束后,12-bit ADC 对积分器输出进行采样,得到原始计数值(Raw Count)。
当手指靠近电极时,其对地寄生电容(Cp)增大,导致相同激励下电荷转移量减少,最终 ADC 读数降低。因此,触摸检测的核心判据是:原始计数值相对于基线值(Baseline)的衰减量(Delta = Baseline – RawCount)是否超过设定阈值(Threshold)。
2.2 电极布局与坐标解算
TSIServo 支持两种主流电极拓扑:
| 布局类型 | X 轴电极 | Y 轴电极 | 定位原理 | 典型分辨率 |
|---|---|---|---|---|
| 条形电极阵列 | 4 根平行条带 | 4 根平行条带 | 对 X/Y 各自电极组进行扫描,通过重心插值(Center of Gravity, CoG)计算坐标 | ~1/4 电极宽度 |
| 菱形/网格电极 | 2 对差分电极 | 2 对差分电极 | 利用差分信号比值(如 (E1-E2)/(E1+E2))提升线性度与抗噪性 | ~1/8 电极宽度 |
以 4 条 X 轴电极(E0–E3)为例,其原始计数值记为raw_x[4]。基线值base_x[4]在初始化时通过多次无触摸采样求平均获得。触摸发生后,计算各电极 Delta 值:
delta_x[i] = base_x[i] - raw_x[i]; // i = 0,1,2,3若delta_x[i]均小于阈值THRESHOLD_X,则判定为无触摸;否则,对所有delta_x[i] > 0的电极进行加权求和:
sum_delta = delta_x[0] + delta_x[1] + delta_x[2] + delta_x[3]; pos_x = (0 * delta_x[0] + 1 * delta_x[1] + 2 * delta_x[2] + 3 * delta_x[3]) / sum_delta;pos_x即为归一化到 [0, 3] 区间的 X 坐标,再经线性映射至屏幕像素范围(如 0–320)。Y 轴同理。此 CoG 算法能有效平滑单点触摸的抖动,并天然支持多指粗略定位(需配合峰值检测逻辑)。
3. TSIServo API 接口详解
TSIServo 提供一套精简而完备的 C 函数接口,全部声明于头文件tsiservo.h中。所有函数均以TSI_为前缀,符合 Kinetis SDK 命名惯例。
3.1 初始化与配置
typedef struct { uint8_t tsi_base; // TSI 模块基地址索引 (0 for TSI0, 1 for TSI1) uint8_t x_electrodes; // X 轴电极数量 (2, 4 or 8) uint8_t y_electrodes; // Y 轴电极数量 (2, 4 or 8) uint16_t baseline_cycles; // 基线采集周期数 (建议 32–128) uint16_t threshold_x; // X 轴触摸阈值 (典型值 50–200) uint16_t threshold_y; // Y 轴触摸阈值 (典型值 50–200) uint16_t debounce_ms; // 触摸消抖时间 (ms, 典型值 10–50) } tsi_config_t; /** * @brief 初始化 TSI 硬件并配置 TSIServo 库 * @param config 指向配置结构体的指针 * @return kStatus_Success 成功;kStatus_Fail 失败(如时钟未使能、GPIO 复用冲突) */ status_t TSI_Init(const tsi_config_t *config); /** * @brief 执行基线校准(在无触摸环境下调用) * @return kStatus_Success 成功;kStatus_TSI_BaselineFail 校准失败(环境干扰过大) */ status_t TSI_CalibrateBaseline(void);TSI_Init()是使用库的第一步,其内部执行以下关键操作:
- 使能 TSI 模块时钟(SIM_SCGC5[TSI0] 或 SIM_SCGC5[TSI1]);
- 将指定 GPIO 引脚配置为 TSI 功能(PORTx_PCRn[IRQC] = 0b1010, PORTx_PCRn[_MUX] = 0b101);
- 配置 TSI 控制寄存器:设置扫描模式(
TSI0_GENCS[MODE] = 0b01)、电极数量(TSI0_GENCS[NSCN])、基准电流(TSI0_GENCS[REFCHRG])、积分时间(TSI0_GENCS[EXTCHRG]); - 分配并初始化内部缓冲区(
raw_x[],raw_y[],base_x[],base_y[])。
TSI_CalibrateBaseline()在系统上电或环境温度剧变后必须调用。它执行baseline_cycles次完整扫描,对每次扫描结果取平均,并剔除异常值(如标准差 > 5% 均值者),确保基线稳定性。此过程耗时约baseline_cycles × 2ms,需在用户界面静默期完成。
3.2 核心数据采集与处理
/** * @brief 执行一次完整的 X/Y 轴扫描与坐标解算 * @param x_pos 输出:计算出的 X 坐标 (0–x_electrodes-1, 归一化) * @param y_pos 输出:计算出的 Y 坐标 (0–y_electrodes-1, 归一化) * @param touch_state 输出:触摸状态枚举 * @return kStatus_Success 成功;kStatus_TSI_NoTouch 当前无有效触摸 */ status_t TSI_ScanAndCalculate(uint16_t *x_pos, uint16_t *y_pos, tsi_touch_state_t *touch_state); typedef enum _tsi_touch_state { kTSI_TouchStateNoTouch, // 无触摸 kTSI_TouchStatePressed, // 按下(首次检测到) kTSI_TouchStateHeld, // 持续按住 kTSI_TouchStateReleased, // 释放(从按下/持续状态退出) } tsi_touch_state_t;TSI_ScanAndCalculate()是库的核心函数,其执行流程如下:
- 硬件扫描:依次激活 X 轴各电极,读取其
TSI0_DATA寄存器值,存入raw_x[];Y 轴同理; - Delta 计算:对每个电极计算
delta = base - raw; - 触摸判定:若
max(delta_x) < threshold_x && max(delta_y) < threshold_y,返回kStatus_TSI_NoTouch; - 坐标解算:对 X/Y 轴分别执行 CoG 算法,输出归一化坐标;
- 状态机更新:基于当前 Delta 峰值、历史状态及
debounce_ms计时器,更新touch_state(内部维护一个有限状态机)。
该函数执行时间约为 1–3ms(取决于电极数量),可安全地置于主循环中调用,或由 SysTick 定时器以 100Hz(10ms 周期)触发。
3.3 高级功能与调试接口
/** * @brief 获取指定电极的原始计数值(用于调试与波形分析) * @param axis 'X' or 'Y' * @param electrode_index 电极索引 (0-based) * @return 原始 ADC 计数值 (0–4095) */ uint16_t TSI_GetRawCount(char axis, uint8_t electrode_index); /** * @brief 强制更新基线(动态适应缓慢环境漂移) * @param electrode_index 指定电极索引,或 0xFF 表示全部 */ void TSI_UpdateBaseline(uint8_t electrode_index); /** * @brief 获取当前 TSI 模块状态寄存器快照(用于故障诊断) * @return TSI0_GENCS 寄存器值 */ uint32_t TSI_GetStatusRegister(void);TSI_GetRawCount()是调试利器。在示波器无法接入的现场,可通过 UART 将各电极原始值打印出来,直观观察:
- 无触摸时各电极
raw值是否稳定(波动应 < ±5); - 手指悬停时
raw值是否平缓下降; - 触摸瞬间
raw值是否出现尖峰(指示噪声耦合)。
TSI_UpdateBaseline()解决了长期运行中因温漂导致的基线缓慢偏移问题。例如,在烤箱控制面板中,环境温度从 25°C 升至 60°C,电极材料介电常数变化会使base下降约 10%,此时调用TSI_UpdateBaseline(0xFF)可重新校准,避免误触发。
4. 典型应用工程实践
4.1 裸机环境下的轮询式实现
在资源极度受限的系统中,可采用最简轮询模型:
#include "tsiservo.h" #include "fsl_gpio.h" int main(void) { tsi_config_t tsi_cfg = { .tsi_base = 0, .x_electrodes = 4, .y_electrodes = 4, .baseline_cycles = 64, .threshold_x = 120, .threshold_y = 120, .debounce_ms = 20 }; BOARD_InitPins(); BOARD_BootClockRUN(); TSI_Init(&tsi_cfg); TSI_CalibrateBaseline(); // 必须在触摸前调用 uint16_t x, y; tsi_touch_state_t state; while(1) { if (kStatus_Success == TSI_ScanAndCalculate(&x, &y, &state)) { switch(state) { case kTSI_TouchStatePressed: printf("TOUCH PRESSED: X=%d, Y=%d\n", x, y); break; case kTSI_TouchStateHeld: // 更新 UI 进度条或音量值 break; case kTSI_TouchStateReleased: printf("TOUCH RELEASED\n"); break; default: break; } } SDK_DelayAtLeastUs(10000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY); // 100Hz } }4.2 FreeRTOS 环境下的事件驱动集成
在复杂 HMI 系统中,推荐将触摸扫描置于独立任务,并通过队列向 UI 任务投递事件:
// 定义触摸事件结构体 typedef struct { uint16_t x; uint16_t y; tsi_touch_state_t state; TickType_t timestamp; // 用于手势识别的时间戳 } touch_event_t; QueueHandle_t g_touch_queue; void vTouchTask(void *pvParameters) { touch_event_t event; const TickType_t xScanPeriod = pdMS_TO_TICKS(10); // 100Hz for(;;) { if (kStatus_Success == TSI_ScanAndCalculate(&event.x, &event.y, &event.state)) { event.timestamp = xTaskGetTickCount(); if (xQueueSend(g_touch_queue, &event, 0) != pdPASS) { // 队列满,丢弃旧事件(常见于快速滑动) } } vTaskDelay(xScanPeriod); } } // UI 任务中接收并处理 void vUITask(void *pvParameters) { touch_event_t event; for(;;) { if (xQueueReceive(g_touch_queue, &event, portMAX_DELAY) == pdPASS) { switch(event.state) { case kTSI_TouchStatePressed: g_last_press_time = event.timestamp; g_start_x = event.x; g_start_y = event.y; break; case kTSI_TouchStateHeld: // 实现滑动跟踪 if (event.timestamp - g_last_press_time > pdMS_TO_TICKS(200)) { handle_long_press(); } break; case kTSI_TouchStateReleased: if (event.timestamp - g_last_press_time < pdMS_TO_TICKS(300)) { handle_tap(g_start_x, g_start_y, event.x, event.y); } else { handle_swipe(g_start_x, g_start_y, event.x, event.y); } break; } } } }4.3 PCB 设计关键规范
TSIServo 的性能上限由硬件设计决定。以下是经 Kinetis 开发套件验证的关键 Layout 规则:
电极设计:
- 形状:优先选用矩形或圆角矩形,避免锐角(易产生电场集中);
- 尺寸:单电极尺寸 ≥ 10mm × 10mm,间距 ≥ 2mm;
- 覆铜:电极必须为实心覆铜,禁用网格填充(会降低灵敏度);
- 保护:电极上方覆盖 1–3mm 厚玻璃或塑料面板,其介电常数(εr)影响灵敏度(εr 越高,灵敏度越低)。
走线规范:
- TSI 电极走线必须为50Ω 阻抗控制线,长度 ≤ 5cm;
- 走线全程包地(Ground Guard Ring),环路面积最小化;
- 禁止与高速信号线(如 USB、SPI CLK)平行走线,至少保持 10mm 间距。
电源与接地:
- TSI 模块必须使用独立的模拟电源(VDDA)和地(VSSA),并通过 1μF + 100nF 电容滤波;
- 数字地(VSS)与模拟地(VSSA)在单点(通常为 MCU 底部)连接。
违反上述任一规则,均可能导致TSI_ScanAndCalculate()返回kStatus_TSI_NoTouch即使手指已接触,或raw值剧烈跳变无法稳定。
5. 常见问题诊断与优化策略
5.1 灵敏度不足(需大力按压才响应)
根因分析:
- 电极尺寸过小或覆盖层过厚(>3mm);
threshold_x/y设置过高;- TSI 激励电流(
TSI0_GENCS[REFCHRG])过低。
解决方案:
- 在
TSI_Init()后立即调用:// 增大基准充电电流(值越大,灵敏度越高,但功耗增加) TSI0->GENCS |= TSI_GENCS_REFCHRG(0b11); // 最大电流档 // 同时适当降低阈值 g_threshold_x = 80; // 原为 120
5.2 误触发(无触摸时随机上报 Pressed)
根因分析:
- 电源纹波过大(尤其 VDDA);
- 电极走线受开关电源噪声耦合;
- 基线校准期间存在环境干扰(如荧光灯启辉)。
解决方案:
- 在
TSI_CalibrateBaseline()前插入 100ms 延迟,等待电源稳定; - 修改基线校准算法,增加中值滤波:
// 在 calibrate 函数内部,对每次扫描结果先排序,取中位数而非平均值 uint16_t sorted_raw[BASELINE_CYCLES]; memcpy(sorted_raw, raw_buffer, sizeof(raw_buffer)); qsort(sorted_raw, BASELINE_CYCLES, sizeof(uint16_t), cmp_uint16); base = sorted_raw[BASELINE_CYCLES/2];
5.3 坐标跳变(触摸位置抖动)
根因分析:
- CoG 算法对单电极噪声敏感;
- 未启用硬件去抖(
TSI0_GENCS[STPE]未设置)。
解决方案:
- 启用 TSI 内置的 4 次硬件平均(
TSI0_GENCS[STPE] = 0b0011); - 在软件层增加一阶 IIR 滤波:
// 全局变量 static uint16_t g_filtered_x = 0, g_filtered_y = 0; // 在 TSI_ScanAndCalculate 后 g_filtered_x = (g_filtered_x * 3 + x) >> 2; // α = 0.25 g_filtered_y = (g_filtered_y * 3 + y) >> 2;
6. 性能边界与选型建议
TSIServo 的性能极限由 Kinetis TSI 硬件本身决定:
- 最大电极数:8×8(需修改库内数组大小,但扫描时间将增至 ~15ms,刷新率 < 70Hz);
- 最低功耗:在
TSI0_GENCS[TSIIEN]=0时,TSI 模块待机电流 < 1μA; - 温度范围:-40°C 至 +105°C(Kinetis A/M 系列工业级 MCU)。
对于新项目选型,建议:
- 入门级 HMI(按钮/滑块):Kinetis KL27Z(超低功耗,TSI0) + TSIServo,BOM 成本 < $0.8;
- 中端面板(简易手势):Kinetis K22F(120MHz,TSI0+TSI1 可双模冗余) + TSIServo + FreeRTOS;
- 高可靠性工业设备:务必启用
TSI_UpdateBaseline()定期校准,并在TSI_ScanAndCalculate()返回kStatus_TSI_NoTouch时记录环境温度(通过内部温度传感器),建立温度-基线补偿表。
TSIServo 的生命力源于其对 Kinetis 硬件特性的深度绑定。它不追求跨平台兼容,而是在特定 MCU 生态内做到极致——将一个需要 200 行寄存器配置代码的外设,压缩为 3 个函数调用。这种“窄而深”的设计哲学,正是嵌入式底层开发的精髓所在:不是让代码跑在更多芯片上,而是让芯片的能力被更彻底地释放。
