一、所选任务和项目介绍
1.1 项目主题
本项目选择的任务为"基于电容式触摸传感器的LED控制系统"。该系统以Microchip PIC32CM5164LS00048微控制器为核心,利用其片内集成的Peripheral Touch Controller(PTC)外设实现自电容式触摸检测,将检测到的触摸状态以LED电平反转的方式进行可视化反馈。
1.2 项目来源与背景
本项目为Funpack 5-2任务,要求使用EV41C56A评估板(即PIC32CM5164LS00048的评估板)实现触摸控制LED的功能设计。Funpack活动是Microchip为帮助工程师快速上手其新产品而设立的学习实践活动,通过提供完整的软硬件资料和实际可运行的项目代码,降低新芯片和新工具的学习门槛。
电容式触摸检测技术在当今的电子产品中已经无处不在,从智能手机的触控屏到家电产品的控制面板,再到工业设备的操作界面,触摸交互已经成为人机接口的主流方式。相比于传统的机械式按键,电容式触摸检测具有寿命长、密封性好、响应速度快、外观美观等多方面优势。本项目选取触摸控制LED这一简单但完整的应用场景作为切入点,通过一个完整的小项目全面掌握PIC32CM系列微控制器的PTC外设开发、QTouch库的使用方法以及TrustZone安全编程的基本概念。
1.3 项目目标
本项目需要达成以下具体目标:
第一,touch库的使用;
第二,LED的控制;
第三,在应用层实现带防抖功能的触摸检测算法——仅当连续两次测量周期均检测到触摸时才确认按下事件成立,从而有效过滤电气噪声和偶然干扰引起的误触发;
1.4 项目实现的功能
系统最终实现了以下功能:用户手指触摸评估板上的板载Touch传感器时,系统通过自电容测量检测到触摸事件,经过应用层防抖确认后,驱动PA15引脚上的LED指示灯电平反转,LED从亮变灭或从灭变亮;手指释放后系统检测到触摸结束,状态复位,为下一次触摸事件做好准备。整个过程周而复始,用户每次触碰传感器都会引起LED状态的一次翻转。
二、硬件介绍
开发板
本项目使用的核心控制器是Microchip PIC32CM5164LS00048,这是一款基于ARM Cortex-M23内核的32位微控制器,属于Microchip PIC32CM系列中集成触摸控制器的主力型号。该芯片支持TrustZone安全扩展,能够在硬件层面实现安全世界与非安全世界的隔离,这是本项目架构设计的重要基础。该芯片最高运行频率可达48MHz,内置512KB的Flash程序存储器和64KB的SRAM数据存储器,足以容纳QTouch库的完整代码和应用程序。在外设资源方面,芯片集成了丰富的定时器资源(包括RTC实时时钟、TC通用定时器和TCC高级定时器)、全速USB接口、多个串行通信接口(SERCOM支持UART/SPI/I2C三种模式)以及多达48通道的PTC触摸控制器。
项目所使用的EV41C56A评估板提供了丰富的硬件资源和调试接口。板上设有一个由PA15驱动的用户LED,该LED采用低电平有效驱动方式——当PA15输出低电平时电流从VCC经限流电阻流向PA15引脚,LED点亮;当PA15输出高电平时LED两端无电位差,LED熄灭。低电平有效驱动方式利用了GPIO引脚在低电平时的更强灌电流能力,能够提供更稳定的LED亮度。
评估板还提供了专用的触摸传感器区域,该区域通过标准化接口与PTC的Y16通道相连。触摸传感器区域通常以PCB铜箔作为感应电极,铜箔的形状和面积直接影响触摸灵敏度和检测距离——较大的电极面积能够提供更强的电容变化信号,但同时也可能增加对邻近信号的敏感性。
使用的硬件接口

系统的硬件接口关系非常简洁,主要包括PA15(LED驱动)和PTC的Y16通道(触摸传感器)两部分。PA15配置为GPIO输出模式,通过软件控制其输出电平实现LED的开关。PTC外设的Y16通道配置为自电容测量模式,传感器电极与PTC内部电路形成充放电回路,手指触摸时引入的附加电容改变了充放电时间常数,系统通过检测该时间常数的变化判断触摸是否发生。
三、方案框图和项目设计思路介绍
3.1 系统方案框图

3.2 软件层次结构

3.3 项目设计思路
在项目设计之初,首先面临的核心问题是:触摸传感器输出的原始数据是连续变化的模拟量(表现为充放电时间的长短),而应用层需要的是一个二值化的"触摸"或"未触摸"判定。如何将连续的模拟信号转化为离散的触摸状态,是整个系统软件设计的核心课题。Microchip QTouch库已经帮我们完成了从模拟信号到数字状态判定的主要工作。QTouch库在PTC外设采集到的原始信号基础上,通过去抖动计数器、参考值自适应和频率跳频等一系列信号处理算法,最终输出一个经过滤波和确认的数字状态。get_sensor_state(0) & KEY_TOUCHED_MASK返回的1或0,就是QTouch库处理后的结果。
QTouch库提供的判定结果虽然已经经过滤波处理,但在实际工程应用中,偶然的电气噪声、供电波动或者邻近信号的耦合干扰仍可能导致QTouch库偶尔输出错误的单次触摸判定。为了进一步提高系统的可靠性,本项目在QTouch库之上额外实现了一层应用层的防抖确认机制。该机制要求连续两次测量周期均检测到触摸状态才认为触摸事件成立——这意味着从手指首次触碰到系统最终确认触摸之间最多需要等待一个测量周期(20ms),但换来的是对偶发误触发的强大抑制能力。
四、软件流程图和关键代码介绍
4.1 软件总体流程

4.4 关键代码解析
应用层防抖状态
以下代码是本项目应用层防抖状态机的完整实现,包含了所有关键逻辑:
/* 防抖参数配置 */
#define APP_DEBOUNCE_PRESS 2 /* 连续2次按下才确认触摸 */
#define APP_DEBOUNCE_RELEASE 1 /* 连续1次释放即确认释放 */
/* 防抖状态枚举 */
typedef enum {
APP_IDLE, /* 空闲状态,未触摸 */
APP_DEBOUNCING, /* 防抖中,等待连续确认 */
APP_TRIGGERED /* 触摸已确认,LED已翻转 */
} AppState;
/* 模块变量 */
static AppState g_state = APP_IDLE; /* 当前状态机状态 */
static uint8_t g_press_cnt = 0; /* 连续按下计数器 */
static uint8_t g_rel_cnt = 0; /* 连续释放计数器 */
static bool g_led_state = false; /* LED当前状态 */
static uint8_t g_last_raw = 0; /* 上次原始读数 */
static uint32_t g_tick_cnt = 0; /* 测量周期计数 */
这里使用枚举类型定义状态机的三个状态,相比于使用整数常量,枚举具有更好的可读性和类型安全性。所有的计数器变量均使用uint8_t类型,因为防抖计数器的最大值不超过255,8位无符号整数的取值范围完全足够且节省存储空间。
主循环防抖处理逻辑
while (true) {
touch_process();
if (measurement_done_touch == 1u) {
measurement_done_touch = 0u;
g_tick_cnt++;
/* 读取传感器状态并提取触摸标志 */
uint8_t raw_touched = get_sensor_state(0) & KEY_TOUCHED_MASK;
switch (g_state) {
case APP_IDLE:
if (raw_touched) {
g_state = APP_DEBOUNCING;
g_press_cnt = 1;
g_rel_cnt = 0;
}
break;
case APP_DEBOUNCING:
if (raw_touched) {
g_press_cnt++;
if (g_press_cnt >= APP_DEBOUNCE_PRESS) {
/* 触摸已确认,翻转LED */
g_state = !g_led_state;
g_led_state = !g_led_state;
if (g_led_state) {
PORT_GroupSet(LED_PIN_PORT, LED_PIN_MASK);
} else {
PORT_GroupClear(LED_PIN_PORT, LED_PIN_MASK);
}
g_state = APP_TRIGGERED;
}
} else {
/* 释放发生在确认之前,判定为噪声,返回IDLE */
g_state = APP_IDLE;
g_press_cnt = 0;
}
break;
case APP_TRIGGERED:
if (!raw_touched) {
g_rel_cnt++;
if (g_rel_cnt >= APP_DEBOUNCE_RELEASE) {
g_state = APP_IDLE;
g_rel_cnt = 0;
}
} else {
g_rel_cnt = 0;
}
break;
}
g_last_raw = raw_touched;
}
}
这段代码体现了本项目防抖设计的核心思想。APP_DEBOUNCING状态中的防抖逻辑是整个算法的关键:只有当连续两个测量周期(共计40ms)均检测到触摸时,系统才认定触摸事件成立。在此期间如果发生过一次释放(即raw_touched变回0),系统立即返回APP_IDLE状态,丢弃之前累计的计数器值。这意味着短暂的电气干扰脉冲无法完成整个防抖确认过程,从而被有效过滤。
LED驱动实现
/* LED端口定义 */
#define LED_PIN_PORT PORT_GROUP_0
#define LED_PIN_MASK ((uint32_t)1U << 15U)
/* 端口初始化 */
PORT_GroupOutputEnable(LED_PIN_PORT, LED_PIN_MASK);
PORT_GroupClear(LED_PIN_PORT, LED_PIN_MASK); /* LED初始熄灭 */
/* LED状态翻转 */
g_led_state = !g_led_state;
if (g_led_state) {
PORT_GroupSet(LED_PIN_PORT, LED_PIN_MASK); /* 高电平,LED灭 */
} else {
PORT_GroupClear(LED_PIN_PORT, LED_PIN_MASK); /* 低电平,LED亮 */
}
LED驱动采用PORT_GroupSet和PORT_GroupClear两个函数操作端口寄存器。PORT_GROUP_0对应Port A组,LED_PIN_MASK定义为(1U << 15)即第15位掩码。系统初始化时LED为熄灭状态,当触摸被确认后,g_led_state取反,并通过相应的端口函数将PA15设置为高电平(灭)或低电平(亮)。
五、测试效果
我们看到非常稳定的LED控制:

六、心得体会
通过本项目的完整实践,对PIC32CM系列微控制器的开发流程有了系统性的掌握。在QTouch库的使用方面,深入理解了从传感器配置、测量参数整定到后处理算法调优的完整链路。关于去抖的描述往往是"延时消抖",检测到状态变化后等待一段时间(如10ms)再确认。但这种方法在实时系统中有一个显著缺陷:它会阻塞主循环,影响其他任务的执行。本项目采用的"连续计数确认"方案完全不依赖延时,通过计数器跨周期累积的方式实现滤波,既不消耗CPU时间又不阻塞系统响应。