系列文章目录
文章目录
前言
在做单片机项目时,延时是经常需要使用到的,在单片机中,有两种定时器,一种是systick定时器,一种是TIM定时器。在这里推荐使用TIM定时器作为系统延时,一方面是systick在RTOS操作系统中默认作为时基了。还有一方面是TIM更加稳定。
在大项目中,TIM2(或其他通用外设定时器)比 SysTick 更适合实现稳定的系统延时,核心原因是:
资源隔离:独立于内核和 RTOS,避免与系统核心功能(如任务调度)冲突;
灵活适配:支持长延时、多场景定时,时钟源选择多样,抗干扰能力强;
可靠兼容:中断机制完善,优先级可控,适配复杂系统的中断协同需求。
基于以上原因,本文使用TIM作为延时函数。
为了将来不更新延时驱动文件,我这里还是封装一下,将来只需要更改板子文件就行了,不需要再次修改延时驱动文件。
1 工程目录
2 软件编写
2.1 main.c
#include "board_config.h"
/************************************************
systick 定时器驱动
实验现象:LED1闪烁。
淘宝店铺:https://shop475501589.taobao.com/?spm=pc_detail.29232929/evo365560b447259.shop_block.dshopinfo.5dd97dd6JvMuG3
咸鱼店铺:https://www.goofish.com/personal?spm=a21ybx.item.itemHeader.1.c17a3da6hy8k28&userId=3890583014
哔哩哔哩:https://space.bilibili.com/482024430?spm_id_from=333.788.upinfo.detail.click
作者:胜磊电子
************************************************/
/************************************* 宏定义 *******************************************************/
/*********************************** 局部函数 *******************************************************/
/*
************************************************************
* 函数名称: main
*
* 函数功能:
*
* 入口参数: 无
*
* 返回参数: 0
*
* 说明:
************************************************************
*/
int main(void)
{
// 初始化所有外设
BOARD_InitAll();
while (1) {
// 闪烁LED1
LED_Toggle(&BOARD_LED3);
DelayMs(500);
}
}
2.2 board_config
2.2.1 board_config.c
#include "board_config.h"
/************************************************
淘宝店铺:https://shop475501589.taobao.com/?spm=pc_detail.29232929/evo365560b447259.shop_block.dshopinfo.5dd97dd6JvMuG3
咸鱼店铺:https://www.goofish.com/personal?spm=a21ybx.item.itemHeader.1.c17a3da6hy8k28&userId=3890583014
哔哩哔哩:https://space.bilibili.com/482024430?spm_id_from=333.788.upinfo.detail.click
作者:胜磊电子
************************************************/
// 定义LED对象
LED_TypeDef BOARD_LED1;
LED_TypeDef BOARD_LED2;
LED_TypeDef BOARD_LED3;
// 定义蜂鸣器对象
Beep_TypeDef BOARD_BEEP;
void BOARD_InitLEDs(void) {
// 初始化LED1 (PB0)
LED_Init(&BOARD_LED1, GPIOC, GPIO_Pin_0);
// 初始化LED2 (PB1)
LED_Init(&BOARD_LED2, GPIOB, GPIO_Pin_1);
}
void BOARD_InitBeep(void) {
// 初始化蜂鸣器 (PB8)
Beep_Init(&BOARD_BEEP, GPIOB, GPIO_Pin_8);
}
void BOARD_InitDelay(uint8_t timer) {
// 初始化延时模块,可选择TIM2/TIM3等
Delay_Init(timer);
}
void BOARD_InitAll(void) {
// 初始化所有外设,使用TIM2作为延时定时器
BOARD_InitLEDs();
BOARD_InitBeep();
BOARD_InitDelay(1);
}
2.2.2 board_config.h
#ifndef BOARD_CONFIG_H
#define BOARD_CONFIG_H
#include "led_driver.h"
#include "beep_driver.h"
#include "delay.h"
/************************************************
淘宝店铺:https://shop475501589.taobao.com/?spm=pc_detail.29232929/evo365560b447259.shop_block.dshopinfo.5dd97dd6JvMuG3
咸鱼店铺:https://www.goofish.com/personal?spm=a21ybx.item.itemHeader.1.c17a3da6hy8k28&userId=3890583014
哔哩哔哩:https://space.bilibili.com/482024430?spm_id_from=333.788.upinfo.detail.click
作者:胜磊电子
************************************************/
// 导出LED对象供外部使用
extern LED_TypeDef BOARD_LED1;
extern LED_TypeDef BOARD_LED2;
extern LED_TypeDef BOARD_LED3;
// 导出蜂鸣器对象供外部使用
extern Beep_TypeDef BOARD_BEEP;
// 板子初始化函数
void BOARD_InitAll(void);
void BOARD_InitLEDs(void);
void BOARD_InitBeep(void);
void BOARD_InitDelay(uint8_t timer);
#endif /* BOARD_CONFIG_H */
2.3 delay
2.3.1 delay.c
#include "delay.h"
/************************************************
淘宝店铺:https://shop475501589.taobao.com/?spm=pc_detail.29232929/evo365560b447259.shop_block.dshopinfo.5dd97dd6JvMuG3
咸鱼店铺:https://www.goofish.com/personal?spm=a21ybx.item.itemHeader.1.c17a3da6hy8k28&userId=3890583014
哔哩哔哩:https://space.bilibili.com/482024430?spm_id_from=333.788.upinfo.detail.click
作者:胜磊电子
************************************************/
/** 系统支持的所有定时器配置表 */
const Delay_TimerConfigTypeDef TimerConfigTable[] = {
{TIM1, RCC_APB2Periph_TIM1, RCC_APB2Periph_TIM1, RCC_APB2PeriphClockCmd},
{TIM2, RCC_APB1Periph_TIM2, RCC_APB1Periph_TIM2, RCC_APB1PeriphClockCmd},
{TIM3, RCC_APB1Periph_TIM3, RCC_APB1Periph_TIM3, RCC_APB1PeriphClockCmd},
{TIM4, RCC_APB1Periph_TIM4, RCC_APB1Periph_TIM4, RCC_APB1PeriphClockCmd},
{TIM5, RCC_APB1Periph_TIM5, RCC_APB1Periph_TIM5, RCC_APB1PeriphClockCmd},
{TIM6, RCC_APB1Periph_TIM6, RCC_APB1Periph_TIM6, RCC_APB1PeriphClockCmd},
{TIM7, RCC_APB1Periph_TIM7, RCC_APB1Periph_TIM7, RCC_APB1PeriphClockCmd},
{TIM8, RCC_APB2Periph_TIM8, RCC_APB2Periph_TIM8, RCC_APB2PeriphClockCmd},
{TIM9, RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM9, RCC_APB2PeriphClockCmd},
{TIM10, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM10, RCC_APB2PeriphClockCmd},
{TIM11, RCC_APB2Periph_TIM11, RCC_APB2Periph_TIM11, RCC_APB2PeriphClockCmd},
{TIM12, RCC_APB1Periph_TIM12, RCC_APB1Periph_TIM12, RCC_APB1PeriphClockCmd},
{TIM13, RCC_APB1Periph_TIM13, RCC_APB1Periph_TIM13, RCC_APB1PeriphClockCmd},
{TIM14, RCC_APB1Periph_TIM14, RCC_APB1Periph_TIM14, RCC_APB1PeriphClockCmd},
};
/** 定时器配置表大小 */
const uint8_t TimerConfigTableSize = sizeof(TimerConfigTable) / sizeof(TimerConfigTable[0]);
/** 当前使用的定时器索引 */
static uint8_t currentTimerIndex = 0;
/**
* @brief 初始化定时器,用于延时功能
* @param timerIndex: 定时器在配置表中的索引
* @retval 无
*/
void Delay_Init(uint8_t timerIndex)
{
if (timerIndex >= TimerConfigTableSize) {
// 索引超出范围,使用默认定时器
timerIndex = 0;
}
currentTimerIndex = timerIndex;
const Delay_TimerConfigTypeDef* config = &TimerConfigTable[timerIndex];
// 使能定时器时钟
config->CLKCmd(config->TIMx_CLK, ENABLE);
// 停止定时器
TIM_Cmd(config->TIMx, DISABLE);
// 配置定时器为向上计数模式
TIM_InternalClockConfig(config->TIMx);
TIM_CounterModeConfig(config->TIMx, TIM_CounterMode_Up);
// 设置 PSC 为 71,使计数器时钟为 1MHz (72MHz / 72 = 1MHz)
TIM_PrescalerConfig(config->TIMx, 71, TIM_PSCReloadMode_Immediate);
// 清除更新标志位
TIM_ClearFlag(config->TIMx, TIM_FLAG_Update);
}
/**
* @brief 微秒级延时函数
* @param us: 延时的微秒数,范围 0 - 65535
* @retval 无
*/
void DelayUs(uint16_t us)
{
if (us == 0) return;
const Delay_TimerConfigTypeDef* config = &TimerConfigTable[currentTimerIndex];
// 设置自动重载值
TIM_SetAutoreload(config->TIMx, us);
// 清零计数器
TIM_SetCounter(config->TIMx, 0);
// 清除更新标志位
TIM_ClearFlag(config->TIMx, TIM_FLAG_Update);
// 启动定时器
TIM_Cmd(config->TIMx, ENABLE);
// 等待更新标志位被置位
while (!TIM_GetFlagStatus(config->TIMx, TIM_FLAG_Update));
// 停止定时器
TIM_Cmd(config->TIMx, DISABLE);
// 清除更新标志位
TIM_ClearFlag(config->TIMx, TIM_FLAG_Update);
}
/**
* @brief 毫秒级延时函数
* @param ms: 延时的毫秒数
* @retval 无
*/
void DelayMs(uint16_t ms)
{
for (uint16_t i = 0; i < ms; i++)
{
DelayUs(1000); // 循环调用 1000 微秒延时实现 1 毫秒延时
}
}
/**
* @brief 获取当前使用的定时器索引
* @retval 当前使用的定时器在配置表中的索引
*/
uint8_t Delay_GetCurrentTimerIndex(void)
{
return currentTimerIndex;
}
2.3.2 delay.h
#ifndef DELAY_H
#define DELAY_H
#include "system_config.h"
/************************************************
淘宝店铺:https://shop475501589.taobao.com/?spm=pc_detail.29232929/evo365560b447259.shop_block.dshopinfo.5dd97dd6JvMuG3
咸鱼店铺:https://www.goofish.com/personal?spm=a21ybx.item.itemHeader.1.c17a3da6hy8k28&userId=3890583014
哔哩哔哩:https://space.bilibili.com/482024430?spm_id_from=333.788.upinfo.detail.click
作者:胜磊电子
************************************************/
/** 定时器配置结构体 */
typedef struct {
TIM_TypeDef* TIMx; // 定时器指针
uint32_t TIMx_CLK; // 定时器时钟
uint32_t APBxPeriph_CLKCmd; // 时钟使能函数参数
void (*CLKCmd)(uint32_t, FunctionalState); // 时钟使能函数指针
} Delay_TimerConfigTypeDef;
/** 系统支持的所有定时器配置表 */
extern const Delay_TimerConfigTypeDef TimerConfigTable[];
extern const uint8_t TimerConfigTableSize;
/** 延时函数初始化和操作 */
void Delay_Init(uint8_t timerIndex);
void DelayUs(uint16_t us);
void DelayMs(uint16_t ms);
/** 获取当前使用的定时器索引 */
uint8_t Delay_GetCurrentTimerIndex(void);
#endif /* DELAY_H */
2.4 led_driver
2.4.1 led_driver.c
#include "led_driver.h"
/************************************************
LED灯驱动
淘宝店铺:https://shop475501589.taobao.com/?spm=pc_detail.29232929/evo365560b447259.shop_block.dshopinfo.5dd97dd6JvMuG3
咸鱼店铺:https://www.goofish.com/personal?spm=a21ybx.item.itemHeader.1.c17a3da6hy8k28&userId=3890583014
哔哩哔哩:https://space.bilibili.com/482024430?spm_id_from=333.788.upinfo.detail.click
作者:胜磊电子
************************************************/
/**
* @brief 初始化LED
* @param led: LED结构体指针
* @param GPIOx: GPIO端口
* @param GPIO_Pin: GPIO引脚
* @retval 无
*/
void LED_Init(LED_TypeDef* led, GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) {
GPIO_InitTypeDef GPIO_InitStructure;
uint32_t RCC_APB2Periph_GPIOx = 0;
// 根据GPIO端口确定RCC时钟
if (GPIOx == GPIOA) {
RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOA;
} else if (GPIOx == GPIOB) {
RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOB;
} else if (GPIOx == GPIOC) {
RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOC;
} else if (GPIOx == GPIOD) {
RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOD;
} else if (GPIOx == GPIOE) {
RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOE;
}
// 使能GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);
// 配置GPIO为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOx, &GPIO_InitStructure);
// 初始化LED结构体
led->GPIOx = GPIOx;
led->GPIO_Pin = GPIO_Pin;
led->state = LED_OFF;
// 默认关闭LED
GPIO_SetBits(GPIOx, GPIO_Pin);
}
/**
* @brief 打开LED
* @param led: LED结构体指针
* @retval 无
*/
void LED_On(LED_TypeDef* led) {
GPIO_ResetBits(led->GPIOx, led->GPIO_Pin);
led->state = LED_ON;
}
/**
* @brief 关闭LED
* @param led: LED结构体指针
* @retval 无
*/
void LED_Off(LED_TypeDef* led) {
GPIO_SetBits(led->GPIOx, led->GPIO_Pin);
led->state = LED_OFF;
}
/**
* @brief 切换LED状态
* @param led: LED结构体指针
* @retval 无
*/
void LED_Toggle(LED_TypeDef* led) {
if (led->state == LED_ON) {
LED_Off(led);
} else {
LED_On(led);
}
}
/**
* @brief 获取LED状态
* @param led: LED结构体指针
* @retval LED状态
*/
LED_State LED_GetState(LED_TypeDef* led) {
return led->state;
}
2.4.2 led_driver.h
#ifndef LED_DRIVER_H
#define LED_DRIVER_H
#include "system_config.h"
/************************************************
LED灯驱动
淘宝店铺:https://shop475501589.taobao.com/?spm=pc_detail.29232929/evo365560b447259.shop_block.dshopinfo.5dd97dd6JvMuG3
咸鱼店铺:https://www.goofish.com/personal?spm=a21ybx.item.itemHeader.1.c17a3da6hy8k28&userId=3890583014
哔哩哔哩:https://space.bilibili.com/482024430?spm_id_from=333.788.upinfo.detail.click
作者:胜磊电子
************************************************/
// LED状态枚举
typedef enum {
LED_OFF = 0,
LED_ON = 1
} LED_State;
// LED结构体定义
typedef struct {
GPIO_TypeDef* GPIOx; // GPIO端口
uint16_t GPIO_Pin; // GPIO引脚
LED_State state; // LED当前状态
} LED_TypeDef;
// 函数声明
void LED_Init(LED_TypeDef* led, GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void LED_On(LED_TypeDef* led);
void LED_Off(LED_TypeDef* led);
void LED_Toggle(LED_TypeDef* led);
LED_State LED_GetState(LED_TypeDef* led);
#endif /* LED_DRIVER_H */
3 延时验证
延时500ms,使用逻辑分析仪采集到的:
虽然赶不上systick的精度,但是稳定性是极大的提高了。
总结
通过上面这样设置,就可以使用延时函数进行系统延时了,可以随意使用延时所用的定时器,不会和其他资源冲突。