在STM32单片机中,实现延时一般都是使用定时器,既可以使用Systick定时器,也可以使用常规的定时器。
定时器在设置了定时并开启之后,就会进入自主运行模式,其中,初始化设置这一阶段是由CPU执行相应指令完成的,之后,定时器外设就会自行计数,这个过程中,CPU就不需要再参与了。这一过程,不管定时器有没有配置中断,定时器都会独立于CPU自主工作。
那么,定时器配置中断和不配置中断有什么区别呢?
定时器在工作时,我们怎么知道有没有到达定时时间呢?因为我们需要判断是否到达,然后据此触发一些相应的动作。如果不配置中断,就需要CPU去不断判断定时器状态标志位是否被置位,这时候,才需要CPU的参与,这就是常说的轮询,效率不高;如果配置了中断,就不用CPU去轮询,定时器模块完成计时的时候,就会给CPU发一个中断通知,然后CPU再去处理中断事务,也就是说,不需要CPU去轮询。
所以,CPU参与不是为了实现定时,而只是去判断定时器有没有完成定时。
之前,我以为定时器计时也是CPU来实现的,还在想,是不是通过并发来实现的,其实,并不是的,各外设在配置完成后都可以独立工作,并不需要CPU全程参与。所以不要弄错了。如果按照这种错误的思路,那么,延时就不可能会实现,因为定时和主循环和并发执行的,不可能会等待计时完成再执行后面的程序。
延时的思路
在学习51时,常使用for循环来实现延时。
延时的思路,其实就是让程序在一个地方空等一段时间,for循环就是在一个地方等待来实现延时,只是这种for循环的方式,难以准确估计等待的时间。
要想实现精准的延时,就需要用到定时器。
让定时器一直阻塞轮询,只有到达计时时间,才会结束轮询,继续执行后续的指令。
接下来,分别使用Systick和定时器来实现延时。
Systick实现延时
直接参考:
【STM32】Systick滴答定时器_一只大喵咪1201的博客-CSDN博客
Systick定时器,是一个简单的定时器,对于CM3、CM4内核芯片,都有Systick定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。
Systic定时器也叫做滴答定时器,是一个24 位的倒计数定时器,计到0时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
如果想熟悉框图,可直接参考:嵌入式学习笔记——SysTick(系统滴答)_systick寄存器_小向是个Der的博客-CSDN博客
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15),被操作系统所调用(当上了操作系统时,其他应用程序则不能再使用该滴答时钟来延时)。
延时程序示例:
具体程序实现详见正点原子示例代码。
常规定时器实现延时
定时器实现的思路也比较简单,配置好定时器之后,在程序里打开定时器,然后循环判断计数值是否到0,或者判断状态标志位是否满足要求即可。
比如:
实现微秒级延时 void TIM3Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启时钟 TIM_TimeBaseInitStruct.TIM_Period = 0xffff; //装载计数值 TIM_TimeBaseInitStruct.TIM_Prescaler = 42 - 1; //装载预分频值,1us TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //预分频值为1 TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;//向上计数方式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct); //进行定时器配置 TIM_Cmd(TIM3, ENABLE); //使能TIMx外设 } // 微秒延时 void delay_us(uint16_t us) { u16 tp1; u16 tp2; u16 dif; tp1 = TIM3->CNT; while(1) { tp2 = TIM3->CNT; dif = tp2 - tp1; if(dif >= us) break; } }