Funpack5-3 - 用MAX32690EVKIT评估套件实现按键控制LED闪烁
该项目使用了MAX32690EVKIT评估套件,实现了按键控制LED闪烁的设计,它的主要功能为:读取按键输入,控制LED的闪烁启动和闪烁频率。
标签
嵌入式系统
Funpack活动
开发板
57SSG
更新2026-08-13
29

1.任务

我选择的活动任务是任务1:按键控制LED闪烁

1.1任务需求

  1. 正确初始化 GPIO 中断
  2. LED 控制逻辑清晰,不出现误触发
  3. 闪烁频率约 1Hz
  4. 短按:LED 闪烁 开启/关闭
  5. 长按:连续调节闪烁频率
    1. 频率从"1Hz → 5Hz"线性变化
    2. 当到达最快后,保持长按可实现"5Hz → 1Hz"反向变化
    3. 按上面的逻辑循环
  6. 频率变化过程应平滑(无明显跳变)

1.2 完成的功能

使用SW2输入按键状态,并使用定时器消抖,完成按键按下和松开识别.当按键被短按(按下持续时间小于1s)时, 开关LED闪烁.当按键被长按(按下持续时间大于1s),每隔1s切换一次LED闪烁频率;当到达最快后,保持长按可实现"5Hz → 1Hz"反向变化.LED的使用PWM的输出引脚进行驱动.当LED闪烁被关闭时,短按和长按按键都会开启LED闪烁.

2.实现思路

2.1软件思路

使用硬件定时器TMR1输出PWM波驱动LED,通过修改PWM的频率来控制LED的闪烁频率.占空比固定为50%.


按键采用中断驱动方案,GPIO4.0配置为双边沿中断.按下时(下降沿)启动10ms软件去抖定时器,定时器到期后通知上层"按键按下".释放时(上升沿)立即通知"按键释放"并停止去抖定时器.


长按检测使用一个1000ms周期定时器:按下时启动,释放时停止.如果定时器到期说明长按超过1s,设置标志位触发频率切换.频率切换在PWM周期结束中断中执行,通过标志位延迟到中断上下文中处理.


主循环while(1)空转,所有事件由中断驱动.


整个软件分为三层:

应用层: main.c(入口), LedTwinkCtrl.c(LED控制), keyCtrl.c(按键事件)

定时器层: softTimer.c(软件定时器池), sysTimer.c(TMR0时基)

硬件层: GPIO4.0, TMR0, TMR1, LED,GPIO

实现大概流程:

image.png


2.2硬件思路

硬件说明 - 复制(1).jpg

查看MAX32690 EVKIT开发板原理图,了解SW2按键和LED的连接方式.LED使用三极管进行驱动, 而MCU只需要给一个控制信号来导通三极管和截至三极管就可以控制LED的亮灭. MAX32690的TMR1的输出信号TMR1_IOA在P0_7引脚。通过取下JP5的跳线帽,然后将P0_7引脚通过杜邦线连接到JP5的左端引脚,也就是LED控制信号引脚。这样就完成了PWM驱动LED的硬件配置。

SW2按键一端接GPIO4.0,另一端接地.因此按下时GPIO4.0为低电平,释放时通过软件配置的弱上拉保持高电平.

硬件资源分配:

TMR0: 系统时基,连续计数模式,APB_CLK时钟,预分频32,1kHz中断

TMR1: LED PWM输出,PWM模式,ISO_CLK时钟,预分频1024

GPIO4.0: 按键输入,弱上拉,双边沿中断


4.代码实现

4.1main.c - 系统初始化

系统入口负责初始化各模块并注册回调,主循环空转:

int main(void)
{
    printf("System start\n");
    TimerInit(SYS_FREQ);
    SysTimerInit(SYS_FREQ, TimerHandle);
    LedTwinkCtrlInit();
    keyCtrlInfo keyCtrlInfoLed = keyCtrlInit(keyIndexLed);
    // 通过ledCtrlKeyCallback函数注册按键事件回调函数,将按键事件传递给LED控制模块使用
    keyCtrlRegisterCallback(keyCtrlInfoLed, ledCtrlKeyCallback);


    while (1)
    {
    }
}

其中 SYS_FREQ 定义为 1000 (Hz),即 1ms tick 周期。TimerInit 初始化软件定时器池(4个槽位),SysTimerInit 启动 TMR0 硬件定时器产生 1kHz 中断并将 TimerHandle 注册为回调。LedTwinkCtrlInit 配置 TMR1 为 PWM 模式。keyCtrlInit 初始化 GPIO4.0 为中断输入模式。


4.2sysTimer.c - 系统时基

使用TMR0配置为连续计数模式,产生1kHz中断,为softTimer提供时基:

void SysTimerInit(uint32_t freq, void (*callback)(void))
{
    // Declare variables
    mxc_tmr_cfg_t tmr;
    uint32_t periodTicks = MXC_TMR_GetPeriod(CONT_TIMER, CONT_CLOCK_SOURCE, 32, freq);


    /*
    Steps for configuring a timer for PWM mode:
    1. Disable the timer
    2. Set the prescale value
    3  Configure the timer for continuous mode
    4. Set polarity, timer parameters
    5. Enable Timer
    */


    MXC_TMR_Shutdown(CONT_TIMER);


    tmr.pres = TMR_PRES_32;
    tmr.mode = TMR_MODE_CONTINUOUS;
    tmr.bitMode = TMR_BIT_MODE_32;
    tmr.clock = CONT_CLOCK_SOURCE;
    tmr.cmp_cnt = periodTicks; //SystemCoreClock*(1/interval_time);
    tmr.pol = 0;


    if (MXC_TMR_Init(CONT_TIMER, &tmr, false) != E_NO_ERROR) {
        printf("Failed Continuous timer Initialization.\n");
        return;
    }


    MXC_TMR_EnableInt(CONT_TIMER);


    extCallback = callback;
    MXC_NVIC_SetVector(TMR0_IRQn, SysTimerIntHandler);
    NVIC_EnableIRQ(TMR0_IRQn);


    MXC_TMR_Start(CONT_TIMER);


    printf("Continuous timer started.\n\n");
}


4.3softTimer.c - 软件定时器

提供轻量级软件定时器池,支持一次性(One-Shot)和周期性(Periodic)两种模式.定时器槽位通过静态数组分配:

本工程中定时器使用情况:

槽位0: 按键去抖 (One-Shot, 10ms, 在 keyDriver.c 中使用)

槽位1: 长按检测 (Periodic, 1000ms, 在 LedTwinkCtrl.c 中使用)

槽位2-3: 预留


4.4keyDriver.c - 按键驱动

负责GPIO硬件配置和去抖处理.采用双边沿中断 + 软件去抖方案.

每个按键使用独立的软件定时器进行消抖。去抖定时器到期后,根据回调函数传入的定时器权柄, 查找对应的按键并通知按键按下:

static void keyDriverNotifyPressed(timerHandle const handle)
{
    for(int i = 0; i < keyIndexMax; i++)
    {
        if (keyDriverInfoList[i].debounceTimerHandle == handle)
        {
            keyDriverNotifyState(&keyDriverInfoList[i], keyDriverPressed);
            break;
        }
    }
}

4.5keyCtrl.c - 按键控制

将 keyDriver 上报的 Pressed/Released 状态转换为业务事件 keyEventPressed/keyEventReleased,通过回调通知应用层.


keyCtrlRegisterCallback() 注册应用层回调,从而将各个不同按键的事件分发给不同的模块:

void keyCtrlRegisterCallback(keyCtrlInfo keyCtrlInfo, keyEventCallback callback)

{

keyCtrlInfo->callback = callback;

}

4.6LedTwinkCtrl.c - LED闪烁控制

整合按键事件处理和PWM驱动,实现LED闪烁的开关控制和频率切换. 按键长按和短按的识别由LED控制模块利用软件定时器实现,属于应用层逻辑

频率序列定义为数组,到达5Hz后自动反向变化:

 int ledTwinkFreq[] = {1, 2, 3, 4, 5, 4, 3, 2};

void ledCtrlKeyCallback(keyEvent_t event)
{
    static timerHandle ledTwinkTimerHandle;
    if (event == keyEventPressed)
    {
        isLongPress = USR_FALSE;
        printf("Key pressed\n");
        TimerStart(&ledTwinkTimerHandle, TIMER_TYPE_PERIODIC, TimerMs2Ticks(1000), LedTwinkReqUpdate);
    }
    else
    {
        printf("Key released\n");
        if (isLongPress == USR_FALSE || pwmStatus == pwmStatusStopped)
        {
            PwmStatusToggle();
        }
        TimerStop(&ledTwinkTimerHandle);
    }
}


PWM周期结束中断中检查标志位并执行频率切换:

static void ledTwinkParaUpdate(void)
{
    if (isReqUpdate == USR_TRUE)
    {
        isReqUpdate = USR_FALSE;
        if (pwmStatus == pwmStatusRunning)
        {
            printf("ledTwinkParaUpdate\n");
            LedTwinkCtrlStop();
            ledTwinkFreqIndex = (ledTwinkFreqIndex + 1) % (int)(sizeof(ledTwinkFreq) / sizeof(ledTwinkFreq[0]));
            printf("freqIndex:%d", ledTwinkFreqIndex);
            LedTwinkCtrlSet(ledTwinkFreq[ledTwinkFreqIndex], DEFAULT_DUTY);
            LedTwinkCtrlStart();
        }
        else
        {
            /* nop */
        }
    }
    else
    {
        /* nop */
    }
}
static void PwmPeriodEndCallBack(void)
{
    ledTwinkParaUpdate();
    printf("pwmCb\n");
}

LedTwinkCtrlInit() 完成 TMR1 的 PWM 配置和中断注册, 需要注意:

void LedTwinkCtrlInit(void)
{
    /*
    Steps for configuring a timer for PWM mode:
    1. Disable the timer
    2. Set Frequency and Duty Cycle
    3. Set polarity, PWM parameters
    4. Configure the timer for PWM mode
    5. Enable Timer
    */
    ledTwinkFreqIndex = 0;
    isReqUpdate       = USR_FALSE;
    isLongPress       = USR_FALSE;

    PwmHalInit(ledTwinkFreq[ledTwinkFreqIndex], DEFAULT_DUTY);
    PwmHalRegisterCallback(PwmPeriodEndCallBack);

    LedTwinkCtrlStart();
}

5.功能展示

5.1闪烁频率可以通过PWM周期回调串口输出确定, 以下是5Hz闪烁频率是的串口输出

5.2短按开关闪烁

5.3持续按键每隔1s加快一次闪烁频率

5.4持续按键每隔一s降低闪烁频率

6.心得体悟和遇到的困难

6.1心得体悟

这次工程让我理解了嵌入式系统中分层设计的重要性.最初写代码时把所有逻辑都放在main.c的while循环里,随着功能增加代码变得越来越难维护.后来参考分层思想,把GPIO操作封装到keyDriver,把事件逻辑封装到keyCtrl,把PWM控制封装到LedTwinkCtrl,每个模块只关心自己的职责,改起来就清晰多了.

软件定时器池使用用静态数组分配固定数量的定时器槽位,避免了动态内存分配,适合嵌入式环境.通过类型区分One-Shot和Periodic,复用了同一个管理逻辑.只对外暴露启动,停止接口, 以及回调函数中传入定时器权柄,从而区分是哪个定时器超时。隐藏内部实现

6.2遇到的困难

6.2.1 PWM启动后,没有输出PWM信号

原因是配置的定时器频率太高,而PWM周期又太低,且PWM的寄存器只有16位,从而出现配置值无法完全写入寄存器.因此无法正确驱动PWM.

附件下载
firmware.rar
团队介绍
业余电子爱好者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号