Funpack5-2 - 基于EV41C56A开发板实现触摸输入与LED控制
该项目使用了Microchip EV41C56A 开发板,实现了触摸输入与LED控制的设计,它的主要功能为:电容触摸按键控制LED灯亮灭,包括短按和长按控制。
标签
Funpack活动
开发板
Microchip
触摸
FuShenxiao
更新2026-06-17
7

活动开发板介绍

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

image.png

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

image.png

任务分析

本次活动我选择的是任务1:触摸输入与LED控制,实现内容包括通过PWM控制LED亮度,短按Touch Button实现LED开/关,长按Touch Button实现连续调节亮度,并按照【亮度从“灭 → 最亮”线性变化;当到达最亮后,保持长按可实现“最亮 → 灭”反向变化】的逻辑循环,要求亮度变化过程应平滑(无明显闪烁或跳变)

该任务可以拆分为两个任务

电容按键状态获取

根据开发板原理图,可以看到PA21和PA22对应着Touch Button的两个引脚

image.png

PWM输出控制LED亮度

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

image.png

目标实现功能

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

image.png

代码功能实现

整体架构

与机械式轻触开关不同,电容按键的实现依赖充放电,因此识别手指是否按上可以理解成检测电压是否超越阈值。为了检测电压是否超越阈值,首先需要定义一个baseline,即手指不触碰按键时的电压,这个电压会随着环境变化而变化,因此要实时更新。通过检测阈值变化可以判断手指的按下和松开,从而通过计算按下的时长,即可判断长短按,从而控制LED灯的亮灭,因此整体流程可以总结如下

启动阶段:

上电 → SYS_Initialize() → LED自检闪烁两次 → 进入主循环

主循环:

读取delta → 饱和检测 → 低通滤波 → 基线跟踪 → 触摸判定 → 长短按区分 → 输出PWM

结合代码内容的流程框图如下:

diagram.png

MCC配置

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

image.png

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

image.png

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

image.png

进入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 采样一次触摸信号,核心变量如下:

  1. delta:原始触摸差分量,直接反映当前电容变化强度
  2. filtered:对 delta 进行低通滤波后的平滑值,用于抑制噪声
  3. 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开发流程有了一定了解,此外,通过对电容按键的读取,我对电容按键的控制方法也有了更深一步的理解。

附件下载
led.zip
项目工程代码
团队介绍
傅沈骁是一名UEFI开发工程师
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号