最近要在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