STM32的四种延时方法
单片机编程过程中经常用到延时函数,最常用的莫过于微秒级延时delay_us()和毫秒级delay_ms()。本文基于STM32F207介绍4种不同方式实现的延时函数。
一:普通延时
这种延迟方式应该是每个人在51台单片机时接触最早的延迟函数。这个比较简单,让单片机做一些无关紧要的工作来打发时间,经常通过循环来实现。在一些编译器下,代码会被优化,导致精度低,用于一般延迟和对精度不敏感的应用场景。
//微秒级的延时
void delay_us(uint32_t delay_us) {volatile unsigned int num;volatile unsigned int t;for (num = 0; num < delay_us; num++) {t = 11;while (t != 0) {t--;}}
}
//毫秒级的延时
void delay_ms(uint16_t delay_ms) {volatile unsigned int num;for (num = 0; num < delay_ms; num++) {delay_us(1000);}
}
二:定时器中断
定时器精度很高,可以配置定时器中断,比如配置1ms中断一次,然后间接判断进入中断的次数,达到精确延迟的目的。这种方法的精度可以保证,但是系统一直在中断,不利于在其他中断中调用这个延迟函数。有些高精度的应用场景不适合,比如其他外部设备输出,不允许中断。
STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍:
初始化SysTick 定时器:
/* 配置SysTick为1ms */
RCC_GetClocksFreq(&RCC_Clocks);
SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
中断服务函数:
void SysTick_Handler(void) {TimingDelay_Decrement();
}void TimingDelay_Decrement(void) {if (TimingDelay != 0x00) {TimingDelay--;}
}
延时函数:
void Delay(__IO uint32_t nTime) {TimingDelay = nTime;while(TimingDelay != 0);
}
三:查询定时器
为了解决定时器频繁中断的问题,我们可以使用定时器,但不使能中断,可以通过查询延迟,既可以解决频繁中断的问题,又可以保证精度。
STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍。
STM32的CM3核心处理器包括SysTick计时器,SysTick是24位倒计数计时器,计算到0时,从RELOAD存储器自动重新加载计时初始值。如果不清除SysTick控制和状态存储器中的使能位置,就不会停止。
SYSTICK的时钟固定为HCLK时钟的1/8,在这里我们选用内部时钟源120M,所以SYSTICK的时钟为(120/8)M,即SYSTICK定时器以(120/8)M的频率递减。SysTick 主要包含CTRL、LOAD、VAL、CALIB 等4 个寄存器。


void delay_us(uint32_t nus) {uint32_t temp;SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000000/8*nus;SysTick->VAL=0X00;//清空计数器 SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源 do {temp=SysTick->CTRL;//读取当前倒计数值}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达SysTick->CTRL=0x00;//关闭计数器 SysTick->VAL =0X00;//清空计数器
}
void delay_ms(uint16_t nms) {uint32_t temp;SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;SysTick->VAL=0X00;//清空计数器SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源 do {temp=SysTick->CTRL;//读取当前倒计数值}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达 SysTick->CTRL=0x00;//关闭计数器 SysTick->VAL =0X00;//清空计数器}
四:汇编指令
如果系统硬件资源紧张,或者没有额外的定时器提供,又不想方法1的普通延时,可以使用汇编指令的方式进行延时,不会被编译优化且延时准确。
/*! * @brief 软件延时 * @param ulCount:延时时钟数 * @return none * @note ulCount每增加1,该函数增加3个时钟 */
void SysCtlDelay(unsigned long ulCount) {__asm(" subs r0, #1\n" " bne.n SysCtlDelay\n" " bx lr");
}
这3个时钟指的是CPU时钟,也就是系统时钟。120MHZ,也就是说1s有120M的时钟,一个时钟也就是1/120 us,也就是周期是1/120 us。3个时钟,因为执行了3条指令。
使用这种方式整理ms和us接口,在Keil和IAR环境下都测试通过。
从理论上讲:汇编方式的延迟也是不准确的,有可能被其它中断打断,最好使用us和ms级的延迟,使用for循环延迟的函数也是如此。理论上,使用定时器延迟也可能是不准确的,定时器延迟是准确的,但在判断语句时,如if语句,判断延迟是否到达时,就在判断时,被中断打断执行其他代码,返回时已经过了一小段时间。但是汇编方式和定时方式,只是理论上不准确,在实际项目中,这两种方式的精度已经足够高了。
