1.任务
我选择的活动任务是任务1:按键控制LED闪烁
1.1任务需求
- 正确初始化 GPIO 中断
- LED 控制逻辑清晰,不出现误触发
- 闪烁频率约 1Hz
- 短按:LED 闪烁 开启/关闭
- 长按:连续调节闪烁频率
- 频率从"1Hz → 5Hz"线性变化
- 当到达最快后,保持长按可实现"5Hz → 1Hz"反向变化
- 按上面的逻辑循环
- 频率变化过程应平滑(无明显跳变)
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
实现大概流程:

2.2硬件思路

查看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.