STM32-使用定时器做延时函数时遇到的坑_stm32 delay函数的坑

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

temp=SysTick->CTRL;
    }
    while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
    SysTick->VAL =0X00;      
}

//延时N ms
void delay_ms(int16_t nms)
{                     
    int32_t temp;           
    SysTick->LOAD=(int32_t)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
    SysTick->VAL =0x00;          
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数  
    do
    {
        temp=SysTick->CTRL;
    }
    while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
    SysTick->VAL =0X00;                 
}

然后我就开始使用了。

我是这么使用的:
主循环调用 delay_ms(),
中断里面调用 delay_us() ,这是考虑到中断里面要尽量做少的操作,所以使用短的延时。
然而,在运行过程中,发现有时候会遇到主循环有快速结束等待的情况,远远没有达到我希望的延时时间!
对着代码左看右看,没看出来毛病。后来,在主循环中替换使用那种简单的循环等待的延时函数,就不再出问题了。这才确定到问题就在这个delay_*()延时函数上。
再仔细分析延时耗时,发现问题:这两个函数使用的是同一个定时器硬件:SysTick。
例如,若主循环中希望延时1000ms,调用delay_ms(1000),
SysTick->LOAD的值设置为1000ms了。

若在这时,又进入了中断,有个延时100us的操作,调用delay_us(100),
SysTick->LOAD的值设置为100us了。
两次设置的是同一个寄存器,显然,后一次的设置,覆盖了前一次的设置值!
然后,启动定时器的倒数计时:
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; 
等100us时间到了,判断循环中的结束条件,
while(temp&0x01&&!(temp&(1<<16)));
符合,则延时完成,继续进行中断里面的其他操作。

等退出中断后,主循环继续执行。此时还在延时函数delay_ms()中等待呢,查看判断条件:

while(temp&0x01&&!(temp&(1<<16)));//等待时间到达

看temp的赋值:temp=SysTick->CTRL;

、、、、-- 开启了倒数计时,并且SysTick倒数到0了。

这里,我们需要判断 SysTick->CTRL 中相关字段的意义:

最低位(第0位):ENABLE,是SysTick 定时器的使能位

第16位:COUNTFLAG,如果在上次读取本寄存器后, SysTick 已经计到了 0,则该位为 1。

再来看这两个位的现状:

考虑到delay_us()执行完成了,也就是说,SysTick 已经计到了 0了,即 SysTick->CTRL&(1<<16) 的值置1了。

并且,跳出循环后还执行了一句:

SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;

它的意思就是关闭SysTick定时器的使能,即 (SysTick->CTRL&0x01)的值为0。

所以,此时,在delay_ms()中的判断条件已经不满足了:
即temp&0x01为0,而且,temp&(1<<16)也是0,所以,会立即结束循环。
基本上,相当于,外面的延时1s,被里面的delay_us截断,同时也结束了。
主循环的延时1s,在最坏的情况下(延时刚启动就遇到中断),可能才过了约100us,就结束了!

教训:对于同一个定时器,这样写法的延时函数,不能在主循环与中断里面同时调用!
当在主循环中处于延时等待状态下,中断里面的延时,会修改定时器的状态,从而导致主循环的延时不准确了。
再从另一个更通用的角度来看,其实就是对于同一个全局变量(SysTick),在两个线程中同时访问,并且没有做访问保护。所以,产生问题,就是迟早的事情了。
解决方法,使用两个定时器,就能解决了:一个在主循环中调用,一个在中断里面调用。

进一步思考:若有多个中断,而且都存在延时的调用,就需要多个定时器吗,这可能会导致定时器都不够用呢,那又该怎么办?这其实就是涉及一个设计思路了。可以说,在中断里面调用延时函数,本身就不是一个好主意,能避免则避免。如果不能避免,就需要采用另外的一种方式来解决问题了,也并不需要多个定时器,一个定时器就可以了,我们看了下面这个问题再来说。

上面这种主循环与中断里面同时调用延时函数的问题,还有另一种表现形式:
类似于我在上一篇博客中的延时函数:
#include “timer.h”
#include “stdio.h”
#include “gpio.h”
#include “stm32f0xx_tim.h”

volatile unsigned int gTimer;

void TIM1_Init(void)
{
    TIM_TimeBaseInitTypeDef          TIM_TimeBaseInitStructure;
    NVIC_InitTypeDef                NVIC_InitStructure;

//    系统中TIM1用的是APB2,TIM14时钟用的是APB1
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);  //tim1时钟使能,APB1时钟8M

TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV2; //分频系数为2   //是对APB1的2倍频进行分频,分频系数为2,所以频率还是8M
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数

TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数设置  //对于TIM1是必须设置的

//    计算定时周期: t=(9+1)*1/f=2/(8M/(7+1))=10*8/8M(s)=10us
        TIM_TimeBaseInitStructure.TIM_Period = 9;             //定时10us  //最大65536
    TIM_TimeBaseInitStructure.TIM_Prescaler = 7;  //时钟8M

TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
    TIM_ClearITPendingBit(TIM1,TIM_IT_Update);//清除TIM1的中断待处理位:TIM 中断源
    TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE);     //允许定时器1更新中断
    TIM_Cmd(TIM1,ENABLE); //使能定时器1
//    设置中断优先级
    NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_UP_TRG_COM_IRQn; //定时器1中断
    NVIC_InitStructure.NVIC_IRQChannelPriority = 0; //优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

}

void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET) //溢出中断
    {
        if(gTimer>0){
            gTimer–;
        }
    }
    TIM_ClearITPendingBit(TIM1,TIM_IT_Update);  //清除中断标志位
}

//毫秒的延时函数  
void delay_ms_tim(uint32_t nTimer)  
{  
    gTimer=nTimer*100;
    while(gTimer);
}

//微秒的延时函数,实际以10us为最小单位
void delay_us_tim(uint32_t nTimer)  
{  
    gTimer=nTimer/10;
    while(gTimer);
}

若在中断与主循环中同时使用delay_ms_tim()  delay_us_tim(),也会有同样的问题。
当主循环中执行了delay_ms_tim(1000)时,设置了gTimer=1000*100=100000;
若此时中断进入,执行了delay_us_tim(100),则设置了gTimer=100/10=10;
这样,后一次的设置,就覆盖了前一次对gTimer的赋值,从而导致两个延时函数会同时结束,也就是说,delay_ms_tim(1000)实际延时时间可能只比100us略多,而我们期望的是1s,差距巨大!
这里的问题,我们可以看出,是因为使用了同一个全局变量gTimer。

那么,我们就有了一个不一样的解决方法:并不需要使用多个定时器,而是使用同一个计数器,多个计数变量。

只是增加计数变量的话,不影响TIM1_Init()函数,下面的代码就不展示它了。我们增加一个gTimer变量,相应修改两个延时函数如下:
volatile unsigned int gTimer_ms;
volatile unsigned int gTimer_us;

void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET) //溢出中断
    {
        if(gTimer_ms>0){
            gTimer_ms–;

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

  • 30
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值