使用HAL库和FreeRTOS时,使用TIM1作为HAL库时基定时器

HAL库和FreeRTOS默认都使用了SysTick定时器,由于FreeRTOS和精确延时都需要SysTick定时器协助,因此,HAL库只能使用其它定时器作为时基定时器

原因如下:

1、HAL库默认是使用SysTick定时器作为库的时钟

HAL库为了不独占SysTick定时器,使用“__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)”来初始化时基定时器,这样HAL_StatusTypeDef HAL_InitTick(),这样,用户就可以重构这个函数。

HAL库默认的“时基函数”如下:

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  HAL_StatusTypeDef  status = HAL_OK;

  if (uwTickFreq != 0U)
  {
    /* Configure the SysTick to have interrupt in 1ms time basis*/
    if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) == 0U)
    {//HAL_SYSTICK_Config()用来初始化SysTick定时器
      /* Configure the SysTick IRQ priority */
      if (TickPriority < (1UL << __NVIC_PRIO_BITS))
      {//在“stm32g474xx.h”中定义“__NVIC_PRIO_BITS=4”
        HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
        uwTickPrio = TickPriority;
      }
      else
      {
        status = HAL_ERROR;
      }
    }
    else
    {
      status = HAL_ERROR;
    }
  }
  else
  {
    status = HAL_ERROR;
  }

  /* Return function status */
  return status;
}

2、FreeRTOS默认使用SysTick定时器作为系统时钟

在port.c中有一个__attribute__(( weak )) void vPortSetupTimerInterrupt( void ),用来配置SysTick定时器。为了不独占SysTick定时器,它和HAL库一样,使用“__weak”修饰,允许用户使用其它定时器实现系统时钟

1)、宏定义

先了解port.c中一些重要的宏定义,有助于理解FreeRTOS是不是使用了SysTick定时器。如下:

#ifndef configSYSTICK_CLOCK_HZ
    #define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ  //使用系统时钟频率
    /* Ensure the SysTick is clocked at the same frequency as the core. */

    #define portNVIC_SYSTICK_CLK_BIT    ( 1UL << 2UL )
    //用来配置SysTick->CTRL寄存器的bit2,选择SysTick定时器的时钟源
#else
    /* The way the SysTick is clocked is not modified in case it is not the same
    as the core. */

    #define portNVIC_SYSTICK_CLK_BIT    ( 0 )
#endif

/* Constants required to manipulate the core.  Registers first... */
#define portNVIC_SYSTICK_CTRL_REG            ( * ( ( volatile uint32_t * ) 0xe000e010 ) )
//0xe000e010为SysTick->CTRL寄存器地址
#define portNVIC_SYSTICK_LOAD_REG            ( * ( ( volatile uint32_t * ) 0xe000e014 ) )
//0xe000e014为SysTick->LOAD寄存器地址
#define portNVIC_SYSTICK_CURRENT_VALUE_REG    ( * ( ( volatile uint32_t * ) 0xe000e018 ) )
//0xe000e018为SysTick->VAL寄存器地址
#define portNVIC_SYSPRI2_REG                ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
/* ...then bits in the registers. */
#define portNVIC_SYSTICK_INT_BIT            ( 1UL << 1UL )    //使能SYSTICK中断
#define portNVIC_SYSTICK_ENABLE_BIT            ( 1UL << 0UL )  //使能SysTick计数

#define portNVIC_SYSTICK_COUNT_FLAG_BIT        ( 1UL << 16UL )
#define portNVIC_PENDSVCLEAR_BIT             ( 1UL << 27UL )
#define portNVIC_PEND_SYSTICK_CLEAR_BIT        ( 1UL << 25UL )

2)、SysTick定时器初始化

FreeRTOS默认的系统时钟函数如下:

__attribute__(( weak )) void vPortSetupTimerInterrupt( void )
{
    /* Calculate the constants required to configure the tick interrupt. */
    #if( configUSE_TICKLESS_IDLE == 1 )
    {
        ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
        xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
        ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
    }
    #endif /* configUSE_TICKLESS_IDLE */

    /* Stop and clear the SysTick. */
    portNVIC_SYSTICK_CTRL_REG = 0UL;
    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

    /* Configure SysTick to interrupt at the requested rate. */
    portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;//配置SysTick->LOAD寄存器
    portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );

    //portNVIC_SYSTICK_CLK_BIT表示设置STK_CTRL寄存器的bit2(CLKSOURCE),CLKSOURCE=1使用AHB时钟
    //portNVIC_SYSTICK_INT_BIT表示使能SYSTICK中断
    //portNVIC_SYSTICK_ENABLE_BIT表示使能SysTick计数  

}

3)、systick中断服务函数,1ms中断一次

//在“FreeRTOSConfig.h”定义SysTick_Handler的别名为“xPortSysTickHandler
void xPortSysTickHandler( void )
{
    /* The SysTick runs at the lowest interrupt priority, so when this interrupt
    executes all interrupts must be unmasked.  There is therefore no need to
    save and then restore the interrupt mask value as its value is already
    known. */

    portDISABLE_INTERRUPTS();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* A context switch is required.  Context switching is performed in
            the PendSV interrupt.  Pend the PendSV interrupt. */

            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    portENABLE_INTERRUPTS();
}

3、主程序使用SysTick定时器用作精确延时

为了能在程序中,使用SysTick定时器作为延时,我们没有直接调用vPortSetupTimerInterrupt(),而是使用delay_init()来替换vPortSetupTimerInterrupt(),使程序具有可读性。

#include "FreeRTOS.h"                    //FreeRTOS使用          
#include "task.h"

static uint8_t  fac_us=0;                            //us延时倍乘数               
static uint16_t fac_ms=0;                            //ms延时倍乘数,在ucos下,代表每个节拍的ms数
    
    
extern void xPortSysTickHandler(void);//xPortSysTickHandler()在port.c中

void My_delay_us(__IO uint32_t nCount);

/函数功能:SysTick定时器初始化
//SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8
//这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!
//SYSCLK:系统时钟频率
//在port.c中有一个vPortSetupTimerInterrupt(),用来配置SysTick定时器
//由于vPortSetupTimerInterrupt()没有被调用,所以不会被执行
//我们使用delay_init()来替换vPortSetupTimerInterrupt(),使程序具有可读性

void delay_init(void)
{
    uint32_t reload;

    fac_us=SystemCoreClock/1000000;
    //相当于将170MHz经过1000000分频用作SysTick的输入时钟,即周期为1us 

    reload=SystemCoreClock/1000000;
  //相当于将170MHz经过1000000分频用作SysTick的输入时钟,即周期为1us 
    reload*=1000000/configTICK_RATE_HZ;
    //在“FreeRTOSConfig.h”中configTICK_RATE_HZ为1000
    //SysTick溢出时间为1000*1us=1ms
    //reload为24位寄存器,最大值:16777216,在170MHz下,约合0.098s左右 
   
    fac_ms=1000/configTICK_RATE_HZ;                //代表OS可以延时的最少单位       

    SysTick->VAL   = 0UL;  //设置计数器值为0,Load the SysTick Counter Value
    HAL_SYSTICK_Config(reload);//配置SysTick定时器

//  SysTick->LOAD  = reload;
    //设置计数reload个输入时钟,则溢出一次    
//  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
//                   SysTick_CTRL_TICKINT_Msk   |
//                   SysTick_CTRL_ENABLE_Msk;
    //SysTick_CTRL_CLKSOURCE_Msk表示设置STK_CTRL寄存器bit2(CLKSOURCE),CLKSOURCE=1使用AHB时钟
    //SysTick_CTRL_TICKINT_Msk表示使能SYSTICK中断
    //SysTick_CTRL_ENABLE_Msk表示使能SysTick计数
    //Enable SysTick IRQ and SysTick Timer    

}

//systick中断服务函数,1ms中断一次
//在FreeRTOSConfig.h中,有一个宏定义”#define xPortSysTickHandler SysTick_Handler“
//xPortSysTickHandler()的函数体位于port.c中
//我们在这里实现SysTick_Handler(),那个宏定义就被我屏蔽了

void SysTick_Handler(void)
{    
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
        xPortSysTickHandler();
        //若带FreeRTOS系统,则xPortSysTickHandler()在port.c中,这个调用是为FreeRTOS服务的           
    }
}

//函数功能:延时nus
//nus:要延时的us数.    
//nus:0~204522252(最大值即2^32/fac_us)    
                                      
void delay_us(uint32_t nus)
{        
    uint32_t ticks;
    uint32_t told,tnow,tcnt=0;
    uint32_t reload=SysTick->LOAD;
    //读取SysTick的LOAD寄存器的值
    
    ticks=nus*fac_us; //计算需要的节拍数 
    told=SysTick->VAL;//刚进入时的计数器值
    while(1)
    {
        tnow=SysTick->VAL;//读SYSTICK计数器值    
        if(tnow!=told)
        {        
            if(tnow<told)tcnt+=told-tnow;    //注意:SYSTICK是一个递减的计数器.
            else tcnt+=reload-tnow+told;        
            told=tnow;
            if(tcnt>=ticks)break;            //时间超过/等于要延迟的时间,则退出.
        }        
    }                                            


//延时nms
//nms:要延时的ms数
//nms:0~65535

void delay_ms(uint32_t nms)
{    
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {        
        if(nms>=fac_ms)                        //延时的时间大于OS的最少时间周期 
        { 
               vTaskDelay(nms/fac_ms);             //FreeRTOS延时
        }
        nms%=fac_ms;                        //OS已经无法提供这么小的延时了,采用普通方式延时    
    }
    delay_us((uint32_t)(nms*1000));                //普通方式延时
}

4、总结

由于FreeRTOS和精确延时都需要SysTick定时器协助,因此,HAL库只能使用其它定时器作为时基定时器

假定使用TIM1,程序如下:

//HAL_InitTick()供HAL_Init()调用
//函数功能:将TIM1配置为向上计数,1ms中断一次
//TickPriority=TICK_INT_PRIORITY,滴答中断优先级为TICK_INT_PRIORITY=0

HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  RCC_ClkInitTypeDef    clkconfig;
  uint32_t              uwTimclock = 0;
  uint32_t              uwPrescalerValue = 0;
  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 = HAL_RCC_GetPCLK2Freq();
    //读取PCLK2的时钟频率,Return the PCLK2 frequency
    //若PCLK2的分频器值为1,则和SystemCoreClock的值相等

  /* 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;
    //定时器周期999
  HTIM1.Init.Prescaler = uwPrescalerValue;
    //设置TIM1预分频器为uwPrescalerValue
  HTIM1.Init.ClockDivision = 0;
    //设置时钟分频系数,TIM1_CR1中的CKD[9:8]=00b,tDTS=ttim_ker_ck;
    //溢出时间为(999+1)*1*170/170000000/1

  HTIM1.Init.CounterMode = TIM_COUNTERMODE_UP;

  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);
        //使能HTIM1更新中断
    if (status == HAL_OK)
    {
    /* Enable the TIM1 global Interrupt */
        HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn);//使能TIM1产生中断
      /* Configure the SysTick IRQ priority */
      if (TickPriority < (1UL << __NVIC_PRIO_BITS))
      {//若使用“NVIC中断分组4”,STM32G4XX uses 4 Bits for the Priority Levels
                //HAL库使用中断优先级组为4
                //在“stm32g474xx.h”中定义“__NVIC_PRIO_BITS=4”
                //Interrupt and exception vectors
        /* Configure the TIM IRQ priority */

        HAL_NVIC_SetPriority(TIM1_UP_TIM16_IRQn, TickPriority, 0U);
          //设置NVIC中断分组4:4位抢占优先级,0位响应优先级
          //选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
                //这里设置TIM1中断优先级为TickPriority

        uwTickPrio = TickPriority;//记录滴答时钟的中断优先级
      }
      else
      {//若没有使用“NVIC中断分组4”,则设置status
        status = HAL_ERROR;
      }
    }
  }

 /* Return function status */
  return status;
}


void HAL_SuspendTick(void)
{
  /* Disable TIM1 update Interrupt */
  __HAL_TIM_DISABLE_IT(&HTIM1, TIM_IT_UPDATE);//不使能定时器更新中断
}
void HAL_ResumeTick(void)
{
  /* Enable TIM1 Update interrupt */
  __HAL_TIM_ENABLE_IT(&HTIM1, TIM_IT_UPDATE);//使能定时器更新中断
}


//HAL_TIM_IRQHandler()调用HAL_TIM_PeriodElapsedCallback(),触发条件是TIM产生“向上溢出中断”
//TIM1_UP_TIM16_IRQHandler()调用了HAL_TIM_IRQHandler()函数
//TIM_DMAPeriodElapsedCplt也调用HAL_TIM_PeriodElapsedCallback()

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM1)
    {
    HAL_IncTick();//uwTick计数器加1,HAL_IncTick()位于stm32g4xx_hal.c中
  }
}

/**
  * @brief This function handles TIM1 update interrupt and TIM16 global interrupt.
  */
//TIM1更新中断和TIM16全局中断,他们公用一个中断源

void TIM1_UP_TIM16_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&HTIM1);
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值