目录
- 前言
 - 一、代码部分
 - 二、使用和验证
 - 1.引入头文件
 - 2.初始化
 - 3.使用和验证
 
- 三、可移植性
 - 总结
 
前言
接触HAL库差不多两年了,一直苦于HAL库没有自带微秒级的延时,网上的前辈们给出的解决方案要么是改写HAL_Delay的延时时间,要么就是额外占用一个定时器来实现,不太方便移植,以下是我给出的解决方案。
软件平台:STM32 Cube IDE 1.5.0
一、代码部分
Delay.c 代码如下
#include "main.h"#define USE_HAL_LEGACY
#include "stm32_hal_legacy.h"#define Timebase_Source_is_SysTick 1	//当Timebase Source为SysTick时改为1
//#define Timebase_Source_is_SysTick 0	//当使用FreeRTOS,Timebase Source为其他定时器时改为0#if	(!Timebase_Source_is_SysTick)extern TIM_HandleTypeDef htimx;		//当使用FreeRTOS,Timebase Source为其他定时器时,修改为对应的定时器#define Timebase_htim htimx#define Delay_GetCounter()		__HAL_TIM_GetCounter(&Timebase_htim)#define Delay_GetAutoreload()	__HAL_TIM_GetAutoreload(&Timebase_htim)
#else#define Delay_GetCounter()		(SysTick->VAL)#define Delay_GetAutoreload()	(SysTick->LOAD)
#endifstatic uint16_t fac_us = 0;
static uint32_t fac_ms = 0;/*初始化*/
void delay_init(void)
{
#if	(!Timebase_Source_is_SysTick)fac_ms = 1000000;				//作为时基的计数器时钟频率在HAL_InitTick()中被设为了1MHzfac_us = fac_ms / 1000;
#elsefac_ms = SystemCoreClock / 1000;fac_us = fac_ms / 1000;
#endif
}/*微秒级延时*/
void delay_us(uint32_t nus)
{uint32_t ticks = 0;uint32_t told = 0;uint32_t tnow = 0;uint32_t tcnt = 0;uint32_t reload = 0;reload = Delay_GetAutoreload();ticks = nus * fac_us;told = Delay_GetCounter();while (1){tnow = Delay_GetCounter();if (tnow != told){if (tnow < told){tcnt += told - tnow;}else{tcnt += reload - tnow + told;}told = tnow;if (tcnt >= ticks){break;}}}
}/*毫秒级延时*/
void delay_ms(uint32_t nms)
{uint32_t ticks = 0;uint32_t told = 0;uint32_t tnow = 0;uint32_t tcnt = 0;uint32_t reload = 0;reload = Delay_GetAutoreload();ticks = nms * fac_ms;told = Delay_GetCounter();while (1){tnow = Delay_GetCounter();if (tnow != told){if (tnow < told){tcnt += told - tnow;}else{tcnt += reload - tnow + told;}told = tnow;if (tcnt >= ticks){break;}}}
}/*重写HAL_Delay*/
void HAL_Delay(uint32_t Delay)
{uint32_t tickstart = HAL_GetTick();uint32_t wait = Delay;/*不太明白官方源码为啥这么写,会多延时1ms,注释掉后更准*/
//  /* Add a freq to guarantee minimum wait */
//  if (wait < HAL_MAX_DELAY)
//  {
//    wait += (uint32_t)(uwTickFreq);
//  }while ((HAL_GetTick() - tickstart) < wait){}
}
 
Delay.h 代码如下
#ifndef DELAY_H
#define DELAY_H#include "main.h"extern void delay_init(void);
extern void delay_us(uint32_t nus);
extern void delay_ms(uint32_t nms);#endif
 
二、使用和验证
1.引入头文件
代码如下(示例):
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "../Delay/Delay.h"		//引入头文件
/* USER CODE END Includes *///...
 
2.初始化
代码如下(示例):
  //.../* Initialize all configured peripherals */MX_GPIO_Init();/* USER CODE BEGIN 2 */delay_init();		//初始化/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE *///...
 
3.使用和验证
(示例)
 所用开发板为 野火指南者 STM32F103VET6 开发板。
 工程优化等级为默认的None,所测输出引脚为PC13和PC4
 
 先测试什么也不做时输出的脉冲周期(此时系统时钟为72M)
  //.../* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */GPIOC->BSRR = GPIO_PIN_13;					//PC13为高GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u;	//PC13为低}/* USER CODE END 3 *///...
 
测得此时的脉冲周期
 
 延时500us时
  //.../* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */GPIOC->BSRR = GPIO_PIN_13;					//PC13为高delay_us(500);GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u;	//PC13为低}/* USER CODE END 3 *///...
 

 延时50us时
  //.../* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */GPIOC->BSRR = GPIO_PIN_13;					//PC13为高delay_us(50);GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u;	//PC13为低}/* USER CODE END 3 *///...
 

 延时10us时
  //.../* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */GPIOC->BSRR = GPIO_PIN_13;					//PC13为高delay_us(10);GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u;	//PC13为低}/* USER CODE END 3 *///...
 

 加入FreeRTOS
 
 Cube建议我们更改时基源
 
 更改Timebase Source为其他定时器
 
 更新工程后修改Delay.c文件
#include "main.h"#define USE_HAL_LEGACY
#include "stm32_hal_legacy.h"//#define Timebase_Source_is_SysTick 1	//当Timebase Source为SysTick时改为1
#define Timebase_Source_is_SysTick 0	//当使用FreeRTOS,Timebase Source为其他定时器时改为0#if	(!Timebase_Source_is_SysTick)extern TIM_HandleTypeDef htim7;		//当使用FreeRTOS,Timebase Source为其他定时器时,修改为对应的定时器#define Timebase_htim htim7#define Delay_GetCounter()		__HAL_TIM_GetCounter(&Timebase_htim)#define Delay_GetAutoreload()	__HAL_TIM_GetAutoreload(&Timebase_htim)
#else#define Delay_GetCounter()		(SysTick->VAL)#define Delay_GetAutoreload()	(SysTick->LOAD)
#endif//...
 
修改测试任务
//...
/* USER CODE BEGIN Header_StartDefaultTask */
/*** @brief  Function implementing the defaultTask thread.* @param  argument: Not used* @retval None*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{/* USER CODE BEGIN 5 *//* Infinite loop */for(;;){GPIOC->BSRR = GPIO_PIN_13;delay_us(50);								//PC13高电平时间为50usGPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u;}/* USER CODE END 5 */
}/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void *argument)
{/* USER CODE BEGIN StartTask02 *//* Infinite loop */for(;;){GPIOC->BSRR = GPIO_PIN_4;osDelay(1);									//PC4高电平时间为1msGPIOC->BSRR = (uint32_t)GPIO_PIN_4 << 16u;}/* USER CODE END StartTask02 */
}
//...
 
如图所示微秒级延时仍然工作正常
 
 
三、可移植性
//...
/*初始化*/
void delay_init(void)
{
#if	(!Timebase_Source_is_SysTick)fac_ms = 1000000;				//作为时基的计数器时钟频率在HAL_InitTick()中被设为了1MHzfac_us = fac_ms / 1000;
#elsefac_ms = SystemCoreClock / 1000;fac_us = fac_ms / 1000;
#endif
}
//...
 
使用滴答定时器作为时基时自然不用多说,当使用其他定时器作为时基时(如本文的例子),Src目录下会自动生成一个stm32f1xx_hal_timebase_tim.c文件,其中的HAL_InitTick函数重构了在stm32f1xx_hal.c中的、__weak修饰的同名函数,它设置了所选定时器的时钟频率为1MHz:
//.../* Compute TIM7 clock */uwTimclock = 2*HAL_RCC_GetPCLK1Freq();/* Compute the prescaler value to have TIM7 counter clock equal to 1MHz */uwPrescalerValue = (uint32_t) ((uwTimclock / 1000000U) - 1U);
//...
 
因此本方案在绝大多数由cube生成工程的情况下应该是通用的。
总结
经过实验,我们发现本方案实现了精度还算可以接受的微秒级延时,不过本方案的延时方式和HAL_Delay差不多,不建议在任务中过多地调用。
不同时基下的初始化过程建议参阅HongAndYi大佬写的
 《HAL和FreeRTOS的基础时钟》