内容介绍
内容介绍
一、项目概述
本项目基于Microchip PIC32CM5164微控制器(搭载于PIC32CMLS00开发板),利用片上集成的触摸传感器外设和PWM(脉冲宽度调制)模块,实现对开发板板载LED亮度的准连续触摸控制。用户通过触摸感应区域即可直观地控制LED的开关及亮度渐变,提供了流畅、可靠的人机交互体验。
项目采用MPLAB X IDE作为开发环境,借助MCC(MPLAB Code Configurator)图形化配置工具快速初始化系统时钟、触摸库、PWM外设及GPIO,极大降低了底层驱动开发复杂度,实现应用程序逻辑的快速原型验证。
二、硬件平台
- 开发板:Microchip PIC32CMLS00
- 主控MCU:PIC32CM5164(ARM Cortex-M23内核,支持TrustZone安全扩展)
- 触摸外设:集成PTC(Peripheral Touch Controller),支持自电容或互电容触摸检测
- 执行单元:板载LED(通过PWM驱动,最多支持4通道同步控制)
- 其他资源:TCC(Timer/Counter for Control)定时器,用于生成高精度PWM波形
三、软件工具
- IDE:MPLAB X IDE(v6.00及以上),主界面如下图
- 配置工具:MCC(MPLAB Code Configurator,集成在MPLAB X IDE内)
- 编译器:XC32(集成在MPLAB X IDE)
- 触摸库:QTouch Library(提供触摸信号采集、滤波及状态机处理)

四、功能特性
1. 短按开关
- 在LED熄灭状态下短触摸 → LED以最近一次保存的亮度点亮
- 在LED点亮状态下短触摸 → LED熄灭并保存当前亮度值
2. 长按调光
- 触摸并保持超过预设阈值(约800ms)
- 系统进入呼吸渐变模式:PWM占空比自动从当前值开始递增或递减,触及最大或最小占空比后自动反向
- 实现平滑、准连续的亮度线性变化
3. 状态记忆
- 每次关闭LED时,当前占空比被保存至备份变量
- 下次开启LED时自动恢复至上次调光后的亮度,实现亮度记忆功能
4. 实时响应
- 触摸检测采用中断+轮询混合机制(由QTouch库管理)
- PWM占空比更新实时生效,无触摸到视觉的延迟
附:长/短按下的LED响应如下,
短按:

长按:

五、系统工作原理
- 系统初始化:
- MCC配置系统时钟(如48MHz)、TCC定时器工作于24位PWM模式,PWM频率固定
- 导入QTouch库并配置单通道触摸按键
- 主循环处理:
- 周期性调用触摸处理函数,检测触摸状态及持续时长
- 根据触摸状态(按下/释放/长按)及当前LED状态,驱动状态机执行开关或调光动作
- PWM控制:
- 占空比通过修改TCC比较寄存器(CC[0]~CC[3])实现
- 四个通道同步更新,保证多色或单色LED亮度一致(但目前只使用了1个通道的输出,4个用于预留)
- 防抖动与边界保护:
- 触摸释放时重置状态计数器,避免误触发
- 占空比限制在安全范围内(最小值100,最大值周期数-1),防止PWM异常
要实现的LED的状态机图如下:

六、 开发过程
- 打开MPLAB X IDE,新建一个工程,选择开发板对应的目标MCU(连接开发板后会IDE会直接产品介绍页面,方便下载资料、寻找例程),或直接以Microchip官方提供的pwm例程作为基础,增加触摸检测、控制调光的逻辑即可。
- 打开MCC配置管理器,如下图,首次使用,需下载所用到的外设的驱动库,此处可以touch为关键字搜索,将与触摸相关的库都安装。安装好后,可使用图形化的方式配置MCU的各个外设,避免了读datasheet、配置寄存器的高门槛


- 打开MCC配置界面后,在左侧添加所需的外设驱动库,对于本项目,配置完成后如右图所示(核心是将PTC外设和RTC外设启用、并连接到了Touch Library),另外,可以见到几个核心库已经被项目自动添加了(内核、CMSIS部分等)

- 使用Touch Configuration打开外设配置界面,这里主要是配置PTC外设的各项参数的阈值、检测周期、状态机参数等,具体的配置方式参考PIC32CM51系列的manual的PTC章节即可(其本质是检测电容值的变化来判断触摸,支持多个通道,各通道的管脚也可配置)。


- 配置完成后点击Generate即可生成外设部分的驱动代码,此后我们只需要实现逻辑部分即可。

- 初始化各模块和参数,给PWM一个上电初始值,使得触摸开灯后有一个明显的亮度。在主while循环中编写状态机处理代码,实现通过触摸控制PWM占空比的代码。行为逻辑见下表,关键代码片段解析如下(完整工程代码见附件)
LED当前状态 | 触发动作 | 预期效果 |
|---|---|---|
关 | 短按 | 开启LED,亮度为上次关闭时的亮度 |
关 | 长按 | 开启LED,到达“长按”条件后开始调亮度,直到(未松开)最大或最小亮度后停止调亮度 |
开 | 短按 | 关闭LED |
开 | 长按 | 到达“长按”条件后开始调亮度,直到(未松开)最大或最小亮度后停止调亮度 |
- 全局变量与配置
#define LONG_TOUCH_THR 800
static uint32_t period; // PWM周期
static uint32_t tcc_backup = 600;// LED关闭时备份的占空比
static bool cur_on = false; // LED当前是否亮着
static bool increasing = true; // 亮度变化方向(增/减)
static bool block = false; // 是否阻止亮度调节(翻转瞬间短暂锁定)
- period:PWM周期(最大值)。
- tcc_backup:记录关灯前的占空比,用于恢复。
- cur_on:LED实际是否在发光。
- increasing:控制呼吸灯效果的方向(变亮或变暗)。
- block:用于在占空比到达边界时,暂时阻止额外的占空比变化,防止快速连续翻转。
- 函数
change_led_duty()—— 呼吸渐变
int cur_tcc = (int)(TCC0_REGS->TCC_CC[0]);
if (increasing) cur_tcc += 1;
else cur_tcc -= 1;
每次调用,占空比 cur_tcc 加1或减1,产生缓慢渐变。
边界处理:
- 若
cur_tcc > period - 1→ 设为最大值,方向取反,block = true(暂时锁定)。 - 若
cur_tcc < 100→ 设为最小值100,方向取反,block = true。
- 若
同时更新四个通道(四路PWM,但只有1路连到了LED):
TCC0_REGS->TCC_CC[0] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[1] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[2] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[3] = cur_tcc & 0xFFFFFF;
- 函数
toggle_led()—— 开关LED
if (cur_on) {
tcc_backup = cur_tcc; // 关灯前保存当前亮度
cur_tcc = 0; // 关灯(占空比0)
} else {
cur_tcc = tcc_backup; // 恢复上次亮度
}
cur_on = !cur_on;
注意: 关灯时保存占空比,开灯时恢复的亮度是上次关灯前的值。
完整的main函数和相关代码如下:
#include <stddef.h> // Defines NULL
#include <stdbool.h> // Defines true
#include <stdlib.h> // Defines EXIT_FAILURE
#include "definitions.h" // SYS function prototypes
#define LONG_TOUCH_THR 800
/* Save PWM period */
static uint32_t period;
static uint32_t tcc_backup = 600;
static bool cur_on = false;
static bool increasing = true;
static bool block = false;
/* typedef for non-secure callback functions */
typedef void (*funcptr_void) (void) __attribute__((cmse_nonsecure_call));
static void change_led_duty(void)
{
int cur_tcc = (int)(TCC0_REGS->TCC_CC[0]);
if (increasing) {
cur_tcc += 1;
} else {
cur_tcc -= 1;
}
if (cur_tcc > period - 1) {
cur_tcc = period - 1;
increasing = !increasing;
block = true;
} else if (cur_tcc < 100) {
cur_tcc = 100;
increasing = !increasing;
block = true;
}
TCC0_REGS->TCC_CC[0] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[1] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[2] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[3] = cur_tcc & 0xFFFFFF;
}
static void toggle_led(void)
{
uint32_t cur_tcc = (TCC0_REGS->TCC_CC[0]);
if (cur_on) {
tcc_backup = cur_tcc;
cur_tcc = 0;
} else {
cur_tcc = tcc_backup;
}
cur_on = !cur_on;
TCC0_REGS->TCC_CC[0] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[1] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[2] = cur_tcc & 0xFFFFFF;
TCC0_REGS->TCC_CC[3] = cur_tcc & 0xFFFFFF;
}
int main ( void )
{
bool long_touch = false;
bool touching = false;
bool turned_on = false;
uint32_t touch_count = 0;
uint32_t msp_ns = *((uint32_t *)(TZ_START_NS));
volatile funcptr_void NonSecure_ResetHandler;
uint16_t sensor_state, sensor_state_counter, channel_reference;
/* Initialize all modules */
SYS_Initialize ( NULL );
if (msp_ns != 0xFFFFFFFF)
{
/* Set non-secure main stack (MSP_NS) */
__TZ_set_MSP_NS(msp_ns);
/* Get non-secure reset handler */
NonSecure_ResetHandler = (funcptr_void)(*((uint32_t *)((TZ_START_NS) + 4U)));
/* Start non-secure state software application */
NonSecure_ResetHandler();
}
/* Read the period */
period = TCC0_PWM24bitPeriodGet();
/* Start PWM*/
TCC0_PWMStart();
while ( true )
{
touch_timer_handler();
/* call touch process function */
touch_process();
if(measurement_done_touch == 1u)
{
measurement_done_touch = 0u;
sensor_state = qtlib_key_data_set1[0].sensor_state;
sensor_state_counter = qtlib_key_data_set1[0].sensor_state_counter;
channel_reference = qtlib_key_data_set1[0].channel_reference;
// process touch data
if (qtlib_key_data_set1[0].sensor_state == 0x85) {
if (!touching) {
touching = true;
if (!cur_on) {
toggle_led();
turned_on = true;
}
} else {
touch_count += 1;
long_touch = (touch_count >= LONG_TOUCH_THR) ? true : false;
}
if (long_touch && cur_on && !block) {
change_led_duty();
}
} else {
if (touching) {
touching = false;
if (long_touch || turned_on) {
turned_on = false;
touch_count = 0;
long_touch = false;
block = false;
} else {
toggle_led();
}
}
}
}
}
/* Execution should not come here during normal operation */
return ( EXIT_FAILURE );
}
七、使用MPLAB X IDE的优势
- 低开发门槛:MCC图形化配置 + Touch触摸库,无需手动编写触摸底层驱动(类似STM32CubeMX)
- 例程丰富:可在MPLAB X IDE中直接搜索所用开发板/MCU的例程,方便上手
- 可扩展性强:支持多通道触摸按键 + 多路PWM输出,可扩展为彩色LED调光或亮度分组控制
- 安全特性:MCU支持TrustZone,可分割安全与非安全区代码(本项目中未实现)
八、应用场景和后续优化方向
场景
- 智能家居面板触摸调光灯具
- 交互式桌面台灯或氛围灯
- 工业设备触摸式亮度/参数调节器
可优化方向
- 使用真实的LED和配套的驱动电路,代替板载LED,实现真正可用的照明台灯或吸顶灯等。
- 标定PTC外设的几个阈值参数,使其对湿手时的触摸也有良好反馈
- 引入PWM gamma校正,使亮度变化更符合人眼感知
- 添加非易失存储(EEPROM/Flash),断电记忆最后亮度
附件下载
touch_led.rar
工程源码
团队介绍
嵌入式软件工程师FishLee,单兵作战
评论
0 / 100
查看更多
猜你喜欢
Funpack5-2 - 用EV41C56A开发板实现触摸控制LED亮度该项目使用了EV41C56A开发板,实现了触摸控制LED亮度的设计,它的主要功能为:触摸、短按、长按和LED调光。
--小灰灰--
38
Funpack5-2 - 用EV41C56A实现触摸控制LED该项目使用了Microchip EV41C56A 开发板,实现了触摸输入与LED控制的设计,它的主要功能为:单次触摸点亮板载LED,再次触摸关闭LED。
xusp99
34
Funpack5-2 - 基于EV41C56A开发板实现触摸输入与LED控制该项目使用了Microchip EV41C56A 开发板,实现了触摸输入与LED控制的设计,它的主要功能为:电容触摸按键控制LED灯亮灭,包括短按和长按控制。
FuShenxiao
38