做 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多并发嵌入式场景设计,相比普通阻塞延时有以下优势:

  1. 完全非阻塞,多任务并行调度,系统无卡顿;
  2. 单定时器提供全局统一时间戳,所有任务共用一套时基,时间同步;
  3. 单位可灵活切换,自动限制延时合法区间,鲁棒性高;
  4. 分层解耦,头文件集中配置,移植简单;
  5. 天然支持 u32 时间戳溢出,长时间运行无逻辑错误。

代码基于标准库编写,无需操作系统,裸机项目直接使用,是机械臂、小车、云台项目通用底层时间驱动。

如果有更好的方法,欢迎交流!!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐