STM32微秒级延时函数
STM32中的微秒级延时函数主要用于需要的场景。它通过让CPU原地执行特定次数的空操作(或基于硬件定时器计数)来实现短暂的暂停,从而满足外设、通信协议或传感器对精确时序的要求。
STM32中的微秒级延时函数主要用于需要精确控制短时间间隔的场景。它通过让CPU原地执行特定次数的空操作(或基于硬件定时器计数)来实现短暂的暂停,从而满足外设、通信协议或传感器对精确时序的要求。
常见应用场景
1、通信协议驱动:
GPIO模拟通信:驱动I2C、SPI、1-Wire、UART(位碰撞)、WS2812(NeoPixel)等协议时,需要精确控制SCL/SDA的翻转时间、数据位的宽度、起始/停止条件的时间、复位脉冲的长度等。
2、传感器驱动:
初始化/复位脉冲:许多传感器(如DHT11/DHT22温湿度传感器、超声波测距模块HC-SR04的触发信号)需要特定宽度的启动或复位脉冲(通常是几十微秒)。
3、精确脉冲生成:
在GPIO上产生精确宽度的脉冲信号(例如,用于控制步进电机的步进脉冲、简单的PWM模拟、测试信号等),当所需脉冲宽度小于硬件定时器/PWM能方便设置的最小分辨率时,微秒延时结合GPIO翻转是常用方法。
实现方式
1、循环空指令实现:
固有缺陷
1、非线性误差:短延时误差大,长延时相对准确
2、阻塞式:延时期间CPU完全被占用
3、频率依赖:系统时钟变化时需重新校准
4、编译器依赖:不同编译器生成代码不同
5、不可中断:无法响应紧急事件
循环空指令的方式有很多,我给出我常用的一个
我测试发现可以满足大多数驱动程序,延时越长越精准,可能延时1微妙就误差特别大,慎用
#define LOOPS_PER_US 9 // 72MHz下典型值
void Delay_us(uint32_t us)
{
uint32_t count = us * LOOPS_PER_US;
while(count--);
}
2、使用通用定时器(如TIM2)——更高精度
误差分析:
1、函数调用开销:约0.1μs (72MHz下约7个时钟周期)
2、循环判断开销:约0.05μs
3、总误差:随延时时间增加基本保持恒定在0.1-0.2μs
具体配置:设置定时器每1us计数一次,以stm32f103c8t6演示
Clock Source: Internal Clock
Prescaler: 71 (72MHz / (71+1) = 1MHz → 1μs/计数)
Counter Mode: Up
Counter Period: 65535 (最大16位值)
Auto-reload preload: Enable
其他保持默认
注意定时器溢出问题,设置的时间既然能溢出也没必要使用微秒级。
void delay_us(uint32_t us)
{
__HAL_TIM_SET_COUNTER(&htim1, 0); // 重置计数器
HAL_TIM_Base_Start(&htim1); // 启动定时器
while (__HAL_TIM_GET_COUNTER(&htim1) < us); // 等待计数达到指定值
HAL_TIM_Base_Stop(&htim1); // 停止定时器
}
3、使用系统滴答定时器(SysTick)
如果不想占用硬件定时器,可以使用SysTick实现(精度稍低),像f103c8t6,只有四个定时器,资源有限,可以使用这种方式,虽然有缺点,但是影响都不大,这种方式比较推荐。
缺点:
阻塞式延时:占用CPU资源,期间无法执行其他任务。
中断干扰:若高优先级中断触发,可能导致延时误差增大。
24位计数器限制:最大延时受限于LOAD值(如72MHz时最长约186ms)。
低功耗模式失效:CPU休眠时SysTick停止,无法使用。
static uint32_t us_multiplier; // 每微秒的时钟周期数
// 初始化函数(必须在系统时钟配置后调用)
void delay_init(void) {
// 计算每微秒所需的时钟周期数
us_multiplier = SystemCoreClock / 1000000;
// 确保SysTick已经启用(CubeMX默认会配置)
if (!(SysTick->CTRL & SysTick_CTRL_ENABLE_Msk)) {
HAL_InitTick(0); // 初始化SysTick
}
}
// 微秒级延时函数
void delay_us(uint32_t us) {
if (us == 0) return;
uint32_t start_tick = SysTick->VAL;
uint32_t ticks_required = us * us_multiplier;
uint32_t elapsed_ticks;
while (1) {
uint32_t current_tick = SysTick->VAL;
// 处理计数器回绕(24位递减计数器)
if (current_tick > start_tick) {
elapsed_ticks = start_tick + (SysTick->LOAD - current_tick) + 1;
} else {
elapsed_ticks = start_tick - current_tick;
}
if (elapsed_ticks >= ticks_required) {
break;
}
}
}
4、使用DWT周期计数器实现高精度微秒延时
DWT(Data Watchpoint and Trace)是ARM Cortex-M系列处理器中的调试组件,其中的CYCCNT(Cycle Counter)寄存器提供了极其精确的计时功能,是STM32上实现高精度延时的最佳方案。
原理说明
DWT的CYCCNT是一个32位向上计数器,它在每个CPU时钟周期自动增加。在72MHz的STM32F103上:
-
1秒 = 72,000,000个周期
-
1微秒 = 72个周期
通过读取此计数器,我们可以精确测量代码执行时间,实现纳秒级延时精度。
方案优势
1、超高精度:
在72MHz下精度可达±0.014μs(约1个时钟周期)
实测误差小于0.05μs(50纳秒)
2、性能优异:
函数调用开销极小(约3个周期)
不占用任何硬件定时器资源
与SysTick、HAL_Delay完全兼容
3、自动适应:
使用SystemCoreClock自动适应不同时钟频率
在8MHz-72MHz范围内均可正常工作
// DWT初始化函数//先初始化才可以使用
void DWT_Init(void)
{
// 使能DWT访问权限
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
// 重置周期计数器
DWT->CYCCNT = 0;
// 使能周期计数器
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
// 微秒级延时函数
void Delay_us(uint32_t us)
{
// 计算需要的时钟周期数
uint32_t cycles = us * (SystemCoreClock / 1000000);
// 记录起始周期计数
uint32_t start = DWT->CYCCNT;
// 等待达到指定周期数
while ((DWT->CYCCNT - start) < cycles) {
// 空循环
}
}
更多推荐



所有评论(0)