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

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

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

好比在主循環中加入程序:ide

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

燒錄程序後使用示波器觀察方波波形:
在這裏插入圖片描述
能夠看到方波週期爲4ms,相鄰跳變之間的時間差爲2ms,存在+1ms的偏差svg

實際使用中若是延時時間爲幾百ms或幾s,1ms的偏差並無太大影響,而遇到延時時間很是短的狀況則會產生巨大影響。函數

分析HAL_Delay函數定義

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

/** * @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):spa

HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT;  /* 1KHz */

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

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的延時。code

重定義HAL_Delay函數

因爲HAL_Delay爲虛函數,用戶可根據實際須要進行重定義,因此能夠從新定義延時函數爲以下形式,在保留原有功能的基礎上消除這個偏差:xml

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)
  { 
 
  
  }
}

可是,因爲系統中其餘地方也會用到這個函數,因此不建議對其進行重定義
比較穩妥的作法是手動定義新函數來實現延時功能blog

附: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