无需另配定时器在STM32 HAL下实现微秒级延时(兼容FreeRTOS)



前言

接触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)
#endif

static 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()中被设为了1MHz
	fac_us = fac_ms / 1000;
#else
	fac_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高电平时间为50us
	GPIOC->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高电平时间为1ms
	GPIOC->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()中被设为了1MHz
	fac_us = fac_ms / 1000;
#else
	fac_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的基础时钟》

  • 30
    点赞
  • 155
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乙酸氧铍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值