STM32CubeMX滴答定时器Systick的相关问题分析(含HAL_Delay函数分析与重写、微秒延时函数delay_us实现原理)

 参考资料:1. 正点原子

2.http://t.csdnimg.cn/dhPeN

摘要:本篇文章首先讨论了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);
}

具体原理在正点原子的网课上都有讲,我就不再赘述了。

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值