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

相关推荐
QK_004 小时前
STM32-热敏传感器以及光敏传感器
stm32·单片机·嵌入式硬件
代码游侠5 小时前
复习——ARM Cortex-A 裸机开发深度解析
arm开发·笔记·嵌入式硬件·学习·架构
清风6666665 小时前
基于单片机的燃气热水器智能控制系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
youcans_6 小时前
【动手学STM32G4】(2)STM32G431之外部中断—按键控制
stm32·单片机·嵌入式硬件·外部中断
Smart-佀7 小时前
FPGA入门:CAN总线原理与Verilog代码详解
单片机·嵌入式硬件·物联网·算法·fpga开发
与光同尘 大道至简7 小时前
ESP32 小智 AI 机器人入门教程从原理到实现(自己云端部署)
人工智能·python·单片机·机器人·github·人机交互·visual studio
老李的森林8 小时前
嵌入式开发--无刷电机FOC控制--用定时器事件驱动ADC采样
stm32·单片机·嵌入式硬件·foc·无刷电机
一路往蓝-Anbo8 小时前
【第42期】调试进阶(一):IDE中的Register与Memory窗口
c语言·开发语言·ide·stm32·单片机·嵌入式硬件
boneStudent8 小时前
STM32工业HMI控制系统
stm32·单片机·嵌入式硬件