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

相关推荐
sramdram8 小时前
MCU微控制器OA打印机方案
单片机·嵌入式硬件·mcu·mcu微控制器
天天爱吃肉82189 小时前
【电源拆解:跟着问答逐一认识开关适配器PCB元器件】
大数据·人工智能·python·功能测试·嵌入式硬件·汽车
科芯创展12 小时前
3.5A,60VIN,异步降压芯片,XZ1815
单片机·嵌入式硬件
茯苓gao12 小时前
嵌入式开发笔记:Qt信号槽机制深度解析——从原理到实战的全方位指南
开发语言·笔记·嵌入式硬件·qt·学习
浅止菌14 小时前
海思回归第二弹:瑞芯微的隐忧与对手的反击
嵌入式硬件
Kuakewei88814 小时前
PW2609A在2.6A负载下压差0.14V,温升较3A工况低11°C
单片机·嵌入式硬件
Zyed14 小时前
[FreeRTOS]Day7项目介绍
stm32·单片机·嵌入式硬件
xuxie9915 小时前
start7 电机闭环控制
单片机·嵌入式硬件
嵌入式-老费16 小时前
esp32开发与应用(播放wav音频文件)
嵌入式硬件
LCG元16 小时前
嵌入式开发踩坑排查实战:从 STM32 外设初始化到 Linux SPI 驱动的 15 类硬件疑难故障深度复盘
linux·stm32·单片机