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

相关推荐
xyx-3v17 分钟前
RS485 RE、DE
单片机·嵌入式硬件
weixin_4324447642 分钟前
单片机 Flash 指定地址存储常量字符串调试笔记
笔记·单片机·嵌入式硬件
提灯春秋1 小时前
基于定时器中断的多任务轮询架构
单片机·嵌入式硬件·架构
jllllyuz1 小时前
ESP32开发-迷你掌上平衡车miniBot完整开发指南
单片机·嵌入式硬件
不爱吃大饼1 小时前
WeMos开发板
单片机·嵌入式硬件
三佛科技-134163842121 小时前
LP3799FAC/LP3799FBC--非标60W(24V2.5A)电源芯片恒压恒流方案分析(电路图,PCB设计)
嵌入式硬件·物联网·智能家居·pcb工艺
雅斯驰1 小时前
BMS、电机控制、医疗设备:ISO1540DR的I²C隔离应用版图
单片机·嵌入式硬件
xiangw@GZ1 小时前
芯片失效分析:EM电迁移导致的BUCK输出失效
嵌入式硬件
yuan199971 小时前
温度传感器VC++串口通信程序(与51单片机通讯)
stm32·单片机·嵌入式硬件
LCMICRO-133108477462 小时前
国产长芯微LDC4048完全P2P替代DAC128S085,是一款 8 通道、带输出放大器的数模转换器 (DAC)
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·数模转换器dac