最近要在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函數在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為虛擬函式,使用者可根據實際需要進行重定義,所以可以重新定義延時函數為如下形式,在保留原有功能的基礎上消除這個誤差:
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