STM32 标准库 多任务非阻塞延时
做 STM32多任务项目时,最忌讳使用while(xxx)、delay()这种阻塞式延时。一旦调用阻塞延时,整个系统会卡死,无法同时处理按键、串口、电机闭环、视觉数据等并行任务。
本文基于STM32F10x 标准库,使用 TIM 定时器做 1ms 全局系统时基,封装一套通用非阻塞延时框架:
- 全局毫秒计数器,记录系统上电总运行时长;
- 独立延时句柄,每个任务单独占用一个延时结构体,互不干扰;
- 支持秒 / 毫秒 / 微秒三种计时单位切换;
- 自带参数边界校验、空指针保护,稳定性强;
整套代码分为 Timer.h 头文件 + Timer.c 源文件,可直接复制使用,更换 TIM 定时器只需修改少量宏。
整体设计思路:
1. TIM 定时器溢出中断:配置 1ms 中断,每次中断全局自增Timer_cnt,作为系统唯一时间基准;
2. 非阻塞延时原理:每个延时任务分配独立句柄,第一次调用记录起始时间戳,后续循环对比当前时间与起始时间差值,达到定时时长自动复位;
3. 多任务并行:多个延时结构体可同时创建,分别控制电机调速、传感器采样、串口上报,互不阻塞;
4. 单位可配置:通过宏切换 S/MS/US,自动计算最大、最小合法延时;
5. 安全防护:空指针判断、延时阈值限制、时间戳溢出天然兼容 u32 无符号减法特性。
#ifndef __TIMER_H__
#define __TIMER_H__
/********************** 头 文 件 包 含 ***********************/
#include "stm32f10x.h"
#include "stdio.h"
/*定时器时间单位(选一个,其它注释掉)*/
//#define TIMER_UNIT_S
#define TIMER_UNIT_MS
//#define TIMER_UNIT_US
/*******对单位的预处理,最终单位宏定义为 TIMER_Unit ************/
#ifdef TIMER_UNIT_S
#define TIMER_UNIT 1
#elif defined(TIMER_UNIT_MS)
#define TIMER_UNIT 1000
#elif defined(TIMER_UNIT_US)
#define TIMER_UNIT 1000000
#else
#define TIMER_UNIT 1000 //默认以毫秒为单位
#endif
#define TIMER_MIN_DELAY ((u32)TIMER_UNIT * ((u32)TIMER_ARR * TIMER_PSC) / TIMER_CLK)
#define TIMER_MAX_DELAY ((u32)TIMER_MAX_CNT * TIMER_UNIT)
/********** 基 本 参 数 的 宏 定 义(1ms) ***********/
#define TIMER_CLK 72000000//时钟源频率
#define TIMER_ARR 1000
#define TIMER_PSC 72
#define TIMER_TIMx TIM2
#define TIM_IRQn TIM2_IRQn//中断通道
#define TIMER_MAX_CNT 0xFFFFFFFF//Timer_cnt 的最大值(u32类型)
/******************枚 举 结 构 体 定 义***********************/
typedef struct
{
u32 start_tim; //起始时间戳
FlagStatus delay_flag; //定时标志位
}TIMER_Delay_HandleTypeDef;
typedef enum
{
TIMER_DELAY_ERR = 0, //错误
TIMER_DELAY_RUNNING, //运行中
TIMER_DELAY_COMPLETE //完成
}TIMER_Delay_StateTypedef;
/****************** 函 数 声 明 **************************/
void Timer_Init(void);
u32 Timer_GetCnt(void);
TIMER_Delay_StateTypedef Timer_delay(TIMER_Delay_HandleTypeDef *hdelay, u32 delay_tim);
void Timer_Delay_StructInit(TIMER_Delay_HandleTypeDef *hdelay);
#endif
#include "Timer.h"
static volatile u32 Timer_cnt;
/**
*@brief 定时器初始化
*@param 无
*@retval 无
*/
void Timer_Init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//**开启定时器2时钟
TIM_InternalClockConfig(TIMER_TIMx); //设置内部时钟为时钟源
TIM_TimeBaseInitTypeDef tim_init_struct;
tim_init_struct.TIM_ClockDivision = TIM_CKD_DIV1; //时钟1分频
tim_init_struct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
tim_init_struct.TIM_Period = TIMER_ARR - 1; //自动重装寄存器
tim_init_struct.TIM_Prescaler = TIMER_PSC - 1; //预分频器
tim_init_struct.TIM_RepetitionCounter = 0; //高级定时器才有用,默认0
TIM_TimeBaseInit(TIMER_TIMx, &tim_init_struct);
TIM_ITConfig(TIMER_TIMx, TIM_IT_Update, ENABLE);//使能定时器的溢出中断
TIM_Cmd(TIMER_TIMx, ENABLE); //开启定时器
NVIC_InitTypeDef nvic_init_srtuct;
nvic_init_srtuct.NVIC_IRQChannel = TIM_IRQn;//设置中断通道
nvic_init_srtuct.NVIC_IRQChannelCmd = ENABLE;
nvic_init_srtuct.NVIC_IRQChannelPreemptionPriority = 1; //主优先级
nvic_init_srtuct.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_Init(&nvic_init_srtuct);
}
/**
*@brief 定时器获取计数值
*@param 无
*@retval Timer_cnt
*/
u32 Timer_GetCnt()
{
return Timer_cnt;
}
/**
*@brief 非阻塞延迟函数
*@param hdelay:指向 TIMER_Delay_HandleTypeDef 结构体的指针
*@param delay_tim: 需要定时的时间
*@retval 返回状态,可以是 TIMER_DelayStateTypedef 枚举中的值:
* TIMER_DELAY_ERR TIMER_DELAY_RUNNING TIMER_DELAY_COMPLETE
*/
TIMER_Delay_StateTypedef Timer_delay(TIMER_Delay_HandleTypeDef *hdelay, u32 delay_tim)
{
/*判断是否为空指针*/
if(hdelay == NULL)
return TIMER_DELAY_ERR;
/*阈值判断,如果超出阈值返回错误*/
if(delay_tim < TIMER_MIN_DELAY || delay_tim >TIMER_MAX_DELAY)
return TIMER_DELAY_ERR;
if(hdelay->delay_flag == RESET)//检测是否第一次进入
{
hdelay->start_tim = Timer_cnt; //读取当前时间戳
hdelay->delay_flag = SET; //标志位置1
return TIMER_DELAY_RUNNING; //返回状态
}
else//第二次进入
{
if((u32)(Timer_cnt - hdelay->start_tim) >= delay_tim) //判断定时时间是否达到
{
hdelay->delay_flag = RESET; //标志位置0,方便下次使用
hdelay->start_tim = 0; //起始时间清零
return TIMER_DELAY_COMPLETE; //返回延迟完成状态
}
else
{
return TIMER_DELAY_RUNNING; //检测到还没有延迟完成
}
}
}
/**
*@brief Delay结构体初始化
*@param hdelay:指向TIMER_Delay_HandleTypeDef结构体的指针
*@retval 无
*/
void Timer_Delay_StructInit(TIMER_Delay_HandleTypeDef *hdelay)
{
if(hdelay != NULL)
{
hdelay->delay_flag = RESET; //标志位置0
hdelay->start_tim = 0; //开始时间清零
}
}
/*定时器中断函数*/
void TIM2_IRQHandler()
{
if(TIM_GetITStatus(TIMER_TIMx, TIM_IT_Update))
{
TIM_ClearITPendingBit(TIMER_TIMx, TIM_IT_Update); //清除中断挂起位
Timer_cnt ++;
}
}
核心原理讲
1. 1ms 时基计算验证
定时器时钟 72MHz,预分频 PSC=72,重装 ARR=1000 计数频率 = 72000000 / 72 = 1000000 Hz 溢出周期 = 1000000 / 1000 = 1000Hz,中断周期 1ms。
2. 无符号数溢出兼容
u32减法天然支持时间戳回绕: 当Timer_cnt从 0xFFFFFFFF 溢出到 0 时,当前计数值 - 起始值依然能算出正确间隔,不用额外判断溢出分支,代码简洁。
3. 多任务并行核心优势
阻塞延时只能串行执行;本框架每个任务创建独立句柄:
使用示例:
//示例:两个独立的延迟任务
// 定义两路独立延时句柄
TIMER_Delay_HandleTypeDef arm_delay; //机械臂关节运动定时
TIMER_Delay_HandleTypeDef print_delay;//串口数据打印定时
int main(void)
{
//系统初始化、外设初始化省略
Timer_Init();
Timer_Delay_StructInit(&arm_delay);
Timer_Delay_StructInit(&print_delay);
while(1)
{
//机械臂500ms执行一次姿态调整
if(Timer_delay(&arm_delay, 500) == TIMER_DELAY_COMPLETE)
{
Arm_Move_Adjust(); //机械臂运动函数
}
//串口1000ms打印一次位置信息
if(Timer_delay(&print_delay, 1000) == TIMER_DELAY_COMPLETE)
{
UART_Send_Arm_Pos();
}
//其他任务:按键检测、传感器读取、闭环PID
Key_Scan();
Sensor_Read();
}
}
这套定时器时基 + 非阻塞延时框架专为 STM32多并发嵌入式场景设计,相比普通阻塞延时有以下优势:
- 完全非阻塞,多任务并行调度,系统无卡顿;
- 单定时器提供全局统一时间戳,所有任务共用一套时基,时间同步;
- 单位可灵活切换,自动限制延时合法区间,鲁棒性高;
- 分层解耦,头文件集中配置,移植简单;
- 天然支持 u32 时间戳溢出,长时间运行无逻辑错误。
代码基于标准库编写,无需操作系统,裸机项目直接使用,是机械臂、小车、云台项目通用底层时间驱动。
如果有更好的方法,欢迎交流!!
更多推荐

所有评论(0)