stm32cubemx设置系统时基的两种方式和区别(systick和timer)

1.用systick作为时基

        用systick作为时基的时候,HAL_Init中的HAL_InitTick函数会通过HAL_SYSTICK_Config函数对systick中断的时间配置为1ms。也就是说用systick作为时基,就要利用到systick的中断,在SysTick_Handler这个中断中,每进一次中断就加一次uwTick的值。例如F429的频率为180MHz,那么在设置SysTick->LOAD的值的时候,就应该设置为180K,才能让中断为1ms一次。这样说比较抽象,看代码讲解吧。

__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;
}

这个就是HAL_Init在执行过程中会执行到的HAL_InitTick函数,需要说明的是,这个是默认选择了systick为时基的,如果我们在用stm32cubemx设置时基为tim的时候,这个弱定义的函数就会被覆盖,重新定义的HAL_InitTick函数是下面第2部分用tim作为时基用到的。

以systick为时基用到的就是上面的函数实现,只看第一个if语句

首先SystemCoreClock在还没有执行SystemClock_Config这个系统时钟配置之前,被默认设置为16M,当然这个16M不用管,因为在执行完SystemClock_Config后,会被赋值为180M。这个180M是在SystemClock_Config函数里面设置的,当然是因为你在配置时钟的时候做了相应的设置,然后经过HAL_RCC_ClockConfig这个函数,在这个函数中对SystemClock_Config做了相应的赋值,才得到的180M。

  /* 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;

上面的语句就是HAL_RCC_ClockConfig函数内对SystemClock_Config赋值的语句,可以看到,在赋值完以后,又调用了HAL_InitTick函数,也就是说又要调用一次HAL_SYSTICK_Config来配置一下systick的中断,上面没讲到怎么配置中断,那就直接看代码,最后HAL_SYSTICK_Config函数会进到下面的函数中,可以看到在赋值重装载寄存器,并且设置了systick中断优先级,最后就是使能中断了,这里重装载寄存器值是180K,因为180MHz周期是1/180us,要是的HAL_Delay是1ms的延时,那就得1/180us * 180k = 1ms,也就是计数值跑完刚好1ms。

__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 */
}

2.用tim作为时基

tim作时基的情况,会重新有个文件,比如笔者用的tim1,HAL_InitTick的实现是在stm32f4xx_hal_timbase_tim.c文件中,而一systick为时基的时候,HAL_InitTick是在stm32f4xx_hal.c文件中实现的,而且还是个弱定义的,也就是为了不以systick为时基的时候能去重新定义做的准备。

HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  RCC_ClkInitTypeDef    clkconfig;
  uint32_t              uwTimclock = 0U;

  uint32_t              uwPrescalerValue = 0U;
  uint32_t              pFLatency;
  HAL_StatusTypeDef     status;

  /* Enable TIM1 clock */
  __HAL_RCC_TIM1_CLK_ENABLE();

  /* Get clock configuration */
  HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);

  /* Compute TIM1 clock */
  uwTimclock = 2*HAL_RCC_GetPCLK2Freq();

  /* Compute the prescaler value to have TIM1 counter clock equal to 1MHz */
  uwPrescalerValue = (uint32_t) ((uwTimclock / 1000000U) - 1U);

  /* Initialize TIM1 */
  htim1.Instance = TIM1;

  /* Initialize TIMx peripheral as follow:
  + Period = [(TIM1CLK/1000) - 1]. to have a (1/1000) s time base.
  + Prescaler = (uwTimclock/1000000 - 1) to have a 1MHz counter clock.
  + ClockDivision = 0
  + Counter direction = Up
  */
  htim1.Init.Period = (1000000U / 1000U) - 1U;
  htim1.Init.Prescaler = uwPrescalerValue;
  htim1.Init.ClockDivision = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

  status = HAL_TIM_Base_Init(&htim1);
  if (status == HAL_OK)
  {
    /* Start the TIM time Base generation in interrupt mode */
    status = HAL_TIM_Base_Start_IT(&htim1);
    if (status == HAL_OK)
    {
    /* Enable the TIM1 global Interrupt */
        HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
      /* Configure the SysTick IRQ priority */
      if (TickPriority < (1UL << __NVIC_PRIO_BITS))
      {
        /* Configure the TIM IRQ priority */
        HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, TickPriority, 0U);
        uwTickPrio = TickPriority;
      }
      else
      {
        status = HAL_ERROR;
      }
    }
  }

 /* Return function status */
  return status;
}

这里和以systick的方式看起来不一样,其实还是一样的,其实HAL_InitTick的目的都一样就是为将时基配为1ms然后供给给HAL_Delay来做1ms的延时,那么既然如此,那是不是可以推测出这里tim就是要设置一个1ms进一次中断的配置,进一次tim的中断,就还是将HAL_Delay里面那个用来对比延时时长的全局变量加1,这个很好验证,就是观察一下tim的中断处理函数里面做了什么,HAL库里就直接看tim的回调函数就行了:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

HAL_IncTick从函数名都能看出,这就是在对tick数进行增加

__weak void HAL_IncTick(void)
{
  uwTick += uwTickFreq;
}

 也就是这个uwTick数在进一次tim中断后就加1,进一次就是1ms,HAL_Delay里

__weak uint32_t HAL_GetTick(void)
{
  return uwTick;
}

用HAL_GetTick来获取这个值,然后用每次获取的这个值减去刚进入HAL_Delay的时候的那个时间的这个变量的值做减法,就是进了多少次tim的中断,也就是过了多少ms,用这个两个的差值与HAL_Delay传入的参数值(这个参数值就是延时多少ms的数值)做对比,当比它大的时候,结束while循环,也就是延时时间到了,退出函数。

__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)
  {
  }
}

在使用STM32CubeMX生成项目框架时,有时会遇到main函数需要清理或简化的情况。这通常是为了更好地管理代码结构、提高可读性便于维护。 ### 清除 `main` 函数的方法 #### 1. 移动初始化代码到其他文件 将 `main.c` 中的硬件初始化其他配置移到独立的 `.c` 文件中,例如创建一个新的源文件如 `hw_init.c` 或者按模块分割成多个文件(比如 `uart.c`, `timer.c` 等)。这样做的好处是可以让主程序保持简洁,并且每个功能都有明确的责任归属。 ```cpp // hw_init.c 示例 void Hardware_Init(void) { // HAL 库初始化 HAL_Init(); // System Clock Configuration 配置系统时钟 // 初始化所有外设 (通过 CubeMX 自动生成) } ``` 然后,在 `main.c` 只保留必要的内容: ```cpp int main(void){ /* USER CODE BEGIN 1 */ /* 用户代码开始部分 */ /* USER CODE END 1 */ /* MCU Configuration----------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ Hardware_Init(); while (1){ /* USER CODE BEGIN WHILE */ // 主循环内用户可以添加自己的业务逻辑代码 /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } } /* 当然别忘了包含新创建文件头文件以及声明相应的外部函数 */ #include "hw_init.h" extern void Hardware_Init(void); ``` #### 2. 使用 FreeRTOS 或其它实时操作系统(RTOS) 如果你的应用场景适合采用 RTOS,则可以把大部分任务分配给各个线程去处理而不是全部堆积于 `main()` 内部;这样做不仅能让主线程变得非常干净而且还能充分利用 CPU 资源并行运行多个进程。 对于 STM32 来说,可以直接利用 STM32CubeMX 的中间件库集成支持多种主流 RTOS 方案,包括但不限于: - **FreeRTOS**: 广泛应用于嵌入式系统的轻量级 OS。 - **CMSIS-RTOS v2** : ARM 官方提供的 CMSIS 标准下的 RTOS API 接口版本2。 选择合适的方案后只需按照官方文档指导步骤操作即可完成移植工作。 --- 综上所述,针对 stm32cube mx 所产生的复杂 `main()` ,我们有两种常见策略来进行优化整理:一是拆分重组冗长繁琐的任务至各自专属区域二是引入更高级别的调度机制即RTOS来接管整个应用程序流程控制权。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值