1.stm32粗延时函数
粗延时的意思就是延时时间不太准确,一般用在对延时时间要求不严格的场合。这种延时方式是采用软件延时,但因为编译器会在编译的时候加上一些其他辅助指令,所以不能确定C程序的准确运行时间。我们可以采用下面的方法进行估算:
假设stm32 MCU系统时钟(SYSCLK)为48MHz,指令周期为4个系统时钟,则一个指令周期时长为1/12微秒。
若要让计时单位为微秒(us),则可以让CPU空转约12次,即在软件上可以令减数周期变量为12,但由于存在其他辅助指令,所以可以将这个减数周期减小一些,比如10。
类似地,若以毫秒(ms)为计时单位,则可以让CPU空转12000次。空转次数越多,则其他辅助指令占用时间相对越短。
以微秒(us)为单位进行延时:
void delay_us(u16 us_time)
{
u16 i=0;
while(us_time--)
{
i=10; // 这要根据系统时钟频率进行计算
while(i--) ; // 延时主操作,空操作
}
}
以毫秒(ms)为单位进行延时:
void delay_ms(u16 ms_time)
{
u16 i=0;
while(ms_time--)
{
i=12000; // 以ms为单位
while(i--) ;
}
}
2.stm32精准延时函数
精准延时,就要依靠硬件来完成。这个硬件就是硬核Cortex-Mx的Systick定时器。Systick定时器就是系统滴答定时器(不需要CPU干涉),一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值,并发出中断请求。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
Systick定时器的计数频率可以设定为系统频率或系统频率的1/8。假设系统频率为48MHz,则Systick的频率为6MHz。
这个可以由下面函数设定:
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
若计时单位为微秒,则滴答定时器要计数6次;若计时单位为毫秒,则计数6000次。
有两种方法进行精确定时:中断方式和非中断方式。
2.1 中断方式
①配置SysTick中断函数,让延时时间变量在中断中递减。中断函数:
void SysTick_Handler(void)
{
if (TimingDelay != 0x00) //TimingDelay是一个用户全局变量,代表要延时的时间。
{
TimingDelay --;
}
}
在用户源文件中定义:
extern __IO uint32_t TimingDelay;
这里要注意,TimingDelay要是需要被其他位置的源文件调用,则要加extern前缀或不加(默认);若只由自身源文件内调用,则可以加上static前缀,防止被其他源文件混用。这个要根据程序实际情况设定。
②设置SysTick的重装载值,并配置中断触发。
if(SysTick_Config(SystemCoreClock/1000)) //配置滴答计数值及滴答中断,这里一个计时周期为1ms
{ //SystemCoreClock为系统时钟频率(Hz)
while(1); // 返回1,表示配置失败;0表示成功
}
//若设定计时周期为1us,则如下
if(SysTick_Config(SystemCoreClock/1000000))
{
while(1);
}
SysTick_Config(uint32_t ticks)是用来初始化系统定时器及其中断的,并开启系统定时器及产生周期性中断。ticks是定时器计数次数。该函数存放在core_cm0(3/4/7…).h中。
③延时函数
延时函数所能做的就是给延时变量TimeDelay赋值,并等待SysTick中断。
static void Delay(__IO uint32_t nTime) // 延时nTime单位个时间
{
TimingDelay = nTime;
while(TimingDelay != 0) ; // 静静等待
}
2.2 非中断方式
中断方式最大的弊端就是稍显繁琐,而非中断方式则直接得多。SysTick 主要包含CTRL、LOAD、VAL、CALIB 等4 个寄存器。SysTick 的 counter 从 reload值往下递减到 0 的时候, CTRL 寄存器的位 16:countflag 会置 1,且读取该位的值可清 0,
我们可以直接操作寄存器,在等待中使用软件查询的方法来实现延时。
//不进入systic中断,微秒级延时
void delay_us(__IO uint32_t us)
{
uint32_t i;
SysTick_Config(SystemCoreClock/1000000);
for (i=0;i<us;i++)
{
while( !((SysTick->CTRL)&(1<<16)) ) ; //等待,计数器的值减小到0时,CRTL寄存器的位16会置1
}
SysTick->CTRL &=(~SysTick_CTRL_ENABLE_Msk); // 关闭定时器
}
// 若是延时单位为毫秒级,则将分子1000000改成1000.
在网上有网友用如下更底层的方法,其原理与上面的程序一致:
SYSTICK 的时钟设定为HCLK 时钟的1/8,这里选用系统时钟频率为72MHz,所以SYSTICK的时钟为9M,即SYSTICK定时器以9M的频率递减。
//仿原子延时,不进入systic中断
void delay_us(u32 nus) //us为单位
{
u32 temp;
SysTick->LOAD = 9*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(u16 nms) // ms为单位
{
u32 temp;
SysTick->LOAD = 9000*nms;
SysTick->VAL=0X00; //清空计数器
SysTick->CTRL=0X01; //使能,减到零是无动作,采用外部时钟源
do
{
temp=SysTick->CTRL; //读取当前倒计数值
}while((temp&0x01)&&(!(temp&(1<<16)))); //等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
2.3 SysTick寄存器介绍
SysTick->CTRL
位段 |
名称 |
类型 |
复位值 |
描述 |
16 |
COUNTFLAG |
R |
0 |
如果在上次读本寄存器后systick已为0,则该位为1,若 读该位自动清零 |
2 |
CLKSOURCE |
RW |
0 |
0:外部时钟源 1:内部时钟 |
1 |
TICKINT |
RW |
0 |
0:减到0无动作;1:减到0产生systick异常请求 |
0 |
ENABLE |
RW |
0 |
systick定时器使能位 |
SysTick-> LOAD
位段 |
名称 |
类型 |
复位值 |
描述 |
23:0 |
RELOAD |
RW |
0 |
减到0时被重新装载的值 |
SysTick-> VAL
位段 |
名称 |
类型 |
复位值 |
描述 |
23:0 |
CURRENT |
RW |
0 |
读取时返回当前倒计数的值,写则清零,同时还会清除在systick控制及状态寄存器中的COUNTFLAG标志 |
SysTick-> CALIB 不常用,在这里也没用到,有兴趣的读者可查阅ST关于Cortex-m0(3/4/..)的manual。