天天看點

【STM32筆記】STM32 延時函數的實作

在MCU程式設計中,微秒延時和毫秒延時使用最為頻繁,在RTOS中,毫秒延時可以由系統提供,可是微秒延時卻需要開發人員編寫。本文基于STM32F407ZG晶片實作幾種微秒延時操作。

1、定時器延時

STM32F407裡提供的定時器有:

  • 進階定時器:TIM1和TIM8,16位定時器
  • 通用定時器:TIM2到TIM5,TIM9到TIM14,16位定時器
  • 基本定時器:TIM6和TIM7,16位定時器

時鐘總線:

【STM32筆記】STM32 延時函數的實作
【STM32筆記】STM32 延時函數的實作

本文采用基本定時器TIM6來作為微秒延時的定時器。

定時器6初始化:

/**
 * @brief  TIM6初始化 
 * @note   84MHz時鐘頻率,時鐘分頻84,每個CNT計數1us,16位定時器,最大可計數65535us
 */
void tim6Init(void)
{
  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);  /* 初始化時鐘 */

	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);
	
	TIM_TimeBaseInitStructure.TIM_Period        = 65535;                  /* 最大計數 */ 	
	TIM_TimeBaseInitStructure.TIM_Prescaler     = (84-1);                 /* 定時器分頻,分頻後頻率為1MHz,最低1us定時 */ 
	TIM_TimeBaseInitStructure.TIM_CounterMode   = TIM_CounterMode_Down;   /* 向下計數 */
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;           /* 時鐘分頻 */

	TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure);                    /* 初始化 */
	TIM_Cmd(TIM6,ENABLE);                                                 /* 使能定時器 */
}
           

關閉定時器:

/**
 * @brief  關閉定時器 
 * @note   延時結束後關閉定時器
 */
void tim6Stop(void)
{
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,DISABLE);
  TIM_Cmd(TIM6,DISABLE); 
}
           

微秒延時:

/**
 * @brief  微秒延時 
 * @param  uint32_t nUs  延時數
 */
void delayUs(uint32_t nUs)
{
  volatile uint32_t startCnt = 0;

  (nUs >= 65535) ? (nUs = 65536) : (nUs = nUs);     /* 16位定時器,最大計數延時65535us */

  tim6Init();   /* 初始化定時器 */

  startCnt = TIM6->CNT;

  while((TIM6->CNT-startCnt) < nUs);

  tim6Stop();
}
           

以上代碼在單線程裸機系統中,在準确度上可以滿足MCU的微秒延時使用;但是在多線程的RTOS中,會因為在多線程中同時調用微秒延時,導緻線程A在while裡等待延時時,定時器被線程B關閉,CPU無法釋放。

微秒延時(代碼段保護):

/**
 * @brief  微秒延時 
 * @param  uint32_t nUs  延時數
 */
void delayUs(uint32_t nUs)
{
  volatile uint32_t startCnt = 0;

  (nUs >= 65535) ? (nUs = 65535) : (nUs = nUs);     /* 16位定時器,最大計數延時65535us */

  rt_enter_critical();            /* 進入保護段,禁止系統排程 */

  tim6Init();   /* 初始化定時器 */

  startCnt = TIM6->CNT;

  while((TIM6->CNT-startCnt) < nUs);

  tim6Stop();

  rt_exit_critical();             /* 離開保護段 */
}
           

2、嘀嗒定時器(中斷方式)

STM32的cotex核心上提供了一個嘀嗒定時器(SysTick),嘀嗒定時器是一個24位的計時器,向下計數的方式,當計數倒數到0時,将從RELOAD寄存器裡自動重裝載值。隻要CTRL寄存器的ENABLE位不被清除,則嘀嗒定時器用不停止。

可以用嘀嗒定時器為系統提供微秒延時。

嘀嗒定時器初始化:

/**
 * @brief  嘀嗒定時器初始化 
 * @param  {type}
 * @retval none
 */
void sysTickInit(void)
{
  RCC_GetClocksFreq(&RCC_Clocks);     /* 擷取系統時鐘 */
  SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);   /* 配置嘀嗒為1ms */
  SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;         /* 關閉嘀嗒 */
}
           

延時實作:

volatile uint32_t delayTimer;

/**
 * @brief  嘀嗒中斷服務函數 
 * @param  {type}
 * @retval none
 */
void SysTick_Handler(void)
{
  if(delayTimer)
  {
    delayTimer --;
  }
}

/**
 * @brief  延時函數 
 * @param  {type}
 * @retval none
 */
void delay(uint32_t nTimer)
{
  delayTimer = nTimer;

  SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;           /* 開啟嘀嗒 */

  while(delayTimer);

  SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;         /* 關閉嘀嗒 */
}
           

3、嘀嗒定時器(查詢)

中斷方式會頻繁打斷系統,可以使用查詢嘀嗒計數器的方式完成延時。

【STM32筆記】STM32 延時函數的實作
【STM32筆記】STM32 延時函數的實作
【STM32筆記】STM32 延時函數的實作

在此貼上一段RT-TRHEAD的嘀嗒初始化函數:

/**
 * @brief  嘀嗒定時器初始化 
 * @note   使用RT-THREAD的嘀嗒初始化,嘀嗒逾時事件1ms,嘀嗒時鐘8分頻
 */
void SysTick_Configuration(void)
{
  RCC_ClocksTypeDef  rcc_clocks;
  rt_uint32_t         cnts;

  RCC_GetClocksFreq(&rcc_clocks);

  cnts = (rt_uint32_t)rcc_clocks.HCLK_Frequency / 1000;
  cnts = cnts / 8;

  SysTick_Config(cnts);
  SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
}

/**
 * This is the timer interrupt service routine.
 *
 */
void SysTick_Handler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    rt_tick_increase();			/* 系統時間片累加 */
		
    /* leave interrupt */
    rt_interrupt_leave();	
}
           

時鐘8分頻,每1ms完成一次中斷,每1ms完成一次系統時間片的累加計數。

初始化後的嘀嗒重裝載值(SysTick->RELOAD)為:

168000000 / 1000 / 8 = 21000 168000000 / 1000 / 8 = 21000 168000000/1000/8=21000

嘀嗒計數滿21000個值,時間剛好是1ms,故每一次計數的時間為:

( 0.001 s / 21000 ) s (0.001 s / 21000) s (0.001s/21000)s

嘀嗒定時器延時實作:

/**
 * @brief  微秒延時 
 * @param  {type}
 * @retval none
 */
void delayUs(uint32_t nUs)
{
  uint32_t ticks;
	uint32_t told,tnow,tcnt=0;
	uint32_t reload=SysTick->LOAD;	

	rt_enter_critical();
	
	ticks = nUs*(168 >> 3); 		//168 MHZ		
	told  = SysTick->VAL;       
   			
	while(1)
	{
		tnow = SysTick->VAL;	

		if(tnow != told)
		{	    
			if(tnow < told)
			{
				tcnt += told - tnow;
			}
			else 
			{
				tcnt += reload - tnow + told;	
			}		

			told = tnow;

			if(tcnt >= ticks)
			{
				break;
			}				
		}  
	}

	rt_exit_critical();
}
           

最低的延時時間為1us。

  1. 完成1us需要嘀嗒定時器VAL的計數值:

    前面計算的每一次計數時間為:(0.001 s / 21000) s,

    完成1us需要的計數為:

    t i c k s = 1 u s / ( 0.001 s / 21000 ) = 21 ticks = 1us / (0.001s / 21000) = 21 ticks=1us/(0.001s/21000)=21