? ? ? 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这两个文件并添加到工程中即可。 注意:使用DWT来实现延迟有一个好处,就是如果你单片机主频足够高,可以精确实现us级延时。 ? ? 下面的代码是以STM32H743为例,使用的是HAL库,如果要适配其它型号STM32,直接修改对应的头文件即可。
core_delay.c代码如下:
#include "core_delay.h"
#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)
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
DEM_CR |= (uint32_t)DEM_CR_TRCENA;
DWT_CYCCNT = (uint32_t)0u;
DWT_CR |= (uint32_t)DWT_CR_CYCCNTENA;
return HAL_OK;
}
uint32_t CPU_TS_TmrRd(void)
{
return ((uint32_t)DWT_CYCCNT);
}
uint32_t HAL_GetTick(void)
{
return ((uint32_t)DWT_CYCCNT/SysClockFreq*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)
{
if(tnow > told)
{
tcnt += tnow - told;
}
else
{
tcnt += UINT32_MAX - told + tnow;
}
told = tnow;
if(tcnt >= ticks)break;
}
}
}
core_delay.h代码如下:
#ifndef __CORE_DELAY_H
#define __CORE_DELAY_H
#include "stm32f4xx.h"
#define GET_CPU_ClkFreq() HAL_RCC_GetSysClockFreq()
#define SysClockFreq (400000000)
#define CPU_TS_INIT_IN_DELAY_FUNCTION 0
uint32_t CPU_TS_TmrRd(void);
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority);
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
参考: ? ? 1.cubemx在使用freertos的时候为何推荐使用除systick以外的timebase
? ? 2.野火《RT-Thread 内核实现与应用开发实战——基于 STM32》
?
|