一、SysTick方案

ARM在Cortex-M0/M3/M4/M7等内核中统一将SysTick设计为24位,这是架构义的标准行为。

核心机制:

  1. 利用Cortex-M内置的24位系统定时器(SysTick)
  2. 通过捕获VAL寄存器的变化值累计周期数
  3. 自动处理计数器重装载(RELOAD)时的翻转

相关寄存器:

SysTick->LOAD

  • 功能:它是一个 24 位的重装载寄存器,用于设置 SysTick 定时器的计数值。当 SysTick 计数器(SysTick->VAL)递减到 0 时,SysTick 会触发一个中断(SysTick 异常),然后自动重新加载这个寄存器中设置的值,继续计数。

  • 作用:通过配置这个寄存器,可以决定 SysTick 定时器的计时周期。简单的说,设置SysTick->LOAD的值决定了定时时间。

  • 设置示例:如果系统时钟频率(SystemCoreClock)是72 MHz,并且希望SysTick定时器每1毫秒触发一次中断,那么SysTick->LOAD的值可以设置为72000-1(因为72 MHz对应每秒有72,000,000 个时钟周期,1毫秒即需要72,000 个周期,但LOAD寄存器的值是从0开始计数的,故减1)。

SysTick->VAL

  • 功能:它是一个24位的当前计数值寄存器,表示SysTick定时器当前的计数值。这个寄存器的值会随着SysTick定时器的运行而递减。

  • 作用:用于实时监测SysTick定时器的当前计数值。在编程中,可以通过读取这个寄存器的值来获取当前SysTick定时器的状态,或者在延时函数中使用它来实现精确的延时。

  • 使用示例:在延时函数中,可以通过监测SysTick->VAL的变化来实现延时功能。例如,在一个延时函数中,可以记录初始的SysTick->VAL值,然后循环检测当前的SysTick->VAL值,直到两者之差达到所需的延时周期。

代码要求:SystemCoreClock 的值必须是 1 MHz 的整数倍,以确保 ticks 计算的准确性。

// C库文件
#include <stdint.h>
// HAL库文件
#include "stm32f4xx.h"
#include "core_cm4.h"
// 自定义文件
#include "usDelay_SysTick.h"

// 微秒级延时函数
void delay_us(uint32_t nus) {
    uint32_t ticks;
    uint32_t told, tnow, tcnt = 0;
    uint32_t reload = SysTick->LOAD; // 读取SysTick的重装载值

    ticks = nus * (SystemCoreClock / 1000000); // 计算需要的计数值
    told = SysTick->VAL; // 获取当前的计数值(起始时间)

    while (1) {
        tnow = SysTick->VAL; // 获取当前的计数值(当前时间)

        if (tnow != told) { // 如果计数值发生变化
            if (tnow < told) { // 如果计数溢出(从0开始递减到最大值)
                tcnt += told - tnow; // 计算已经过去的时钟周期数
            } else {
                tcnt += reload - tnow + told; // 计算已经过去的时钟周期数(考虑溢出)
            }
            told = tnow; // 更新起始时间

            if (tcnt >= ticks) { // 如果已经过去的时钟周期数达到所需延时
                break; // 退出循环,完成延时
            }
        }
    }
}

二、 DWT方案

DWT(Data Watchpoint and Trace)单元的32位计数器(CYCCNT)设计为全字长(32位)是ARM Cortex-M架构的深度优化决策

核心机制:

  1. 使用数据观察点跟踪单元(DWT)的32位周期计数器(CYCCNT)
  2. 计数器每个CPU时钟周期自动递增
  3. 通过计算起止周期差实现延时
// C库文件
#include <stdint.h>
// HAL库文件
#include "stm32f4xx.h"
#include "core_cm4.h"
// 自定义文件
#include "DWT_Delay.h"

/**
 *  #define DWT_CR *(volatile uint32_t *)0xE0001000     // DWT->CTRL;
 *  #define DWT_CYCCNT *(volatile uint32_t *)0xE0001004 // DWT->CYCCNT;
 *  #define DEM_CR *(volatile uint32_t *)0xE000EDFC     // CoreDebug->DEMCR;
 *
 *  DWT->CYCCNT;      // 0xE0001000UL + 0x004 = 0xE0001004
 *  DWT->CTRL;        // 0xE0001000UL + 0x000 = 0xE0001000
 *  CoreDebug->DEMCR; // 0xE000EDF0UL + 0x00C = 0xE000EDFC
 */

// 初始化DWT单元
void InitDWT(void)
{
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT->CYCCNT = 0;
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}

// 微秒级延时函数
void DelayUS(uint32_t us)
{
    uint32_t start = DWT->CYCCNT;
    uint32_t cycles = us * (SystemCoreClock / 1000000);
    while ((DWT->CYCCNT - start) < cycles)
        ;
}

三、两种方案的对比

1、实现细节对比

特性

SysTick方案

DWT方案

寄存器位宽

24位(最大0xFFFFFF)

32位(最大0xFFFFFFFF)

时钟源

可配置为内核时钟或外部时钟

固定为内核时钟

初始化要求

需配置RELOAD值

需启用DWT和CYCCNT

中断影响

可能被SysTick中断打断

完全不受中断影响

多任务兼容性

与RTOS冲突(如FreeRTOS占用SysTick)

可与其他系统共存

最小分辨率

1/SystemCoreClock

1/SystemCoreClock

2、CPU占用率:两种方案均为忙等待(busy-wait)模式,CPU利用率100%

3、应用场景推荐

优先选择SysTick的情况:

  1. 系统已使用DWT做调试跟踪
  2. 需要兼容没有DWT的Cortex-M0/M3芯片
  3. 项目对代码体积敏感(DWT需额外初始化)

优先选择DWT的情况:

  1. 需要超过200ms的长延时
  2. RTOS已占用SysTick
  3. 高精度实时控制(如PWM波形生成)

四、DeepSeek方案处理

1.定义关键变量

在stm32f1xx_it.c中,添加下面代码

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint64_t system_time_us = 0;
volatile uint32_t systick_overflow_count = 0;
/* USER CODE END PV */

/******************************************************************************/
/*           Cortex-M3 Processor Interruption and Exception Handlers          */
/******************************************************************************/

/**
 * @brief This function handles System tick timer.
 */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
  system_time_us += 1000;   // 1ms递增
  systick_overflow_count++; // 记录溢出次数
  /* USER CODE END SysTick_IRQn 1 */
}

1. 精确微秒延时实现

void delay_us(uint32_t us)
{
    if (us == 0)
        return;

    uint32_t start = SysTick->VAL;
    uint32_t ticks = us * (SystemCoreClock / 1000000);
    uint32_t elapsed = 0;

    while (elapsed < ticks)
    {
        uint32_t current = SysTick->VAL;

        if (current < start)
        {
            elapsed += start - current;
        }
        else
        {
            elapsed += start + (SysTick->LOAD - current);
        }
        start = current;
    }
}

2. 系统时间戳获取

#include "stm32f1xx.h"

/* stm32f1xx_if.c文件的外部变量 - 不可以初始化 */
extern volatile uint64_t system_time_us;
extern volatile uint32_t systick_overflow_count;

uint64_t get_system_time_us(void)
{
    uint32_t val, overflow;

    do
    {
        overflow = systick_overflow_count;
        val = SysTick->VAL;
        // 内存屏障确保顺序读取
        __DSB();
    } while (overflow != systick_overflow_count);

    // 计算当前周期内的时间
    uint32_t elapsed_ticks = SysTick->LOAD - val;
    uint64_t current_us = (uint64_t)elapsed_ticks * 1000000 / SystemCoreClock;

    // 总时间 = 完整溢出周期 + 当前周期时间
    return (overflow * 1000) + current_us;
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐