Stm32F407基于标准库的systick实现微秒级延迟函数

使用 ​​SysTick 定时器(系统滴答定时器)​​ 实现微秒级延时

SysTick​​ 是 Cortex-M 内核自带的 ​​24 位定时器​​,运行在 ​​系统主频(如 84MHz / 168MHz)​​ 上,非常适合用来实现延时函数:

1. 确定系统时钟频率(SYSCLK)

2. SysTick 基本公式

  • ​1 微秒(μs) = 1 / (SYSCLK_MHz) 秒​

  • 例如F407系统最大时钟频率为168MHz,以系统时钟为计数时钟频率,每计数168次为1微秒。

3. SysTick寄存器

 因为标准库没有直接提供SysTick操作的函数,所以需要操作对应的寄存器。SysTick 定时器由以下 ​​4 个内核寄存器​​ 组成,定义在 ​​Cortex-M 内核的 System Control Block (SCB)​​ 中:

寄存器名称

寄存器全称

位宽

说明

​CTRL​

SysTick Control and Status Register

32 bit

​控制与状态寄存器​​:使能、时钟源、中断使能、计数状态

​LOAD​

SysTick Reload Value Register

32 bit

​重装载值寄存器​​:设定定时周期

​VAL​

SysTick Current Value Register

32 bit

​当前计数值寄存器​​:读取当前倒计时值,可手动清零

​CALIB​

SysTick Calibration Value Register

32 bit

​校准值寄存器​​(一般用不到,由芯片厂商预配置)

4. 延时函数实现方式1,轮询检查CTRL寄存器的标志位

void Systick_Delay_us(uint16_t us)
{
	uint16_t i = 0;
	SysTick->LOAD = SystemCoreClock / 8000000; 					// 8分频的情况下 1us需要计数的次数
	SysTick->VAL = 0;											// 清空计数值
	SysTick->CTRL &= (~SysTick_CTRL_COUNTFLAG_Msk); 			// 清除标志位
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; 					// 使能
	SysTick->CTRL &= (~SysTick_CTRL_TICKINT_Msk); 				// 不开启中断
	SysTick->CTRL &= (~SysTick_CTRL_CLKSOURCE_Msk); 			// 8分频
	for (i = 0; i < us; i++) 									// 每次等待1us
	{
		while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);	// 等待计数完成
		SysTick->CTRL &= (~SysTick_CTRL_COUNTFLAG_Msk); 			// 清除标志位
	}
	SysTick->CTRL &= (~SysTick_CTRL_ENABLE_Msk);				// 关闭计数器
}
实现原理:计算出1us需要计数的次数 然后×要等待的us数,开启定时器检查标志位,等到计数完成后结束循环,关闭计时器。
问题:如果Systick_Delay_us函数在运行中被中断,中断函数又调用了Systick_Delay_us函数,那么中断程序结束后,定时器将会被关掉,导致CTRL的标志位永远无法为1,延时函数直接会进入死循环。
解决方法:while循环可以多加入一个判断条件 :
SysTick->CTRL & SysTick_CTRL_ENABLE_Msk == 1
但是依然会丢掉上一次计数的过程,而且要加上中断程序处理的时间,导致计数值不准确。
所以如果中断程序中不使用Systick_Delay_us函数,这种方案是可行的。 

5. 延时函数实现方式2,使用Systick的中断函数,创建一个全局变量,每us计数一次。

static uint32_t count = 0;
void SysTick_Init()
{
	NVIC_InitTypeDef NVIC_InitStruct;
	SysTick->LOAD = SystemCoreClock / 8000000; 		// 1us完成一次计数并产生中断
	SysTick->VAL = 0;
	SysTick->CTRL &= (~SysTick_CTRL_COUNTFLAG_Msk); // 清除标志位
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; 		// 使能
	SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; 		// 开启中断
	SysTick->CTRL &= (~SysTick_CTRL_CLKSOURCE_Msk); // 设置8分频
	/* NVIC设置Systick中断优先级为最高 */
	NVIC_InitStruct.NVIC_IRQChannel = SysTick_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStruct);
}

void SysTick_Handler(void)
{
	++count;
}

void Systick_Delay_us(u16 us)
{
	u32 tmp = count;
	if (0xFFFFFFFF - tmp <= us) // 数据会溢出 从0开始计数
	{
		count = 0;
		tmp = 0;
	}
	while (count - tmp <= us); // 等待计数差大于要等待的时间
}

void Systick_Delay_ms(u16 ms)
{
	u16 i = 0;
	for(i = 0; i < ms; i++)
	{
		Systick_Delay_us(1000);
	}
}
这种方式要注意的是设置Systick的中断优先级为最高,否则可能其他中断中使用此延时函数时,Systick中断无法执行,计数值无法增加,会导致程序在while (count - tmp <= us);进入死循环。
另外要注意配置NVIC的中断管理组,让Systick的中断抢占优先级生效:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
<think>嗯,用户想基于STM32F051的标准外设库和SysTick定时器实现微秒级的延时函数。我需要先回忆一下SysTick的相关知识。SysTick是Cortex-M0内核的一个定时器,通常用于操作系统的时钟节拍,但也可以用来做精确延时。 首先,用户提到的标准外设库,可能是指ST的旧版库,因为现在推荐使用HAL库,但很多项目还在用标准库SysTick的配置需要设置重装载值,并启动定时器。SysTick的时钟源一般是系统时钟,比如STM32F051的主频假设是48MHz,那SysTick的每个计数周期是1/48MHz ≈ 20.83ns。 不过,SysTick是24位的,所以最大计数值是0xFFFFFF。微秒级延时的话,比如1us,需要计数值是48(如果系统时钟是48MHz的话)。但直接使用SysTick可能会遇到重装载的问题,因为SysTick的重装载寄存器是24位的,所以需要计算合适的值。 另外,用户可能需要考虑中断的问题。通常,SysTick的中断用于系统节拍,如果用户启用了FreeRTOS之类的操作系统,SysTick可能已经被占用。这时候需要避免冲突,或者使用其他定时器。 但是用户的问题中没有提到操作系统,所以可能假设是裸机编程。那步骤应该是:配置SysTick的时钟源,设置重装载值,然后进行计数。需要注意的是,SysTick在计数到0时会重新加载重装载值,所以需要根据延时时间计算正确的计数值。 例如,对于delay_us函数,假设系统时钟是48MHz,1us需要48个周期。所以每次延时1us时,设置SysTick的LOAD为48-1(因为从0开始计数)。然后启动定时器,等待计数到0。不过需要注意的是,在循环中可能需要多次加载,特别是在延时较长时间的时候。 但SysTick的校准值寄存器可能影响计数,不过通常可以忽略,直接使用系统时钟频率进行计算。另外,标准库中可能提供了SysTick的配置函数,比如SysTick_Config(),这个函数会配置重装载值并启动定时器。但需要检查这个函数是否适用于微秒级延时,因为通常SysTick用于毫秒级延时。 比如,SysTick_Config(SystemCoreClock / 1000)会产生1ms的中断。但用户需要的是微秒级,所以可能需要直接操作寄存器,而不是用库函数。比如,关闭中断,手动控制SysTick的计数。 可能的实现步骤: 1. 确定系统时钟频率,比如48MHz。 2. 计算1us对应的计数值,比如48。 3. 编写delay_us函数,在函数中设置SysTick的LOAD值为计数值-1,清除当前值,启动定时器,然后等待计数标志位被置位。 4. 注意在等待时可能需要循环,因为SysTick的COUNTFLAG会在计数到0时置位,读取该标志后会自动清除。 但需要注意,在多次调用delay_us时,可能会有累积误差。另外,如果延时时间较长,可能需要循环多次,因为单次SysTick的最大延时是LOAD最大值(比如48MHz时,0xFFFFFF/48 ≈ 349ms)。不过对于微秒级来说,单次延时可能较短,需要多次调用。 参考引用中的例子,用户提到的使用类似CyDelay的函数,可能也需要类似的实现。比如,在STM32中,直接操作SysTick的寄存器:CTRL、LOAD、VAL。需要确保在延时期间没有中断干扰,或者处理得当。 所以,具体的代码可能如下: - 初始化SysTick,但不启用中断。 - 在delay_us函数中,设置LOAD为us * (SystemCoreClock / 1000000) - 1。 - 清除当前VAL寄存器。 - 启动SysTick,然后等待COUNTFLAG置位。 但需要注意,当us较大时,LOAD可能超过24位最大值,所以需要分多次延时。例如,当us为1000时,计数值是48000,超过了0xFFFFFF(16777215),所以需要分段处理。但48MHz下,1ms的计数值是48000,远小于最大计数值,所以单次延时没问题。但如果是更大的延时,比如1秒,就需要循环了。 不过用户可能只需要微秒级的延时,所以可能不需要处理非常大的us值。或者函数内部处理分段时间。 另外,系统时钟的配置是否正确也很重要,需要确保SystemCoreClock变量正确反映了实际的时钟频率。 总结步骤: 1. 确定系统时钟频率。 2. 计算每微秒的周期数。 3. 配置SysTick,禁用中断,手动控制。 4. 在delay_us函数中,设置LOAD和VAL,启动定时器,等待标志位。 5. 处理可能的溢出情况。 可能的问题:在SysTick的计数过程中,如果有其他中断发生,可能导致延时不准。因此,在需要高精度的情况下,可能需要禁用全局中断,但这会影响系统响应。或者,确保延时函数在不会被中断打断的上下文中使用。 另外,SysTick的校准值寄存器可能会影响计数,但通常在应用中可以忽略,直接使用计算的计数值。 最后,编写示例代码,并测试其准确性。可能需要用示波器或调试器来验证延时是否准确。</think>### 基于STM32F051标准库SysTick微秒级延时实现 #### 实现原理 SysTick是Cortex-M0内核的内置24位定时器,可通过标准库函数配置。延时函数的核心步骤如下: 1. **时钟配置**:假设主频为$48\ \text{MHz}$,则1微秒对应$48$个时钟周期。 2. **寄存器操作**:通过设置重装载值(`LOAD`)、清除当前值(`VAL`),并轮询标志位(`COUNTFLAG`)实现精准延时。 #### 代码实现 ```c #include "stm32f0xx.h" // 初始化SysTick定时器(不启用中断) void SysTick_Init(void) { SysTick->CTRL = 0; // 禁用SysTick SysTick->LOAD = 0xFFFFFF; // 设置最大重装载值 SysTick->VAL = 0; // 清除当前值 SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk; } // 微秒级延时函数(主频48MHz时有效) void delay_us(uint32_t us) { uint32_t ticks = us * 48; // 计算所需时钟周期数 SysTick->LOAD = ticks - 1; // 设置重装载值 SysTick->VAL = 0; // 清除当前值 while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0); } ``` #### 关键说明 1. **主频要求**:代码假设系统时钟为$48\ \text{MHz}$,需在`SystemInit()`中正确配置时钟树[^1]。 2. **误差控制**:禁用中断避免时序偏差,适用于裸机环境。 3. **范围限制**:单次最大延时为$0xFFFFFF/48 ≈ 349,525\ \mu\text{s}$(约0.35秒),超过需循环调用。 #### 使用示例 ```c int main(void) { SysTick_Init(); while (1) { GPIO_SetBits(GPIOA, GPIO_Pin_0); delay_us(10); // 延时10μs GPIO_ResetBits(GPIOA, GPIO_Pin_0); delay_us(1000); // 延时1ms } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值