原创 STM32精确延时(非中断,非ST库函数) (续)

2008-12-15 11:24 16388 7 13 分类: MCU/ 嵌入式

       昨天在做红外解码的时候,发现我先前那个延时函数在某些情况下会不可用.


      分析完之后,发现,该延时函数delay_ms(u32 Nms);delay_us(u32 Nus);在非中断函数里面运行很好.但是只要你在中断里面再调用delay_ms(u32 Nms);delay_us(u32 Nus);的话,就可能出问题了.


      原因如下:假设在处理延时1000ms,此时中断来了.那么你的程序就会打断延时,进入中断处理.但是很不幸,你在中断里面也调用了延时函数,假设是delay_us这个函数(delay_ms也一样).


void delay_us(u32 Nus)

 SysTick->LOAD=Nus*fac_us;       //时间加载     
 SysTick->CTRL|=0x01;            //开始倒数   
 while(!(SysTick->CTRL&(1<<16)));//等待时间到达
 SysTick->CTRL=0X00000000;       //关闭计数器
 SysTick->VAL=0X00000000;        //清空计数器    


        可以发现,在这个函数里面,你会先把LOAD寄存器重装,然后重新开始倒数.这时VAL中的值是未可知的,决定于先前在delay_ms里面的计时值.因为重载的发生是在VAL的值为0的条件下,所以除非VAL中的值为0,否则,在LOAD改变的时候VAL并不会重载.这样,就会导致延时的不准确(实际延时,就是上次的VAL减到0的时间).


       以上分析的是在中断里面可能出现延时不准.但是更严重的错误发生在退出中断后.在退出中断后,函数重新返回到延时1000ms的函数里面执行.可是,你从上面的代码可以知道,此时STRL已经控制计数器关闭了!这就导致了会死在delay_ms里面的


while(!(SysTick->CTRL&(1<<16)));//等待时间到达


这个时间是永远无法到达的.因为CTRL已经被清掉了,bit16无法被置1,所以无法退出该句代码!


    以上就是上次的延时函数存在的两个问题. 针对这两个问题,我把代码改了,可以实现中断调用,但是也存在一点小问题,就是跳出中断后当前的延时不再有效,也就是说,在发生中断的时候会有一次延时不准!(就是被中断打断的这次延时)


#ifndef __DELAY_H
#define __DELAY_H      
//使用SysTick的普通计数模式对延迟进行管理
//包括delay_us,delay_ms 
//正点原子@SCUT
//2008/12/14
//V1.2
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
 
static u8  fac_us=0;//us延时倍乘数
static u16 fac_ms=0;//ms延时倍乘数
//初始化延迟函数
void delay_init(u8 SYSCLK)
{
 SysTick->CTRL&=0xfffffffb;//选择内部时钟 HCLK/8
 fac_us=SYSCLK/8;     
 fac_ms=(u16)fac_us*1000;
}           
//延时Nms
//注意Nms的范围
//Nms<=0xffffff*8/SYSCLK
//对72M条件下,Nms<=1864
void delay_ms(u16 nms)
{        
 u32 temp;    
 SysTick->LOAD=(u32)nms*fac_ms;//时间加载
 SysTick->VAL =0x00;           //清空计数器
 SysTick->CTRL=0x01 ;          //开始倒数 
 do
 {
  temp=SysTick->CTRL;
 }
 while(temp&0x01&&!(temp&(1<<16)));//等待时间到达  
 SysTick->CTRL=0x00;       //关闭计数器
 SysTick->VAL =0X00;       //清空计数器       
}  
//延时us          
void delay_us(u32 Nus)
{  
 u32 temp;      
 SysTick->LOAD=Nus*fac_us; //时间加载     
 SysTick->VAL=0x00;        //清空计数器
 SysTick->CTRL=0x01 ;      //开始倒数  
 do
 {
  temp=SysTick->CTRL;
 }
 while(temp&0x01&&!(temp&(1<<16)));//等待时间到达  
 SysTick->CTRL=0x00;       //关闭计数器
 SysTick->VAL =0X00;       //清空计数器    

#endif
    代码里面增加了对VAL的清空操作,以此来保证数据更新.在等待的时候,也加入了防错控制.CTRL的bit1必须为1,也就是计时器必须开启的情况下,延时函数才有效,否则,马上退出,这就避免了死循环,代价就是1次延时的不准确,不过一般来说还是可以接受的.


    其次还要注意读取CTRL的时候会把COUNTFLAG标志位在被读取之后自动清零,所以读取的时候注意不要把CTRL的读取顺序搞错了.

 

PARTNER CONTENT

文章评论6条评论)

登录后参与讨论

xyd20405 2015-1-27 13:31

如果不用systick,如何做到较为精确的延时呢?

用户576680 2013-9-18 15:15

学习了!

用户377235 2012-6-15 11:36

还好cortex m3、m4是有点关系的,代码直接可以在f4上运行。lz*才阿!

用户377235 2012-4-25 01:16

嗯,受用了。

用户403916 2011-12-30 12:24

刚使用sysstick 并发现了这个问题!谢谢分享

用户1323903 2010-7-24 15:43

不错啊
相关推荐阅读
正点原子 2013-05-17 23:47
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第六十一章 战舰STM32开发板综合实验(标准例程终结篇)
   第六十一章 战舰STM32开发板综合实验        前面已经给大家讲了55个实例了,本章将设计一个综合实例,作为本指南的最后一个实验 ,该实验向大家展示了STM...
正点原子 2013-05-03 23:02
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第六十章 UCOSII实验3-消息队列、信号量集和软件定时器
   第六十章 UCOSII实验3-消息队列、信号量集和软件定时器   上一章,我们学习了UCOSII的信号量和邮箱的使用,本章,我们将学习消息队列、信号量集和软件定时器...
正点原子 2013-05-03 20:42
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十七章 ENC28J60网络实验
第五十七章 ENC28J60网络实验   本章,我们将向大家介绍ALIENTEK ENC28J60网络模块及其使用。本章,我们将使用ALIENTEK ENC28J60网络模块...
正点原子 2013-05-01 23:00
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十九章 UCOSII实验2-信号量和邮箱
第五十九章 UCOSII实验2-信号量和邮箱      上一章,我们学习了如何使用UCOSII,学习了UCOSII的任务调度,但是并没有用到任务间的同步与通信,本章我们将学习两个最基本的...
正点原子 2013-04-30 10:55
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十八章 UCOSII实验1-任务调度
  第五十八章 UCOSII实验1-任务调度      前面我们所有的例程都是跑的裸机程序(裸奔),从本章开始,我们将分3个章节向大家介绍UCOSII(实时多任务操作系...
正点原子 2013-04-26 23:16
【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十七章 ENC28J60网络实验
 第五十七章 ENC28J60网络实验  本章,我们将向大家介绍ALIENTEK ENC28J60网络模块及其使用。本章,我们将使用ALIENTEK ENC28J60网络模块和uIP 1...
EE直播间
更多
我要评论
6
7
关闭 站长推荐上一条 /1 下一条