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

相关推荐
czhaii41 分钟前
STC AI8052U单片机特点
单片机
MAR-Sky1 小时前
keil5中数据的不同定义和单片机(以stc8为例)里的对应关系(idata,xdata,data,code)
单片机·嵌入式硬件
项目題供诗3 小时前
51单片机入门(八)
单片机·嵌入式硬件·51单片机
羽获飞3 小时前
从零开始学嵌入式之STM32——9.STM32的时钟系统
stm32·单片机·嵌入式硬件
飞睿科技4 小时前
乐鑫智能开关方案解析:基于ESP32-C系列的低功耗、高集成设计
嵌入式硬件·物联网·esp32·智能家居·乐鑫科技
来自晴朗的明天5 小时前
13、NMOS 电源防反接电路
单片机·嵌入式硬件·硬件工程
17(无规则自律)5 小时前
深入浅出 Linux 内核模块,写一个内核版的 Hello World
linux·arm开发·嵌入式硬件
芯岭技术5 小时前
PY32MD310单片机:高性能、低功耗的32位电机控制微控制器
单片机·嵌入式硬件
wotaifuzao6 小时前
STM32 + FreeRTOS 的订阅通知组件架构
stm32·嵌入式硬件·架构·freertos·事件驱动·嵌入式架构
小龙报7 小时前
【51单片机】深度解析 51 串口 UART:原理、配置、收发实现与工程化应用全总结
c语言·开发语言·c++·stm32·单片机·嵌入式硬件·51单片机