3 STM32单片机-delay延时驱动

发布于:2025-07-10 ⋅ 阅读:(25) ⋅ 点赞:(0)

系列文章目录



前言

在做单片机项目时,延时是经常需要使用到的,在单片机中,有两种定时器,一种是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的精度,但是稳定性是极大的提高了。


总结

通过上面这样设置,就可以使用延时函数进行系统延时了,可以随意使用延时所用的定时器,不会和其他资源冲突。


网站公告

今日签到

点亮在社区的每一天
去签到