stm32f051精确延时的实现

来源:互联网 发布:网络主播网站 编辑:程序博客网 时间:2024/03/29 20:49

1,使用HAL库中自带的延时函数为

HAL_Delay();

/**  * @brief This function provides accurate delay (in milliseconds) based   *        on variable incremented.  * @note In the default implementation , SysTick timer is the source of time base.  *       It is used to generate interrupts at regular time intervals where uwTick  *       is incremented.  * @note ThiS function is declared as __weak to be overwritten in case of other  *       implementations in user file.  * @param Delay: specifies the delay time length, in milliseconds.  * @retval None  */__weak void HAL_Delay(__IO uint32_t Delay){  uint32_t tickstart = 0;  tickstart = HAL_GetTick();  while((HAL_GetTick() - tickstart) < Delay)  {  }}

该函数传递的参数是uint32_t 类型的,in milliseconds.


2,使用定时器实现

(1)使用操作寄存器的方法实现一个ms延时函数

CR1是控制寄存器,SR是状态寄存器,ARR就是溢出值寄存器,CNT就是计数器的当前值。

PSC是预分频寄存器,你可以给预分频寄存器里面写一个从0~65535的值,这个值+1,就是定时器运行的时钟。举个例子,比如单片机工作在主频72MHz,预分频寄存器写0,预分频系数就是0+1=1,定时器的时钟就是72MHz/1=72MHz;再举个例子,比如单片机还是工作在主频72MHz,预分频寄存器写71,预分频系数就是71+1=72,定时器的时钟就是72MHz/72=1MHz。知道定时器的时钟有什么用?相信很多初学者不清楚,定时器的时钟关乎定时器计数器CNT递增的时间间隔,根据频率和周期的公式f=1/T,定时器计数器递增的时间间隔就是1/定时器的时钟,例如当定时器时钟为1MHz时,定时器计数器递增的时间间隔就是1/1MHz=1微秒,这时,如果你把溢出值设置为1000,就是1000*1us=1ms溢出。


void delay_ms(uint16_t ms){ TIM6->PSC=35999; TIM6->ARR=ms*2; TIM6->CR1|=(1<<3); TIM6->CR1|=0x1; while((TIM6->SR&0X1)==0); TIM6->SR=0;}


第一条语句,设置预分频系数为35999+1=36000,所以定时器的时钟为72000000/36000=2000Hz,那么定时时间间隔就是1/2000=0.0005秒,即0.5毫秒。

第二条语句,设置溢出值为ms乘以2,假如要延时1秒,函数的参数ms就是1000,溢出值就是1000*2=2000,2000*0.5毫秒=1000毫秒,即1秒。这时候,有人会说,为什么不干脆把预分频值PSC设置为71999,即预分频系数为72000,定时器的时钟就是72000000/72000=1000Hz,定时时间就是1毫秒,那么直接把函数的参数ms给了溢出值寄存器ARR就可以了,就不必乘以2了。想法是可以,但是你得知道,定时器都是16位的,所以PSC的值最大到65535,到不了71999。

     第三条语句,CR1寄存器bit3写1,由寄存器定义得知,这是把定时器设置为一旦发生溢出,就停止定时器,因为我们做的是延时函数,延时到了以后,就没有必要让定时器再不断递增了,所以要这样设置。

第五条语句,检测状态寄存器SR中的bit0UIF是否置1,置1的时候,定时值就达到溢出值了,说明定时时间到了。

第六条语句,清除状态寄存器SR中刚才溢出造成的UIF位。

(2)使用库函数的方法实现ms级别的延时

void TIM6_Delay_ms(uint16_t ms){ /* 定义一个定时器基本定时初始化结构体变量 */ TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;  /* 时钟预分频数为36000,在主频72M时,计数器每500us加1*/ TIM_TimeBaseInitStruct.TIM_Prescaler= 35999;  /* 自动重装载寄存器值 */ TIM_TimeBaseInitStruct.TIM_Period=ms*2;  /* 把上面的值配置到寄存器 */ TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStruct);  /* 设置定时时间到了以后停止定时器计数 */ TIM_SelectOnePulseMode(TIM6, TIM_OPMode_Single);  /* 清除SR中的UIF标志 */ TIM_ClearFlag(TIM6, TIM_IT_Update);  /* 打开定时器6 */ TIM_Cmd(TIM6, ENABLE);  /* 检测定时时间是否到来 */ while(TIM_GetFlagStatus(TIM6, TIM_IT_Update)==RESET); /* 软件清除更新标志 */ TIM_ClearFlag(TIM6, TIM_IT_Update);}

你可以细细观察一下上面的库函数,实际上,和直接操作寄存器是一样的。

3,使用systick实现精确延时

(1)SysTick介绍

固件库中的Systick相关函数:

SysTick_CLKSourceConfig() //Systick时钟源选择 misc.c文件中
该函数的时钟源的选择有2种方式,一种是外部时钟源是 HCLK(AHB总线时钟)的1/8,另外一种是内核时钟是 HCLK时钟.
#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)

SysTick_Config(uint32_t ticks) //初始化systick,时钟为HCLK,并开启中断
//core_cm3.h/core_cm4.h文件中
ticks是两次中断之间时钟周期的个数,假如HCLK=72MHZ,那么两次中断之间的时间间隔为72M/ticks * 1/72M (s)
(2)systick的中断服务函数
void SysTick_Handler(void);

(3)使用中断的方式实现ms的延时

static __IO uint32_t TimingDelay;void Delay(__IO uint32_t nTime){TimingDelay = nTime;while(TimingDelay != 0){}}void SysTick_Handler(void){if(TimingDelay != 0x00){TimingDelay--;}} int main(void) {GPIO_InitTypeDef  GPIO_InitStructure;SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); if(SysTick_Config(SystemCoreClock / 1000)) /*中断时间间隔为1ms */ {while(1); }................Delay(50);..................while(1){}}



(4)使用查询的方式实现ms 和 us的延时

static u8  fac_us=0;//us延时倍乘数   static u16 fac_ms=0;//ms延时倍乘数//初始化延迟函数//SYSTICK的时钟固定为HCLK时钟的1/8//SYSCLK:系统时钟void delay_init(){SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟  HCLK/8  systic时钟的频率f=72M/8=9MHZ T=1/9M(s)=1/9(us)  fac_us=SystemCoreClock/8000000; //为系统时钟的1/8 72M/8M= 9 也就是说在当前的时钟下:延时1个us需要走9个时钟周期 fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数  延时1ms就需要走1000个时钟周期}//延时nus//nus为要延时的us数.       void delay_us(u32 nus){u32 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;       //清空计数器 }//延时nms//注意nms的范围//SysTick->LOAD为24位寄存器,所以,最大延时为://nms<=0xffffff*8*1000/SYSCLK//SYSCLK单位为Hz,nms单位为ms//对72M条件下,nms<=1864 void delay_ms(u16 nms){     u32 temp;   SysTick->LOAD=(u32)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;       //清空计数器      } 



查询方式与中断方式的比较:
查询方式就是不断的查询某个标志位,需要耗费大量的cpu 的时间,一般情况下除专门用于延时外不用这种方式;中断方式比较适合处理具有随即特性的事件,事件发生后向cpu提出申请,然后cpu会保存当前的任务转去处理事件
编程时查询方式要不断查询标志位,而中断要编写中断服务子程序来处理中断事件

4,使用while死等待的方式
这种方式的延时是最不准确的,用于粗略的延时。

void delay(uint16_t i){while(i--);}



0 0