贪吃蛇(C语言实现)

快速排序——C语言实现

  返回  

为什么HAL库中加入RTOS后,HAL库不能再使用systick作为时钟源?

2021/8/21 19:58:48 浏览:


HAL 库驱动中,由于某些外设的驱动需要使用超时判断(比如 I2C、 SPI、 SDIO 等),需要精确延时(精度为 1ms),使用的是 SysTick,但是在操作系统里面,我们需要使用 SysTick 来提供系统时基, 那么就冲突了。
HAL库的时钟源主要用于HAL_Delay()这个函数,这个函数用于实现延时以及上面说的外设驱动的超时判断。这个函数主要是靠不断读取SysTick计数器的值来实现延迟。但是加入了RTOS之后,RTOS强制将systick的中断设置为最低,假设在一个中断优先级比systick高的中断int_a中调用HAL_Delay()来进行延时,那么由于int_a中断优先级高于systick,从而导致systick无法抢占,也就无法增加计数器的值,就会导致int_a中断服务函数死等HAL_Delay()延时,无法退出,从而造成比int_a优先级低的中断服务都无法使用,系统也无法调度。

解决办法重写重写 HAL 库里面延时相关的函数,只有三个:HAL_InitTick()、 HAL_GetTick() 和 HAL_Delay(),这三个函数在 HAL 库中都是弱定义函数(函数开头带 __weak 关键字),弱定义的意思是只要用户重写这三个函数,原来 HAL 库里面的就会无效。

可以使用TIM6计时器来重新上面三个函数,如果是这样,直接在cubemx里面,将SYS里面的Timebase Source改成TIM6即可,如下图:
​​
在这里插入图片描述

如果不想使用TIM6,毕竟占用了一个定时器资源,那么可以考虑使用DWT来实现。下面这段及代码来之野火的《RT-Thread 内核实现与应用开发实战——基于 STM32》第16章。

在 Cortex-M 内核里面有一个外设叫 DWT(Data Watchpoint and Trace),该外设有一个 32 位的寄存器叫 CYCCNT,它是一个向上的计数器,记录的是内核时钟运行的个数,最长能记录的时间为:10.74s = 2 的 32 次方/400000000 (CYCNNT 从 0 开始计数到溢出,最长的延时时间与内核的频率有关,假设内核频率为 400M,内核时钟跳一次的时间大概为 1/400M=2.5ns),当 CYCCNT 溢出之后,会清 0 重新开始向上计数。这种延时方案不仅精确,而且还不占用单片机的外设资源,非常方便。所以 HAL 库里面刚刚讲到的需要重写的三个函数我们都基于 CYCCNT 的方案来实现。增加core_delay.c 和 core_delay.h这两个文件并添加到工程中即可。
下面的代码是以STM32H743为例,使用的是HAL库,如果要适配其它型号STM32,直接修改对应的头文件即可。

core_delay.c代码如下:


/**

  ******************************************************************

  * @file    core_delay.c

  * @author  fire

  * @version V1.0

  * @date    2018-xx-xx

  * @brief   使用内核寄存器精确延时

  ******************************************************************

  * @attention

  *

  * 实验平台:野火 STM32H743开发板  

  * 论坛    :http://www.firebbs.cn

  * 淘宝    :https://fire-stm32.taobao.com

  *

  ******************************************************************

  */

  

#include "core_delay.h"   


 

/*

**********************************************************************

*         时间戳相关寄存器定义

**********************************************************************

*/

/*

 在Cortex-M里面有一个外设叫DWT(Data Watchpoint and Trace),

 该外设有一个32位的寄存器叫CYCCNT,它是一个向上的计数器,

 记录的是内核时钟运行的个数,最长能记录的时间为:

 10.74s=2的32次方/400000000

 (假设内核频率为400M,内核跳一次的时间大概为1/400M=2.5ns)

 当CYCCNT溢出之后,会清0重新开始向上计数。

 使能CYCCNT计数的操作步骤:

 1、先使能DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1使能

 2、使能CYCCNT寄存器之前,先清0

 3、使能CYCCNT寄存器,这个由DWT_CTRL(代码上宏定义为DWT_CR)的位0控制,写1使能

 */


 

#define  DWT_CR      *(__IO uint32_t *)0xE0001000

#define  DWT_CYCCNT  *(__IO uint32_t *)0xE0001004

#define  DEM_CR      *(__IO uint32_t *)0xE000EDFC


 

#define  DEM_CR_TRCENA                   (1 << 24)

#define  DWT_CR_CYCCNTENA                (1 <<  0)


 

/**

  * @brief  初始化时间戳

  * @param  无

  * @retval 无

  * @note   使用延时函数前,必须调用本函数

  */

HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)

{

    /* 使能DWT外设 */

    DEM_CR |= (uint32_t)DEM_CR_TRCENA;                

    /* DWT CYCCNT寄存器计数清0 */

    DWT_CYCCNT = (uint32_t)0u;

    /* 使能Cortex-M DWT CYCCNT寄存器 */

    DWT_CR |= (uint32_t)DWT_CR_CYCCNTENA;

  

    return HAL_OK;

}

/**

  * @brief  读取当前时间戳

  * @param  无

  * @retval 当前时间戳,即DWT_CYCCNT寄存器的值

  */

uint32_t CPU_TS_TmrRd(void)

{        

  return ((uint32_t)DWT_CYCCNT);

}

/**

  * @brief  读取当前时间戳

  * @param  无

  * @retval 当前时间戳,即DWT_CYCCNT寄存器的值

  */

uint32_t HAL_GetTick(void)

{        

  return ((uint32_t)DWT_CYCCNT/SysClockFreq*1000);

}


 

/**

  * @brief  采用CPU的内部计数实现精确延时,32位计数器

  * @param  us : 延迟长度,单位1 us

  * @retval 无

  * @note   使用本函数前必须先调用CPU_TS_TmrInit函数使能计数器,

            或使能宏CPU_TS_INIT_IN_DELAY_FUNCTION

            最大延时值为8秒,即8*1000*1000

  */

void CPU_TS_Tmr_Delay_US(uint32_t us)

{

  uint32_t ticks;

  uint32_t told,tnow,tcnt=0;

  /* 在函数内部初始化时间戳寄存器, */  

#if (CPU_TS_INIT_IN_DELAY_FUNCTION)  

  /* 初始化时间戳并清零 */

  HAL_InitTick(5);

#endif

  

  ticks = us * (GET_CPU_ClkFreq() / 1000000);  /* 需要的节拍数 */      

  tcnt = 0;

  told = (uint32_t)CPU_TS_TmrRd();         /* 刚进入时的计数器值 */

  while(1)

  {

    tnow = (uint32_t)CPU_TS_TmrRd();  

    if(tnow != told)

    { 

        /* 32位计数器是递增计数器 */    

      if(tnow > told)

      {

        tcnt += tnow - told;  

      }

      /* 重新装载 */

      else 

      {

        tcnt += UINT32_MAX - told + tnow; 

      } 

      

      told = tnow;

      /*时间超过/等于要延迟的时间,则退出 */

      if(tcnt >= ticks)break;

    }  

  }

}

/*********************************************END OF FILE**********************/

core_delay.h代码如下:

#ifndef __CORE_DELAY_H
#define __CORE_DELAY_H

#include "stm32f4xx.h"

/* 获取内核时钟频率 */
#define GET_CPU_ClkFreq()       HAL_RCC_GetSysClockFreq()
#define SysClockFreq            (400000000)
/* 为方便使用,在延时函数内部调用CPU_TS_TmrInit函数初始化时间戳寄存器,
  这样每次调用函数都会初始化一遍。
  把本宏值设置为0,然后在main函数刚运行时调用CPU_TS_TmrInit可避免每次都初始化 */  

#define CPU_TS_INIT_IN_DELAY_FUNCTION   0  


/*******************************************************************************
* 函数声明
******************************************************************************/
uint32_t CPU_TS_TmrRd(void);
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority);

//使用以下函数前必须先调用CPU_TS_TmrInit函数使能计数器,或使能宏CPU_TS_INIT_IN_DELAY_FUNCTION
//最大延时值为8秒
void CPU_TS_Tmr_Delay_US(uint32_t us);
#define HAL_Delay(ms)     CPU_TS_Tmr_Delay_US(ms*1000)
#define CPU_TS_Tmr_Delay_S(s)       CPU_TS_Tmr_Delay_MS(s*1000)


#endif /* __CORE_DELAY_H */

参考:
1.cubemx在使用freertos的时候为何推荐使用除systick以外的timebase

2.野火《RT-Thread 内核实现与应用开发实战——基于 STM32》

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号