STM32软件微秒延时方案
使用SysTick和DWT两种不同的方法,解决STM32MCU的微秒延时问题。
一、SysTick方案
ARM在Cortex-M0/M3/M4/M7等内核中统一将SysTick设计为24位,这是架构义的标准行为。
核心机制:
- 利用Cortex-M内置的24位系统定时器(SysTick)
- 通过捕获VAL寄存器的变化值累计周期数
- 自动处理计数器重装载(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架构的深度优化决策
核心机制:
- 使用数据观察点跟踪单元(DWT)的32位周期计数器(CYCCNT)
- 计数器每个CPU时钟周期自动递增
- 通过计算起止周期差实现延时
// 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的情况:
- 系统已使用DWT做调试跟踪
- 需要兼容没有DWT的Cortex-M0/M3芯片
- 项目对代码体积敏感(DWT需额外初始化)
优先选择DWT的情况:
- 需要超过200ms的长延时
- RTOS已占用SysTick
- 高精度实时控制(如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;
}
更多推荐




所有评论(0)