赞
踩
在STM32单片机中,实现延时一般都是使用定时器,既可以使用Systick定时器,也可以使用常规的定时器。
定时器在设置了定时并开启之后,就会进入自主运行模式,其中,初始化设置这一阶段是由CPU执行相应指令完成的,之后,定时器外设就会自行计数,这个过程中,CPU就不需要再参与了。这一过程,不管定时器有没有配置中断,定时器都会独立于CPU自主工作。
那么,定时器配置中断和不配置中断有什么区别呢?
定时器在工作时,我们怎么知道有没有到达定时时间呢?因为我们需要判断是否到达,然后据此触发一些相应的动作。如果不配置中断,就需要CPU去不断判断定时器状态标志位是否被置位,这时候,才需要CPU的参与,这就是常说的轮询,效率不高;如果配置了中断,就不用CPU去轮询,定时器模块完成计时的时候,就会给CPU发一个中断通知,然后CPU再去处理中断事务,也就是说,不需要CPU去轮询。
所以,CPU参与不是为了实现定时,而只是去判断定时器有没有完成定时。
之前,我以为定时器计时也是CPU来实现的,还在想,是不是通过并发来实现的,其实,并不是的,各外设在配置完成后都可以独立工作,并不需要CPU全程参与。所以不要弄错了。如果按照这种错误的思路,那么,延时就不可能会实现,因为定时和主循环和并发执行的,不可能会等待计时完成再执行后面的程序。
这里要说的其实就是定时器是独立工作的。
在学习51时,常使用for循环来实现延时。
延时的思路,其实就是让程序在一个地方空等一段时间,for循环就是在一个地方等待来实现延时,只是这种for循环的方式,难以准确估计等待的时间。
要想实现精准的延时,就需要用到定时器。
让定时器一直阻塞轮询,只有到达计时时间,才会结束轮询,继续执行后续的指令。
接下来,分别使用Systick和定时器来实现延时。
直接参考:
【STM32】Systick滴答定时器_一只大喵咪1201的博客-CSDN博客
Systick定时器,是一个简单的定时器,对于CM3、CM4内核芯片,都有Systick定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。
Systic定时器也叫做滴答定时器,是一个24 位的倒计数定时器,计到0时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
如果想熟悉框图,可直接参考:嵌入式学习笔记——SysTick(系统滴答)_systick寄存器_小向是个Der的博客-CSDN博客
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15),被操作系统所调用(当上了操作系统时,其他应用程序则不能再使用该滴答时钟来延时)。
Systick需要初始化吗?继续往下看就知道了。
相关寄存器介绍
Systick有四个主要的寄存器,在标准库中定义如下
接下来依次讲解
寄存器CTRL
叫做SysTick控制及状态寄存器,它有四个位起控制和监视作用,分布情况如下图
位0:(使能)ENABLE位。和所有外设一样,在使用之前,都需要将使能位置1,也就是开启Systic定时器,写0则关闭。
这个位就说明了,systick也是要使能的。
位1:TICKINT位。它是和中断相关的位,SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。Systick中断的优先级也可以设置。将这一位置1后,VAL寄存器中的值减到0时就会进入中断。如果写0,VAL寄存器中的值减到0时不会进入中断,会重新开始下一轮的计数。
中断回调函数在stm32f4xx_it.c中
位2:CLKSOURE位。它是用来选择时钟源的。Systick定时器有俩个时钟源,一个是内部时钟源,它的频率较高,一般和HCLK相同,另一个是外部时钟源,它的频率较低,是HCLK的八分之一。
这里的外部,不是说芯片外部,而是相对内核来说的
参考时钟树理解
注意,HCLK和FCLK的时钟频率是相同的。
位16:COUNTFALG位。这是一个状态位,它在计数的过程中是0,当VAL寄存器中的值减为0时,该位就会由硬件自动置1,并且该位只能读,不能写。
寄存器LOAD
叫做SysTick重装载数值寄存器,它是一个24位的寄存器,分布如下图
这24位是从0到23的,叫做RELOAD位。它里面放的值就是计时用的初值,VAL寄存器中的值就是从这里取出的,当VAL寄存器中的值减为0后,就会自动从LOAD寄存器中再将这个初值取出放入VAL寄存器中,然后继续做减一处理,一直循环下去,除非将使能位写0。
寄存器VAL
叫做Sysytic当前数值寄存器。它是一个24位的寄存器,分布如下图
这24位也是从0到23的,叫做CURRENT位。它里面放的值就是用来计时减一的初值,当这里的值减1到0后,就会自动从LOAD寄存器中再取过来一个初值,继续做减1操作,如此循环,除非将使能位写0。而且VAL寄存器中的值是可以修改的,在计时过程中如果想修改计时时间,则将要计时的初值先放入LOAD寄存器中,再对VAL寄存器写1,则VAL寄存器就会清0,然后从LOAD寄存器中取初值进行新的计时。
寄存器CALIB
叫做SysTick校准数值寄存器。它的分布如下图
这个寄存器很少使用到,等用到的时候再作详细介绍。
与Systick有关的库函数
注意,我这里是基于stm32F407的
STM官方对Systick的使用也提供了库函数,让我们在使用的时候更加方便,不用挨个配置相关的寄存器。
计数初值配置函数
STM官方提供了计数初值配置函数,我们只需要计算出需要计数几个Systick的时钟周期即可,至于LOAD寄存器以及VAL寄存器中的值就不用我们计算好再放进去了。
代码如下:
事实上,这一个函数就可以将systick配置好
这几个位为1分别表示:
时钟源选择内核时钟;
开启中断;
使能systick;
时钟源选择库函数
用这个函数初始化之后,如果需要修改时钟源,也有对应的函数
在STM官方提供的库函数里的misc.c源文件中定义了时钟源选择库函数。
代码如下:
Systick初始化
F407可以直接调用函数SysTick_Config完成初始化
这里时钟是自动获取的,407的库函数提供了一个获取时钟的函数RCC_GetClocksFreq
RCC_Clocks是个结构体变量,可以接着获取对应的指针,而不用我们自己去查找
比如RCC_Clocks.HCLK_Frequency,单位Hz
根据时钟计算计数值然后传入初始化函数,比如上面的初始化1ms
systick中断和延时
如果用systick来做中断,那么进行上述配置就行了。
如果是想用systick来延时,那么可以不用上面的函数来初始化,因为这种情况下不必开启中断。甚至都不用初始化,只要在延时时使能systick定时器就行。
比如正点原子用systick来延时,他们的初始化其实就是把时钟配置成8分频,然后算了下两个一会儿延时时要倍乘的数据。
us延时
ms延时
思路其实都一样,设置计数值,然后使能开始,接着死循环等待时间到达,然后就关闭并清空计数器。
更长时间的ms延时
附:
F103的systick的初始化如下:
//systick定时器初始化 void SystickInit(void) { RCC_ClocksTypeDef rcc_clocks; RCC_GetClocksFreq(&rcc_clocks); SysTick_Config(rcc_clocks.HCLK_Frequency / 1000); }其实和F407类似。
这里有个问题得注意下,那就是SysTick_Config是个静态的内联函数,是直接在头文件中定义的。
这里虽然是静态的,但是是直接写到头文件中,然后被包含在其他文件中,就相当于是直接写在目标文件中,加上是static的,被限制在了文件内部,所以多文件include不会有重复包含的问题,具体可了解静态内联函数的特性,此处不赘述。
定时器实现的思路也比较简单,定时器一般有ms定时和us定时,我们可以先初始化两个定时器,一个配置成1ms定时,一个配置成1us定时。
以下以us级定时器为例说明如何基于定时器编写延时函数。
实现微秒级延时 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; } }上面就是利用定时器的两次计数的差是否到了要设定的时间,因为此时定时器每次计数都是1us,那么计数之差到了要设定的微秒时间时,就表示延时时间到了。
如果还没到,就会在while死循环里一直判断,直到时间到了就退出循环。
因此,能实现延时的目的。
ms级延时是一样的思路。
定时器做延时的时候不需要开启中断,我们利用的是它的计数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。