G0001配置定时器之非阻塞编程技巧
分享作者:F888
作者昵称:我爱小易
评测品牌:灵动微电子
评测型号:MM32G0001A1TC
发布时间:2025-10-15 11:16:59
2 1
前言
开源口碑分享内容
平常的开发中,我们经常常常用 循环等待某个操作或 阻塞试的delay()
函数实现延时,比如:
while (1) {
LED_ON;
delay(500ms);
LED_OFF;
delay(500ms);
}
while(按键弹起)
但这种方式会完全阻塞 CPU,导致系统无法响应其他事件(如串口指令、传感器数据、按键等)。
今天这个教程就是教大家来让cpu“同时做多件事” —— 这就是 非阻塞编程(Non-blocking Programming) 的意义。
今天,我们就以 MM32G0001(Mini-G0001) 为例,通过 定时器中断 + 状态机,实现一个 “按键切换LED闪烁频率” 的功能,彻底告别 delay()
!
一、项目需求
- 按下按键(KEY1,接 PA2),循环切换 LED 闪烁模式:
- 主循环 不能使用任何延时函数
- 按键需支持 防抖(本例通过定时器周期自然消抖)
二、核心思想:时间驱动 + 状态机
我们借助 TIM1 定时器,每 1ms 产生一次中断,在中断中:
- 累加全局时间计数器(
times
) - 调用按键与LED状态处理函数(
KEY_Main()
)
主循环可以做自己的事,真正做到 “非阻塞”。
三、硬件与初始化
1. 按键初始化(PA2,上拉输入)
void KEY_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 内部上拉
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2; // KEY1
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
按键按下时,PA2 为低电平(Bit_RESET)
2. 定时器 TIM1 初始化(1ms 中断)
系统由于时钟为 48MHz):
void TIMER1_INIT(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM1, ENABLE);
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_InitStruct.TIM_Prescaler = 48 - 1; // 48MHz / 48 = 1MHz → 1us/计数
TIM_InitStruct.TIM_Period = 1000 - 1; // 1000 * 1us = 1ms
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_Div1;
TIM_InitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_InitStruct);
// 配置 NVIC 中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM1_BRK_UP_TRG_COM_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM1, ENABLE);
}
四、非阻塞逻辑实现
1. 全局变量(记录状态)
static uint8_t key_now_num; // 当前按键状态
static uint8_t key_formerly_num; // 上次按键状态
static int times = 0; // 全局时间计数器(单位:1ms)
2. 按键扫描函数
#define KEY1_NUM 1
#define GET_KEY1 GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)
uint8_t Get_KEY_Num(void)
{
if (GET_KEY1 == Bit_RESET) return KEY1_NUM;
return 0;
}
3. 核心状态机:KEY_Main()
uint8_t KEY_Main(void)
{
static uint8_t i = 0; // LED 状态(0/1)
static uint8_t key_num_mode = 0; // 闪烁模式(0/1/2)
// 检测按键上升沿(按下瞬间)
key_formerly_num = key_now_num;
key_now_num = Get_KEY_Num();
if (key_formerly_num != key_now_num && key_now_num == KEY1_NUM)
{
key_num_mode = (key_num_mode + 1) % 3; // 切换模式
}
// 根据模式控制 LED 闪烁
switch (key_num_mode)
{
case 0: // 250ms
if (times % 250 == 0) {
GPIO_WriteBit(GPIOA, GPIO_Pin_11, (BitAction)(i & 0x01));
i = ~i;
}
break;
case 1: // 500ms
if (times % 500 == 0) {
GPIO_WriteBit(GPIOA, GPIO_Pin_11, (BitAction)(i & 0x01));
i = ~i;
}
break;
case 2: // 1000ms
if (times % 1000 == 0) {
GPIO_WriteBit(GPIOA, GPIO_Pin_11, (BitAction)(i & 0x01));
i = ~i;
}
break;
}
return key_num_mode;
}
4. 定时器中断服务函数
void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
{
KEY_Main(); // 处理按键与LED
times++; // 时间+1ms
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
}
}
五、结果
通过本项目,我们掌握了嵌入式开发中至关重要的 非阻塞编程思想:
用“时间标记”代替“时间等待”,用“状态机”代替“顺序执行”。
这是迈向 RTOS(实时操作系统) 或 事件驱动架构 的第一步。
全部评论

暂无评论

0/144