参考资料:1. 正点原子
摘要:本篇文章首先讨论了Systick的配置过程、HAL_Delay()函数的实现原理并分析了中断服务函数不能使用延时函数的原因,其次讨论了正点原子的delay_us()函数实现原理以及在使用delay_us()函数时必须重写HAL_Delay()的原因。
作者水平有限,非常欢迎各路大仙批阅指正文章中可能会分析的不恰当之处!非常感谢!!
一、Systick初始化过程
1.1 Systick 寄存器简介
Systick系统滴答定时器是Cortex-M3架构的资源,参阅《Cortex-M3开发手册》,Systick有校准寄存器、重装载寄存器、当前计数值寄存器以及状态及控制寄存器,程序中只对其重装载寄存器SysTick -> LOAD; 当前计数值寄存器Systick -> VAL; 以及控制及状态寄存器Systick -> CTRL进行了配置。我们先来看一下这些寄存器的定义:
这里重点强调一下CTRL控制及状态寄存器的bit2,在STM32中,当配置Systick -> CTRL的bit2 = 0时,选择外部时钟源STCLK,而这里的STCLK即为CUBEMX时钟树上“To Cortex System timer(MHz)”,虽然CubeMX中可以选择是否配置八分频,但是实际上只要CTRL的bit2为0,STCLK的时钟就一定是HCLK的八分频,不管你配没配置八分频...(可能是bug吧)
如果配置Systick -> CTRL的bit2 = 1,则对应时钟树为FCLK:
综上,Systick的时钟源要么是HCLK(CTRL的bit2 = 1),要么是HCLK的八分频(CTRL的bit2 = 0)
注意!无论是在CUBEMX还是STM32手册里都明确说明了HSI固定为8M,但是在HAL_Init()程序的注释中表明HSI为16M!!!且后续Systick的配置均以16M为基准进行的(后续有分析)。因此我大胆猜测是STM32后续更新了HSI为16M但是没有更新CUBEMX和手册?
1.2 上电启动后至系统时钟初始化之前的阶段
在这个阶段,系统时钟由HSI提供。在CubeMX生成的代码中,会自动生成一个HAL_Init()函数
它的作用是初始化HAL库,而且必须在main中第一个被执行。其中一个很重要的功能是初始化Systick定时器(使用HSI时钟源(16M),并产生1ms的中断,中断分组为4)
HAL_StatusTypeDef HAL_Init(void)
{
/* Configure Flash prefetch */
#if (PREFETCH_ENABLE != 0)
#if defined(STM32F101x6) || defined(STM32F101xB) || defined(STM32F101xE) || defined(STM32F101xG) || \
defined(STM32F102x6) || defined(STM32F102xB) || \
defined(STM32F103x6) || defined(STM32F103xB) || defined(STM32F103xE) || defined(STM32F103xG) || \
defined(STM32F105xC) || defined(STM32F107xC)
/* Prefetch buffer is not available on value line devices */
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif
#endif /* PREFETCH_ENABLE */
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
HAL_InitTick(TICK_INT_PRIORITY);
/* Init the low level hardware */
HAL_MspInit();
/* Return function status */
return HAL_OK;
}
其中SysTick的配置通过HAL_InitTick(TICK_INT_PRIORITY)配置,TICK_INT_PRIPORITY通过宏定义设置为15(在中断分组为4的情形下,为系统最低优先级)
我们来具体看一下这个函数:
简而言之这个函数就干了俩事:1. 配置SysTick产生1ms中断 2. 配置SYsTick优先级(抢占优先级为15)
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
优先级配置我们在这里不做讨论,重点讨论SysTick的配置函数HAL_SYSTICK_Config()
其中传递的参数:
uint32_t SystemCoreClock = 16000000;(印证了HSI实际就是16M)
uwTickFreq = 1U;
那么计算传递的参数为:16M/(1000/1)= 16K
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
return SysTick_Config(TicksNumb);
}
好吧..又是套娃,把参数等于16K代入SysTick_Config()函数
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* Reload value impossible */
}
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
这里涉及到了SysTick的寄存器配置,具体寄存器的功能在上文已有讲述。
上述代码操作了ReLoad寄存器、CURRENT寄存器以及CTRL寄存器,我们逐一来看一看:
SysTick->LOAD = (uint32_t)(ticks - 1UL);
此时Reload值为16K - 1
SysTick->VAL = 0UL;
此时当前值为0
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
可以知道此时CTRL寄存器的值为(二进制):
00000000 00000000 00000000 00000111
即CTRL寄存器的bit2 = 1,使用CUBEMX时钟树上对应FCLK的那一路;bit1 = 1,使能SysTick中断;bit0 = 1,开启Systick定时器。
由于SysTick时钟源为16M的HSI,因此周期 = (Reload + 1)/ 16M = 16k / 16M = 1ms!!
1.2 SystemClock_Config()配置系统时钟(根据CUBEMX配置的时钟)
在进行完HAL_Init()后,系统时钟需要切换为用户定义的时钟系统,这里就调用了SystemClock_Config()函数,而这个函数定义好后,SysTick的时钟源FCLK变为72M,因此该函数也对Systick的寄存器进行了进一步的调整,使得Systick时钟能继续产生1ms的中断周期。
以我的CUBEMX配置的时钟树为例,如图所示
现在我们来看一看SystemClock_Config()函数
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
这个函数根据两个结构体
RCC_OscInitStruct、RCC_ClkInitStruct对RCC时钟进行配置,根据CUBEMX我们选择的配置进行相应的赋值。重点在最后的HAL_RCC_ClockConfig()函数,这个函数非常长,也是对RCC的相应寄存器进行赋值配置。
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)
{
uint32_t tickstart;
/* Check Null pointer */
if (RCC_ClkInitStruct == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
assert_param(IS_RCC_CLOCKTYPE(RCC_ClkInitStruct->ClockType));
assert_param(IS_FLASH_LATENCY(FLatency));
/* To correctly read data from FLASH memory, the number of wait states (LATENCY)
must be correctly programmed according to the frequency of the CPU clock
(HCLK) of the device. */
#if defined(FLASH_ACR_LATENCY)
/* Increasing the number of wait states because of higher CPU frequency */
if (FLatency > __HAL_FLASH_GET_LATENCY())
{
/* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
__HAL_FLASH_SET_LATENCY(FLatency);
/* Check that the new number of wait states is taken into account to access the Flash
memory by reading the FLASH_ACR register */
if (__HAL_FLASH_GET_LATENCY() != FLatency)
{
return HAL_ERROR;
}
}
#endif /* FLASH_ACR_LATENCY */
/*-------------------------- HCLK Configuration --------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_HCLK) == RCC_CLOCKTYPE_HCLK)
{
/* Set the highest APBx dividers in order to ensure that we do not go through
a non-spec phase whatever we decrease or increase HCLK. */
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
{
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_HCLK_DIV16);
}
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
{
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, (RCC_HCLK_DIV16 << 3));
}
/* Set the new HCLK clock divider */
assert_param(IS_RCC_HCLK(RCC_ClkInitStruct->AHBCLKDivider));
MODIFY_REG(RCC->CFGR, RCC_CFGR_HPRE, RCC_ClkInitStruct->AHBCLKDivider);
}
/*------------------------- SYSCLK Configuration ---------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_SYSCLK) == RCC_CLOCKTYPE_SYSCLK)
{
assert_param(IS_RCC_SYSCLKSOURCE(RCC_ClkInitStruct->SYSCLKSource));
/* HSE is selected as System Clock Source */
if (RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_HSE)
{
/* Check the HSE ready flag */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
{
return HAL_ERROR;
}
}
/* PLL is selected as System Clock Source */
else if (RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLCLK)
{
/* Check the PLL ready flag */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
{
return HAL_ERROR;
}
}
/* HSI is selected as System Clock Source */
else
{
/* Check the HSI ready flag */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) == RESET)
{
return HAL_ERROR;
}
}
__HAL_RCC_SYSCLK_CONFIG(RCC_ClkInitStruct->SYSCLKSource);
/* Get Start Tick */
tickstart = HAL_GetTick();
while (__HAL_RCC_GET_SYSCLK_SOURCE() != (RCC_ClkInitStruct->SYSCLKSource << RCC_CFGR_SWS_Pos))
{
if ((HAL_GetTick() - tickstart) > CLOCKSWITCH_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
}
#if defined(FLASH_ACR_LATENCY)
/* Decreasing the number of wait states because of lower CPU frequency */
if (FLatency < __HAL_FLASH_GET_LATENCY())
{
/* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
__HAL_FLASH_SET_LATENCY(FLatency);
/* Check that the new number of wait states is taken into account to access the Flash
memory by reading the FLASH_ACR register */
if (__HAL_FLASH_GET_LATENCY() != FLatency)
{
return HAL_ERROR;
}
}
#endif /* FLASH_ACR_LATENCY */
/*-------------------------- PCLK1 Configuration ---------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
{
assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB1CLKDivider));
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_ClkInitStruct->APB1CLKDivider);
}
/*-------------------------- PCLK2 Configuration ---------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
{
assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB2CLKDivider));
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, ((RCC_ClkInitStruct->APB2CLKDivider) << 3));
}
/* Update the SystemCoreClock global variable */
SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE) >> RCC_CFGR_HPRE_Pos];
/* Configure the source of time base considering new system clocks settings*/
HAL_InitTick(uwTickPrio);
return HAL_OK;
}
在函数的最后两行,注意这一句:
SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE) >> RCC_CFGR_HPRE_Pos];
全局变量SystemCoreClock做了修改!这个全局变量是不是很眼熟呢?他就是上面我们分析HSI时定义的全局变量16000000
这里的赋值语句含义是:更新完系统的时钟源后,需要更新SystemCoreClock变量,更新后这个变量值变为72000000!!!(可以通过printf函数打印来验证)
紧接着再一次调用了HAL_InitTick()函数
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
具体流程上文已分析过,区别只是在于全局变量从16000000变成了72000000,之后再通过HAL_SYSTICK_Config()函数更新Systick -> LOAD = 72K - 1,这样便保证了Systick时钟周期时钟为1ms!!!
二、HAL_Delay()函数实现原理以及中断服务函数不能使用延时函数的原因
2.1 HAL_Delay()实现原理
上面啰嗦完SysTick的配置过程,现在迎来重头戏,HAL_Delay()函数的实现。注意,这个HAL_Delay()用__weak关键字修饰,后面会讨论它重写的情形。
__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)
{
}
}
其中调用了HAL_GetTick()函数,作用是实时返回全局变量uwTick的值。
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
那么这个uwTick在系统中是如何操作的呢?上文所述的SysTick的CTRL寄存器有一个SysTick中断使能位开启,而在Systick的中断服务函数中,uwTick就会每隔1ms的中断周期进行自增
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
ISR中调用了HAL_IncTick()函数,该函数为:
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
其中uwTickFreq的值为1
__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)
{
}
}
因此再重新分析HAL_Delay()函数的实现,它首先记录初始时刻的uwTick值,然后记录待延时的变量并进行加一毫秒补偿,并开始通过循环的方式不断查询差值是否达到了等待的时间。当达到时间后,便退出这个函数。至于补偿的策略,个人理解是应对这样的一个情形:
假设uwTick在整1ms,2ms,3ms触发中断进行自增,但是实际运行程序时在1.8ms处调用HAL_Delay(1),此时的tickstart为1,wait为2,所以至少要在3ms处(此时HAL_GetTick()为3,实际延时1.2ms)才可以判定当时的差值是合理的(近似1ms)而不能在2ms处(此时HAL_GetTick()为2)判定当时的差值是合理的(实际只有0.2ms)
但是恰巧如果在中断服务函数执行,uwTick自增之后调用了HAL_Delay()的话,此时误差会最大,将近多延时了1ms!!!因此HAL_Delay()函数传递的参数最好尽可能大(即延时时间尽可能大),这样可以减小误差(百分比)!如果实在需要诸如1ms、2ms的需求,可以采用delay_us(1000)或者delay_us(2000)即采用us延迟的方法!当然如果还需要更精确的延时,那就乖乖使用定时器中断吧,别用delay函数了(狗头)
2.2 中断服务函数中不能使用HAL_Delay()的原因
我们知道了HAL_Delay()实现是基于Systick中断的,然而Systick中断的优先级在整个系统中是最低的!(抢占优先级15),因此如果在某外设的中断服务函数中调用HAL_Delay()函数,则会发生在高优先级中断中等待低优先级中断的情况,结果就是永远也等不来,因为低优先级中断无法打断高优先级中断,此时uwTick值是不会再因为触发Systick中断而自增的,所以永远也等不到需要延迟时间的到来,程序就会在HAL_Delay()中的while()函数里无限循环...
三、正点原子delay_us()实现原理及HAL_Delay()重写
3.1 正点原子delay_us()实现原理
那么问题来了,上面所说的HAL库自带的HAL_Delay()是毫秒级函数,其采用的时基是Systick,而Systick的最小分辨率即为1ms,那么如何才能实现微妙级的延时呢?参考正点原子delay代码实现:
经过前文漫长的分析,现在再看这段代码已经不再是问题了,正点原子是如何实现的呢?也是通过修改Systick的相关配置!现在来分析一下:
首先是delay_init()初始化函数,在f1开发环境中,系统时钟为72M,因此传入参数sysclk为72
void delay_init(uint16_t sysclk)
{
#if SYS_SUPPORT_OS /* 如果需要支持OS. */
uint32_t reload;
#endif
SysTick->CTRL = 0; /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); /* SYSTICK使用内核时钟源8分频,因systick的计数器最大值只有2^24 */
g_fac_us = sysclk / 8; /* 不论是否使用OS,g_fac_us都需要使用,作为1us的基础时基 */
#if SYS_SUPPORT_OS /* 如果需要支持OS. */
reload = sysclk / 8; /* 每秒钟的计数次数 单位为M */
reload *= 1000000 / delay_ostickspersec; /* 根据delay_ostickspersec设定溢出时间
* reload为24位寄存器,最大值:16777216,在9M下,约合1.86s左右
*/
g_fac_ms = 1000 / delay_ostickspersec; /* 代表OS可以延时的最少单位 */
SysTick->CTRL |= 1 << 1; /* 开启SYSTICK中断 */
SysTick->LOAD = reload; /* 每1/delay_ostickspersec秒中断一次 */
SysTick->CTRL |= 1 << 0; /* 开启SYSTICK */
#endif
}
在裸机不带OS的情况下,运行三行代码:
首先将Systick的CTRL寄存器全部置零(之前是bit0、bit1、bit2为1),选择HCLK的八分频、关闭Systick中断,关闭Systick定时器。
其次调用HAL_SYSTICK_CLKSourceConfig()函数重新配置Systick时钟源!参数限定两个,分别为:
#define SYSTICK_CLKSOURCE_HCLK_DIV8 0x00000000U
#define SYSTICK_CLKSOURCE_HCLK 0x00000004U
之后定义全局变量g_fac_us,赋值为72 / 8 = 9
SysTick->CTRL = 0; /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); /* SYSTICK使用内核时钟源8分频,因systick的计数器最大值只有2^24 */
g_fac_us = sysclk / 8; /* 不论是否使用OS,g_fac_us都需要使用,作为1us的基础时基 */
我们来看一下HAL_SYSTICK_CLKSourceConfig()函数:
void HAL_SYSTICK_CLKSourceConfig(uint32_t CLKSource)
{
/* Check the parameters */
assert_param(IS_SYSTICK_CLK_SOURCE(CLKSource));
if (CLKSource == SYSTICK_CLKSOURCE_HCLK)
{
SysTick->CTRL |= SYSTICK_CLKSOURCE_HCLK;
}
else
{
SysTick->CTRL &= ~SYSTICK_CLKSOURCE_HCLK;
}
}
破案了,这个函数实际上就是用来配置Systick的CTRL寄存器的bit2的,传入参数为SYSTICK_CLKSOURCE_HCLK_DIV8,则CTRL的bit2为0,选择外部时钟源STCLK,根据CUBEMX配置此时STCLK为HCLK的八分频!即9M(实际上如果这里CubeMX配置不分频的话,实际上也会分频!)
再来看delay_us()函数的实现,Systick的RELOAD寄存器装载值为nus * g_fac_us因为此时Systick的周期为1/9M,因此延迟nus需要等待nus*9个计数的时间!(g_fac_us = 9)
然后清空CURRENT寄存器的值(val = 0)之后再使CTRL寄存器的bit0置1,即开启Systick定时器,注意此时CTRL寄存器的bit1 = 0,即Systick的中断已经关闭了,如果再运行HAL_Delay()将永远等不到所需等待时间的到来!!!结果就是程序卡死!!!(因此HAL_Delay()需要重写)
再之后就是等待查询CTRL的bit16,如果为1,就说明已经计时结束,再将CTRL的bit0清零,即关闭Systick定时器,再将CURRENT值清空。
void delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = nus * g_fac_us; /* 时间加载 */
SysTick->VAL = 0x00; /* 清空计数器 */
SysTick->CTRL |= 1 << 0 ; /* 开始倒数 */
do
{
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */
SysTick->CTRL &= ~(1 << 0) ; /* 关闭SYSTICK */
SysTick->VAL = 0X00; /* 清空计数器 */
}
3.2 HAL_Delay()重写
上文分析过如果采用正点原子delay_us()函数(实际上是调用了delay_init()函数)将导致HAL库自带的ms级延时函数HAL_Delay()无法使用的问题,那么我们该如何实现ms级延时呢?上文说过,HAL库自带的HAL_Delay()是用关键字__weak修饰的,因此我们可以重写它!思路就是采用delay_us的方法来实现delay_ms
static void delay_ms(uint16_t nms)
{
uint16_t repeat = nms / 1000;
uint16_t remain = nms % 1000;
while(repeat --) delay_us(1000 * 1000);
while(remain --) delay_us(1000);
}
void HAL_Delay(uint32_t Delay)
{
delay_ms(Delay);
}
具体原理在正点原子的网课上都有讲,我就不再赘述了。