STM32L475实现精度更好的delay函数

在单片机上, delay函数只能通过忙等的机制来实现延时, 与PC机所实现的真正意义上的休眠不是一个概念.

代码实现:

delay.h

c 复制代码
#ifndef __DELAY_H
#define __DELAY_H

#include "stm32l4xx_hal.h"

/************************** RTOS自动适配(通用逻辑) **************************/
/* 自动识别 RT-Thread / FreeRTOS / uCOS */
#if defined(__RTTHREAD__) || defined(FREE_RTOS) || defined(USE_FreeRTOS) || \
    defined(OS_CRITICAL_METHOD) || defined(CPU_CFG_CRITICAL_METHOD)
    #define RTOS_USING    1
#else
    #define RTOS_USING    0
#endif

/* 函数声明 */

/**
 * @brief  初始化DWT计数器并自动计算频率偏移
 */
void delay_init(void);

/**
 * @brief  微秒级延时
 * @param  nus: 延时微秒数 (最大支持约 53.6s @ 80MHz)
 */
void delay_us(uint32_t nus);

/**
 * @brief  毫秒级延时 (RTOS模式下会释放CPU)
 * @param  nms: 延时毫秒数
 */
void delay_ms(uint32_t nms);

/**
 * @brief  秒级延时 (建议大延时使用此函数)
 * @param  ns: 延时秒数
 */
void delay_s(uint32_t ns);

#endif /* __DELAY_H */

delay.c

c 复制代码
#include "delay.h"
#include <stdint.h>
#include "core_cm4.h"

static uint32_t s_ticks_per_us = 0;

/**
 * @brief  初始化DWT
 */
void delay_init(void)
{
    // 获取当前 CPU 主频 (例如 80000000 Hz)
    uint32_t cpu_freq = HAL_RCC_GetSysClockFreq();
    s_ticks_per_us = cpu_freq / 1000000;

    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;

    // 部分内核需要解锁 DWT 访问权限
    // DWT->LAR = 0xC5ACCE55;

    DWT->CYCCNT = 0;
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}

/**
 * @brief  微秒级延时
 */
void delay_us(uint32_t nus)
{
    if (nus == 0) return;

    uint32_t start = DWT->CYCCNT;
    uint32_t ticks = nus * s_ticks_per_us;

    // 利用无符号减法,完美处理 CYCCNT 溢出回到 0 的情况
    while ((DWT->CYCCNT - start) < ticks);
}

/**
 * @brief  毫秒级延时 (智能适配 RTOS 与中断)
 */
void delay_ms(uint32_t nms)
{
    if (nms == 0) return;

#if RTOS_USING == 1
    // 检查是否在中断中 或 调度器是否未运行
    if (__get_IPSR() != 0 || xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED)
    {
        for (uint32_t i = 0; i < nms; i++) delay_us(1000);
    }
    else
    {
        // 这里的 API 根据你的 RTOS 选择其一
        vTaskDelay(pdMS_TO_TICKS(nms));
    }
#else
    for (uint32_t i = 0; i < nms; i++) delay_us(1000);
#endif
}

/**
 * @brief  秒级延时
 */
void delay_s(uint32_t ns)
{
    while (ns--)
    {
        delay_ms(1000);
    }
}

为什么不用HAL库的HAL_Delay函数

因为HAL_Delay仅支持毫秒级别的延时, 并且依赖系统的滴答定时器SysTick.

当需要SPI/I2C时序, GPIO电平翻转, 传感器驱动等对微秒级时序要求严格的场景时, 需要延时微秒

代码逐行解释

1. 初始化打开内核跟踪, 启用DWT模块
复制代码
// 全局启用DWT和ITM功能
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;

对应文档 DDI0403E_Armv7-M Architecture Reference Manua.pdf

如下

复制代码
// DWT计数器清零
DWT->CYCCNT = 0;
// DWT计数器使能
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
2. 利用忙等实现延时
复制代码
uint32_t start = DWT->CYCCNT;
uint32_t ticks = nus * s_ticks_per_us;

// 利用无符号减法,完美处理 CYCCNT 溢出回到 0 的情况
while ((DWT->CYCCNT - start) < ticks);  通过不断查看计数器与初始值的差来确定是否结束等待

每1us的耗时确定逻辑如下:

复制代码
uint32_t cpu_freq = HAL_RCC_GetSysClockFreq();
s_ticks_per_us = cpu_freq / 1000000;

先获取SYSCLK主时钟频率, 因为1s=1000,000us, 所以, 主时钟频率÷1000,000就得到每1usCPU能够运行的次数, 我的是80MHz, 相当于每1us就会运算80次, 所以s_ticks_per_us = 80

然后DWT->CYCCNT是CPU运算一次就累加1, 所以DWT->CYCCNT累加80次就是精确的1us

相关推荐
时代的凡人1 小时前
生活中的恒流源和恒压源
嵌入式硬件
恒锐丰科技林技术员2 小时前
EG2181 半桥驱动芯片原理与应用解析
经验分享·嵌入式硬件·硬件工程
Jason_zhao_MR2 小时前
电力数据采集核心板选型指南
linux·嵌入式硬件·fpga开发·嵌入式
沐欣工作室_lvyiyi2 小时前
基于物联网的汽车智能座舱控制系统的设计和实现(论文+源码)
单片机·物联网·汽车·智能座舱·哈尔滨信息工程学院·黄河科技学院
创界工坊工作室2 小时前
21-基于单片机的智能窗帘与光照联动控制系统设计
stm32·单片机·嵌入式硬件·proteus
zhaoshuzhaoshu2 小时前
嵌入式系统看门狗详解——以 STM32 为例
stm32·嵌入式硬件
恒锐丰科技林技术员3 小时前
EG2113D 高压半桥驱动芯片:高压功率变换的实用驱动方案
经验分享·嵌入式硬件·硬件工程
石马马户3 小时前
传感器的中断 Latch、中断状态寄存器、中断读取与保持时间
stm32·单片机·嵌入式硬件
fly_sunnn13 小时前
STM32-GPIO
stm32·单片机·嵌入式硬件
宵时待雨17 小时前
STM32基础1:嵌入式历史与生态
stm32·单片机·嵌入式硬件