最近要在stm32f103上寫一個pwm編解碼程序,要對pwm脈寬進行精確計時,無心間發現使用HAL庫自帶延時函數產生的延時存在+1ms的偏差,即:ide
HAL_Delay(x); 實際延時時間爲(x+1)ms
好比在主循環中加入程序:函數
HAL_Delay(1); HAL_GPIO_TogglePin(LED_GPIO_Port, GPIO_PIN_13);
燒錄程序後使用示波器觀察方波波形:
能夠看到方波週期爲4ms,相鄰跳變之間的時間差爲2ms,存在+1ms的偏差
ui
實際使用中若是延時時間爲幾百ms或幾s,1ms的偏差並無太大影響,而遇到延時時間很是短的狀況則會產生巨大影響。spa
分析HAL_Delay函數定義
觀察HAL_Delay函數在stm32f1xx_hal.c中的定義:code
/** * @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):
blog
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的延時。ci
重定義HAL_Delay函數
因爲HAL_Delay爲虛函數,用戶可根據實際須要進行重定義,因此能夠從新定義延時函數爲以下形式,在保留原有功能的基礎上消除這個偏差:rem
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) { } }
可是,因爲系統中其餘地方也會用到這個函數,因此不建議對其進行重定義
比較穩妥的作法是手動定義新函數來實現延時功能
it
附: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