活动开发板介绍
本次活动使用的开发板是Microchip EV41C56A开发板,PIC32CM LS00 Curiosity Nano+ Touch Evaluation Kit 是一款专为评估 PIC32CM5164LS00048 微控制器而设计的硬件平台。该套件基于安全且超低功耗的 ARM® Cortex®-M23 内核,集成了丰富的安全功能和触摸控制能力。其外形和引脚分配如下

板载资源如下,包括主控、Debugger、轻触开关、电容按键,并预留了丰富的排针接口

任务分析
本次活动我选择的是任务1:触摸输入与LED控制,实现内容包括通过PWM控制LED亮度,短按Touch Button实现LED开/关,长按Touch Button实现连续调节亮度,并按照【亮度从“灭 → 最亮”线性变化;当到达最亮后,保持长按可实现“最亮 → 灭”反向变化】的逻辑循环,要求亮度变化过程应平滑(无明显闪烁或跳变)
该任务可以拆分为两个任务
电容按键状态获取
根据开发板原理图,可以看到PA21和PA22对应着Touch Button的两个引脚

PWM输出控制LED亮度
需要控制的是连接在PA15上的USER LED,通过控制输出PWM的占空比来调整LED的亮度

目标实现功能
基于以上这些分析,可以设置芯片引脚如下,PA15设置为TCC0_WO5,可以作为PWM输出,PA21和PA22分别为GPIO Out和GPIO In

代码功能实现
整体架构
与机械式轻触开关不同,电容按键的实现依赖充放电,因此识别手指是否按上可以理解成检测电压是否超越阈值。为了检测电压是否超越阈值,首先需要定义一个baseline,即手指不触碰按键时的电压,这个电压会随着环境变化而变化,因此要实时更新。通过检测阈值变化可以判断手指的按下和松开,从而通过计算按下的时长,即可判断长短按,从而控制LED灯的亮灭,因此整体流程可以总结如下
启动阶段:
上电 → SYS_Initialize() → LED自检闪烁两次 → 进入主循环
主循环:
读取delta → 饱和检测 → 低通滤波 → 基线跟踪 → 触摸判定 → 长短按区分 → 输出PWM
结合代码内容的流程框图如下:

MCC配置
在MCC中配置系统框图如下,和大家使用Touch相关的库不同,这里我并没有引入该库,因为我发现可以在nonsecure_entry.h中引入相关的函数

配合上文中的引脚配置,点击MCC左上角的Generate即可完成代码初始化配置

此时工程文件的左侧的工程文件就会生成相关文件了,这里我们将不带secure的设置成目标工程

进入src/trustZone/nonsecure_entry.h,引入相关函数,这样就能引入Touch相关的库了,还不会报安全相关的错误
#ifndef NONSECURE_ENTRY_H_
#define NONSECURE_ENTRY_H_
/* Non-secure callable functions */
extern int secure_add(int x, int y);
extern void secure_pwm_set_duty(unsigned int duty);
extern unsigned int secure_touch_is_pressed(void);
extern unsigned int secure_touch_get_delta(void);
extern void secure_touch_force_recalibrate(void);
#endif /* NONSECURE_ENTRY_H_ */
电容按键状态获取
信号处理原理
系统每 20ms 采样一次触摸信号,核心变量如下:
- delta:原始触摸差分量,直接反映当前电容变化强度
- filtered:对 delta 进行低通滤波后的平滑值,用于抑制噪声
- baseline:无触摸基准值,用于补偿环境漂移(温度、湿度、板级变化)
处理流程如下
低通滤波
baseline自适应(仅在未触摸时更新)
当 filtered 小于 baseline:快速下拉 baseline
当 filtered 大于等于 baseline:慢速上拉baseline
滞回阈值判定
按下条件:
释放条件:
长短按区分原理
当检测到触摸上升沿(未触摸→触摸)时,清零 pressMs,每个采样周期 pressMs 累加 20ms,当pressMs 达到 600ms 进入长按模式
触摸释放时:
未进入长按模式:判定为短按
已进入长按模式:判定为长按结束
代码实现
/* 1) 采样与饱和保护 */
delta = secure_touch_get_delta();
if (delta >= SAT_DELTA) {
if (satMs < SAT_TIMEOUT_MS) {
satMs += POLL_INTERVAL_MS;
}
} else {
satMs = 0U;
}
if ((satMs >= SAT_TIMEOUT_MS) && (touchOn == false)) {
secure_touch_force_recalibrate();
satMs = 0U;
SYS_Tasks();
}
/* 2) 低通滤波 + 基线跟踪 */
filtered = (uint16_t)(((uint32_t)filtered * 3U + (uint32_t)delta) / 4U);
if (touchOn == false) {
if (filtered < baseline) {
baseline = filtered;
} else {
baseline = (uint16_t)(((uint32_t)baseline * 31U + (uint32_t)filtered) / 32U);
}
}
/* 3) 滞回判定 */
if ((!touchOn) && (filtered >= (uint16_t)(baseline + TOUCH_ON_OFFSET))) {
touchOn = true;
} else if (touchOn && (filtered <= (uint16_t)(baseline + TOUCH_OFF_OFFSET))) {
touchOn = false;
}
/* 4) 长短按计时 */
if (touchOn && (prevTouchOn == false)) {
pressMs = 0U;
longPressActive = false;
}
if (touchOn) {
if (pressMs < 60000U) {
pressMs += POLL_INTERVAL_MS;
}
if ((!longPressActive) && (pressMs >= LONG_PRESS_MS)) {
longPressActive = true;
}
}
PWM输出控制LED亮度
由于我在硬件设计上设定为active-low PWM,因此PWM占空比与视觉亮度相反,映射关系为
同时我还设计了一个开关状态
当lampEnabled = true:输出映射后的 duty
当lampEnabled = false:输出 PWM_PERIOD(LED 关闭)
长按调光原理
长按激活后,每个周期按固定步进 DIM_STEP 改变 brightness:
在上行阶段:brightness 增加,达到上限后反向
在下行阶段:brightness 减少,达到下限后反向
从而实现循环调光的效果
代码实现
/* 长按调光:上下边界反向 */
if (longPressActive) {
if (dimDirectionUp) {
if ((uint32_t)brightness + DIM_STEP >= PWM_PERIOD) {
brightness = PWM_PERIOD;
dimDirectionUp = false;
} else {
brightness = (uint16_t)(brightness + DIM_STEP);
}
} else {
if (brightness <= DIM_STEP) {
brightness = 0U;
dimDirectionUp = true;
} else {
brightness = (uint16_t)(brightness - DIM_STEP);
}
}
lampEnabled = (brightness > 0U);
if (brightness > 0U) {
lastNonZeroBrightness = brightness;
}
}
/* 触摸释放:短按切换开关;长按保持当前亮度 */
else if (prevTouchOn) {
if (!longPressActive) {
lampEnabled = !lampEnabled;
if (lampEnabled && (brightness == 0U)) {
brightness = lastNonZeroBrightness;
}
} else {
lampEnabled = (brightness > 0U);
}
pressMs = 0U;
longPressActive = false;
}
/* PWM 输出(active-low) */
prevTouchOn = touchOn;
if (lampEnabled) {
secure_pwm_set_duty((uint16_t)(PWM_PERIOD - brightness));
} else {
secure_pwm_set_duty(PWM_PERIOD);
}
完整main.c代码
/*******************************************************************************
Main Source File
Company:
Microchip Technology Inc.
File Name:
main.c
Summary:
This file contains the "main" function for a project.
Description:
This file contains the "main" function for a project. The
"main" function calls the "SYS_Initialize" function to initialize the state
machines of all modules in the system
*******************************************************************************/
// *****************************************************************************
// *****************************************************************************
// Section: Included Files
// *****************************************************************************
// *****************************************************************************
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include "definitions.h"
#include "trustZone/nonsecure_entry.h"
#include <stdint.h>
#define PWM_PERIOD 999U
#define POLL_INTERVAL_MS 20U
#define STARTUP_IGNORE_MS 1500U
#define SAT_DELTA 220U
#define SAT_TIMEOUT_MS 1500U
#define TOUCH_ON_OFFSET 18U
#define TOUCH_OFF_OFFSET 8U
#define LONG_PRESS_MS 600U
#define DIM_STEP 8U
/*
* delay_ms - Busy-wait delay function.
* @ms: Delay duration in milliseconds.
*
* Description:
* Implements a simple software delay by spinning in a tight loop.
* The multiplier (12000U) is calibrated for the processor clock frequency.
* This function uses active CPU cycles (not sleep), so it consumes power.
*/
static void delay_ms(uint32_t ms)
{
volatile uint32_t count = ms * 12000U;
while (count != 0U)
{
count--;
}
}
/*
* led_startup_self_test - Perform LED startup self-test sequence.
*
* Description:
* Toggles the LED between maximum brightness (duty 0) and off (duty PWM_PERIOD)
* four times with 200ms intervals. This visual indicator confirms that:
* - The PWM controller is initialized and functioning.
* - The LED is physically connected and working.
* - The polarity (active-low configuration) is correctly set up.
*/
static void led_startup_self_test(void)
{
/* Alternate min/max duty briefly so either polarity gives visible activity. */
secure_pwm_set_duty(0U);
delay_ms(200U);
secure_pwm_set_duty(PWM_PERIOD);
delay_ms(200U);
secure_pwm_set_duty(0U);
delay_ms(200U);
secure_pwm_set_duty(PWM_PERIOD);
delay_ms(200U);
}
// *****************************************************************************
// *****************************************************************************
// Section: Main Entry Point
// *****************************************************************************
// *****************************************************************************
/*
* main - Non-secure application main entry point.
*
* Description:
* Initializes the system, performs LED self-test, and enters the main control loop.
*
* Main Loop Behavior:
* 1. Startup Phase (1.5s): Calibrates touch baseline, LED remains off.
* 2. Saturation Detection: Monitors touch delta; if saturated for 1.5s, recalibrates.
* 3. Touch State Machine:
* - Short Press (<600ms): Toggles lamp on/off.
* - Long Press (>=600ms): Smoothly cycles brightness up (0→999) then down (999→0).
* 4. Active-Low PWM: Duty=0 means max brightness; Duty=999 means off.
* 5. Poll Interval: 20ms between samples.
*
* Control Variables:
* - brightness: Current PWM duty cycle (0-999).
* - lampEnabled: Logical lamp state (on/off).
* - dimDirectionUp: Long-press dimming direction (up=true, down=false).
* - longPressActive: True if in continuous dimming mode.
* - baseline/filtered: Touch baseline and low-pass filtered delta values.
* - touchOn: Raw touch sensor state.
* - pressMs: Time counter for short/long press discrimination.
*
* Return: Never returns (infinite loop).
*/
int main ( void )
{
uint32_t startupIgnoreMs = STARTUP_IGNORE_MS;
uint32_t satMs = 0U;
uint16_t baseline = 0U;
uint16_t filtered = 0U;
uint16_t brightness = PWM_PERIOD;
uint16_t lastNonZeroBrightness = PWM_PERIOD;
bool touchOn = false;
bool prevTouchOn = false;
bool lampEnabled = false;
bool longPressActive = false;
bool dimDirectionUp = false;
uint32_t pressMs = 0U;
unsigned int delta;
SYS_Initialize(NULL);
led_startup_self_test();
secure_pwm_set_duty(PWM_PERIOD);
while ( true )
{
/*
* Startup Phase: First 1.5 seconds after boot.
* Goal: Establish touch baseline without user interaction.
* LED is held off (duty=PWM_PERIOD) during this phase.
*/
if (startupIgnoreMs > 0U) {
if (startupIgnoreMs > POLL_INTERVAL_MS) {
startupIgnoreMs -= POLL_INTERVAL_MS;
}
else {
startupIgnoreMs = 0U;
secure_touch_force_recalibrate();
}
delta = secure_touch_get_delta();
if (baseline == 0U) {
baseline = (uint16_t)delta;
filtered = (uint16_t)delta;
}
else {
baseline = (uint16_t)(((uint32_t)baseline * 7U + (uint32_t)delta) / 8U);
filtered = baseline;
}
secure_pwm_set_duty(PWM_PERIOD);
delay_ms(POLL_INTERVAL_MS);
SYS_Tasks();
continue;
}
/*
* Saturation Detection & Recovery.
* If touch delta remains high (>220) for 1.5s, suspect sensor saturation.
* Only recalibrate when NOT actively touching to avoid disrupting user input.
*/
delta = secure_touch_get_delta();
if (delta >= SAT_DELTA) {
if (satMs < SAT_TIMEOUT_MS) {
satMs += POLL_INTERVAL_MS;
}
}
else {
satMs = 0U;
}
/*
* Execute Saturation Recalibration.
* Only trigger recalibration if saturated AND currently not touching.
* This prevents visible LED glitches during active user input.
*/
if ((satMs >= SAT_TIMEOUT_MS) && (touchOn == false)) {
secure_touch_force_recalibrate();
satMs = 0U;
SYS_Tasks();
}
/*
* Low-Pass Filter & Baseline Tracking.
* Apply exponential moving average (3:1 ratio) to raw delta.
* While not touching, slowly update baseline (31:1 ratio) to adapt to gradual sensor drift.
*/
filtered = (uint16_t)(((uint32_t)filtered * 3U + (uint32_t)delta) / 4U);
if (touchOn == false) {
if (filtered < baseline) {
baseline = filtered;
}
else {
baseline = (uint16_t)(((uint32_t)baseline * 31U + (uint32_t)filtered) / 32U);
}
}
/*
* Touch State Detection with Hysteresis.
* TOUCH_ON_OFFSET (18) and TOUCH_OFF_OFFSET (8) create hysteresis to avoid chatter.
* Transition on touch: filtered >= baseline + 18.
* Transition off: filtered <= baseline + 8.
*/
if ((!touchOn) && (filtered >= (uint16_t)(baseline + TOUCH_ON_OFFSET))) {
touchOn = true;
}
else if (touchOn && (filtered <= (uint16_t)(baseline + TOUCH_OFF_OFFSET))) {
touchOn = false;
}
/*
* Short/Long Press State Machine.
* On initial touch: reset press timer and long-press flag.
* While touching: measure press duration and decide between short-press (toggle) or long-press (dimming).
*/
if (touchOn && (prevTouchOn == false)) {
pressMs = 0U;
longPressActive = false;
}
/*
* Active Touch: Handle continuous long-press dimming.
* Increment press timer each poll cycle (20ms intervals).
* Trigger long-press mode after 600ms; then smoothly cycle brightness.
*/
if (touchOn) {
if (pressMs < 60000U) {
pressMs += POLL_INTERVAL_MS;
}
if ((!longPressActive) && (pressMs >= LONG_PRESS_MS)) {
longPressActive = true;
if (!lampEnabled)
{
brightness = 0U;
dimDirectionUp = true;
}
}
/*
* Long-Press Dimming Logic.
* Once active, increment/decrement brightness by DIM_STEP (8) each 20ms cycle (~160ms to cross full range).
* At boundaries (0 and 999), reverse direction for continuous cycling.
* Update lampEnabled state based on whether brightness is non-zero.
*/
if (longPressActive)
{
if (dimDirectionUp) {
if ((uint32_t)brightness + DIM_STEP >= PWM_PERIOD) {
brightness = PWM_PERIOD;
dimDirectionUp = false;
}
else {
brightness = (uint16_t)(brightness + DIM_STEP);
}
}
else {
if (brightness <= DIM_STEP) {
brightness = 0U;
dimDirectionUp = true;
}
else {
brightness = (uint16_t)(brightness - DIM_STEP);
}
}
lampEnabled = (brightness > 0U);
if (brightness > 0U) {
lastNonZeroBrightness = brightness;
}
}
}
/*
* Touch Release: Handle short-press toggle or long-press end.
* If short-press: toggle lamp on/off.
* If long-press: preserve the current dimming state (brightness).
*/
else if (prevTouchOn) {
if (!longPressActive) {
lampEnabled = !lampEnabled;
if (lampEnabled && (brightness == 0U)) {
brightness = lastNonZeroBrightness;
}
}
else {
lampEnabled = (brightness > 0U);
}
pressMs = 0U;
longPressActive = false;
}
/*
* Final PWM Output Stage.
* Active-Low Mapping:
* - lampEnabled=true: duty = (PWM_PERIOD - brightness) => LED brightness proportional to brightness value.
* - lampEnabled=false: duty = PWM_PERIOD => LED completely off.
* This compensates for the inverse-polarity hardware setup.
*/
prevTouchOn = touchOn;
if (lampEnabled) {
secure_pwm_set_duty((uint16_t)(PWM_PERIOD - brightness));
}
else {
secure_pwm_set_duty(PWM_PERIOD);
}
delay_ms(POLL_INTERVAL_MS);
SYS_Tasks();
}
return ( EXIT_FAILURE );
}
/*******************************************************************************
End of File
*/
实现效果
最终实现效果如下
短按实现LED灯亮灭

长按实现呼吸灯效果

心得体会
这是我第一次完整基于Microchip的开发板实现的功能,Microchip的开发相较于其他MCU而言时相当繁琐且复杂的,通过这一次活动,我对Microchip的MCU开发流程有了一定了解,此外,通过对电容按键的读取,我对电容按键的控制方法也有了更深一步的理解。