STM32使用HAL庫自帶延時函數HAL_Delay時產生1ms誤差

2020-10-01 14:00:49

最近要在stm32f103上寫一個pwm編解碼程式,要對pwm脈寬進行精確計時,無意間發現使用HAL庫自帶延時函數產生的延時存在+1ms的誤差,即:

HAL_Delay(x);
實際延時時間為(x+1)ms

比如在主迴圈中加入程式:

		HAL_Delay(1);
		HAL_GPIO_TogglePin(LED_GPIO_Port, GPIO_PIN_13);

燒錄程式後使用示波器觀察方波波形:
在這裡插入圖片描述
可以看到方波週期為4ms,相鄰跳變之間的時間差為2ms,存在+1ms的誤差

實際使用中如果延時時間為幾百ms或幾s,1ms的誤差並沒有太大影響,而遇到延時時間非常短的情況則會產生巨大影響。

分析HAL_Delay函數定義

觀察HAL_Delay函數在stm32f1xx_hal.c中的定義:

/**
  * @brief This function provides minimum 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(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  while ((HAL_GetTick() - tickstart) < wait)
  {
  }
}

基本思路是在進入函數時讀取當前的tick值(以ms形式儲存至tickstart變數),之後為了滿足最低延時要求給wait變數+uwTickFreq,最後不斷查詢tick值,直到當前tick值大於wait變數,退出函數。
檢視全域性變數uwTickFreq的定義,其數值為systick時鐘的預設頻率(1khz):

HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT;  /* 1KHz */

HAL_TICK_FREQ_DEFAULT=1U(即無符號整型1):

typedef enum
{
  HAL_TICK_FREQ_10HZ         = 100U,
  HAL_TICK_FREQ_100HZ        = 10U,
  HAL_TICK_FREQ_1KHZ         = 1U,
  HAL_TICK_FREQ_DEFAULT      = HAL_TICK_FREQ_1KHZ
} HAL_TickFreqTypeDef;

可以發現,HAL庫函數為了防止無意義延時(即0ms延時)的產生,在HAL_Delay函數傳入引數之後會對引數加1。 如果使用HAL庫預設延時函數進行延時,實際延時時間將會比預期時間多1ms。換句話說,HAL_Delay函數至少會產生1ms的延時。

重定義HAL_Delay函數

由於HAL_Delay為虛擬函式,使用者可根據實際需要進行重定義,所以可以重新定義延時函數為如下形式,在保留原有功能的基礎上消除這個誤差:

void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  if (wait == 0)
  {
	wait += 1U;
  }
  
  while ((HAL_GetTick() - tickstart) < wait)
  {
  }
}

但是,由於系統中其他地方也會用到這個函數,所以不建議對其進行重定義
比較穩妥的做法是手動定義新函數來實現延時功能

附:us延時函數

void Delay_us(int16_t nus) 
{
  int32_t temp; 
  SysTick->LOAD = nus*9; //72MHz
  SysTick->VAL=0X00;
  SysTick->CTRL=0X01;
  do 
  { 
    temp=SysTick->CTRL;
  }
  while((temp&0x01)&&(!(temp&(1<<16))));
     
  SysTick->CTRL=0x00; 
  SysTick->VAL =0X00; 
}

歡迎批評指正!如果你有更好的方法或我的文章存在錯誤請留言告訴我! XD