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

2019-06-14来源: eefocus关键字:STM32  定时器  延时函数

做延时函数,可以使用简单的循环等待,如下面这样的:

void Delay(uint32_t nCount) 

{

     for(; nCount != 0; nCount--);

}

但是有个问题,就是这个nCount值怎么取?

我们可以通过多次试验,来确定调用时使用的循环次数。

但是还要考虑下,如果硬件有变化,例如外接晶振变化,或类似的主芯片替换等情况下,这个值有可能会变化。另外,编译的优化选项变化,也可能导致循环次数的变化。也就是说,这样写的延时函数,对外部的依赖项比较多,稍不注意,可能最终的延时时间不准确。

更好的延时方式是使用定时器,这样能更准确的定时,并且移植性也更好一些。

但是使用定时器做延时函数时,也是有一些需要注意的事情的,否则,可能会掉入坑中还茫然不知。例如我本人,就掉了几次坑,花了好长时间才爬出来......



先简单说明下我的开发环境,芯片类型是stm32F030C8,集成开发环境用的是Keil5 MDK-ARM,仿真器使用JLINK。


通常我们使用定时器来做延时函数,比较常见的例子就是这样的:



#include "delay.h"


static int8_t  fac_us=0;//us

static int16_t fac_ms=0;//ms

static int flag_HCLK_Div8=1;


void delay_init()     

{

    if(flag_HCLK_Div8){

        SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);//选择外部时钟  HCLK/8

        fac_us=SystemCoreClock/48000000;    //为系统时钟的1/8  

    } else {

        SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//选择外部时钟

        fac_us=SystemCoreClock/1000000;    //为系统时钟

    }    

    fac_ms=(int16_t)fac_us*1000;//每个ms需要的systick时钟数   

}    


//延时N us

void delay_us(int32_t nus)

{        

    int32_t temp;             

    SysTick->LOAD=nus*fac_us; //时间加载               

    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;      

}


//延时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_

[1] [2]

关键字:STM32  定时器  延时函数

编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic464653.html
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:STM32的系统时钟与SystemInit函数
下一篇:STM32-仿真调试时的SystemInit陷阱

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

第38章 I2S—音频播放与录音输入—零死角玩转STM32-F429系列

本章参考资料:《STM32F4xx 中文参考手册》、《STM32F4xx规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》及《I2S BUS》。若对I2S通讯协议不了解,可先阅读《I2S BUS》文档的内容学习。关于音频编译码器WM8978,请参考其规格书《WM8978_v4.5》来了解。38.1 I2S简介Inter-IC Sount Bus(I2S)是飞利浦半导体公司(现为恩智浦半导体公司)针对数字音频设备之间的音频数据传输而制定的一种总线标准。在飞利浦公司的I2S标准中,既规定了硬件接口规范,也规定了数字音频数据的格式。38.1.1 数字音频技术现实生活中的声音是通过一定介质传播
发表于 2019-09-19
第38章 I2S—音频播放与录音输入—零死角玩转STM32-F429系列

第50章 读写内部FLASH—零死角玩转STM32-F429系列

本章参考资料:《STM32F4xx 中文参考手册》、《STM32F4xx规格书》、库说明文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。50.1 STM32的内部FLASH简介在STM32芯片内部有一个FLASH存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中,由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行,见图 501。图 501 STM32的内部框架图除了使用外部的工具(如下载器)读写内部FLASH外,STM32芯片在运行的时候,也能对自身的内部FLASH进行读写
发表于 2019-09-19
第50章 读写内部FLASH—零死角玩转STM32-F429系列

第49章 在SRAM中调试代码—零死角玩转STM32-F429系列

本章参考资料:《STM32F4xx 中文参考手册》、《STM32F4xx规格书》、《Cortex-M3权威指南》、《Cortex-M4 Technical Reference Manual》(跟M3大部分是相同的,读英文不习惯可先参考《Cortex-M3权威指南》)。学习本章时,配合《STM32F4xx 中文参考手册》"存储器和总线结构"及"嵌入式FLASH接口"章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。49.1 在RAM中调试代码一般情况下,我们在MDK中编写工程应用后,调试时都是把程序下载到芯片的内部FLASH运行测试的,代码的CODE及RW-data的内容被写入
发表于 2019-09-19
第49章 在SRAM中调试代码—零死角玩转STM32-F429系列

第48章 MDK的编译过程及文件类型全解—零死角玩转STM32-F429

本章参考资料:MDK的帮助手册《ARM Development Tools》,点击MDK界面的"help->uVision Help"菜单可打开该文件。关于ELF文件格式,参考配套资料里的《ELF文件格式》文件。在本章中讲解了非常多的文件类型,学习时请跟着教程的节奏,打开实际工程中的文件来了解。相信您已经非常熟练地使用MDK创建应用程序了,平时使用MDK编写源代码,然后编译生成机器码,再把机器码下载到STM32芯片上运行,但是这个编译、下载的过程MDK究竟做了什么工作?它编译后生成的各种文件又有什么作用?本章节将对这些过程进行讲解,了解编译及下载过程有助于理解芯片的工作原理,这些知识对制作IAP
发表于 2019-09-19
第48章 MDK的编译过程及文件类型全解—零死角玩转STM32-F429

第47章 QR-Decoder-OV5640二维码识别—零死角玩转STM32-F429系列

本章参考资料:《STM32F4xx 中文参考手册》、《STM32F4xx规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。关于开发板配套的OV5640摄像头参数可查阅《ov5640datasheet》配套资料获知。STM32F4芯片具有浮点运算单元,适合对图像信息使用DSP进行基本的图像处理,其处理速度比传统的8、16位机快得多,而且它还具有与摄像头通讯的专用DCMI接口,所以使用它驱动摄像头采集图像信息并进行基本的加工处理非常适合。本章讲解如何使用二维码识别库进行二维码的识别。47.1 二维码简介二维码,又称二维条码或二维条形码,二维条码是用某种特定的几何图形按一定规律在平面(二维方向
发表于 2019-09-19
第47章 QR-Decoder-OV5640二维码识别—零死角玩转STM32-F429系列

第44章 MPU6050传感器—姿态检测—零死角玩转STM32-F429系列

本章参考数据:《STM32F4xx参考手册》、《STM32F4xx规格书》、库说明文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。关于MPU6050的参考资料:《MPU-60X0寄存器》、《MPU6050数据手册》以及官方驱动《motion_driver_6.12》。本章讲解的内容跨领域的知识较多,若您感兴趣,请自行查阅各方面的资料,对比学习。44.1 姿态检测1.    基本认识在飞行器中,飞行姿态是非常重要的参数,见图 441,以飞机自身的中心建立坐标系,当飞机绕坐标轴旋转的时候,会分别影响偏航角、横滚角及俯仰角。图 441 表示飞机姿态的偏航角、横滚角及俯仰角假如我们知道飞机
发表于 2019-09-19
第44章 MPU6050传感器—姿态检测—零死角玩转STM32-F429系列

小广播

何立民专栏

单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved