STM32实现延时(Systick+Tim)

在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),被操作系统所调用(当上了操作系统时,其他应用程序则不能再使用该滴答时钟来延时)。

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级延时是一样的思路。

定时器做延时的时候不需要开启中断,我们利用的是它的计数。

  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
STM32实现微秒级延时可以使用如方法: 1. 使用SysTick定时:SysTick定时器是一个24位的向下计数器,可以配置为不同的时钟源。你可以使用SysTick定时器实现微秒级的延时。 首先,你需要初始化SysTick定时器,并将其配置为适当的时钟源。然后,使用一个循环来等待SysTick计数器达到所需的延时值。 以下是一个示例代码: ```c #include "stm32f4xx.h" void delay_us(uint32_t us) { // 配置SysTick定时器 SysTick_Config(SystemCoreClock / 1000000); // 使用CPU时钟作为时钟源,每个计数器增加1us uint32_t start = SysTick->VAL; // 记录当前计数器值 while ((start - SysTick->VAL) < us) { // 等待计数器达到所需的延时值 } } int main(void) { // 初始化系统和其他外设 while (1) { // 执行其他任务 delay_us(1000); // 延时1ms } } ``` 请注意,以上代码是基于STM32F4系列微控制器的示例。对于其他系列的STM32微控制器,你需要根据其特定的寄存器和时钟配置进行相应的修改。 2. 使用TIM定时器:另一种实现微秒级延时的方法是使用TIM定时器。你可以配置一个适当的TIM定时器作为计时器,并使用其计数器值来实现微秒级的延时。 以下是一个示例代码: ```c #include "stm32f4xx.h" void delay_us(uint32_t us) { // 配置TIM定时器 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // 使能TIM2时钟 TIM2->PSC = SystemCoreClock / 1000000 - 1; // 设置预分频值,使计数频率为1MHz TIM2->ARR = us - 1; // 设置自动重载寄存器的值,确定延时时间 TIM2->CNT = 0; // 清零计数器 TIM2->CR1 |= TIM_CR1_CEN; // 启动计时器 while (!(TIM2->SR & TIM_SR_UIF)) { // 等待计时器溢出 } TIM2->SR &= ~TIM_SR_UIF; // 清除溢出标志位 } int main(void) { // 初始化系统和其他外设 while (1) { // 执行其他任务 delay_us(1000); // 延时1ms } } ``` 同样,请根据你使用的STM32系列微控制器进行适当的修改。 这些是两种常见的实现微秒级延时的方法,你可以根据需要选择其中之一来实现你的延时需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值