目录
- 前言
- 一、代码部分
- 二、使用和验证
-
- 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)
#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;
}
}
}
}
void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
//
// 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.引入头文件
代码如下(示例):
#include "main.h"
#include "../Delay/Delay.h" //引入头文件
//...
2.初始化
代码如下(示例):
//...
MX_GPIO_Init();
delay_init(); //初始化
//...
3.使用和验证
(示例)
所用开发板为 野火指南者 STM32F103VET6 开发板。
工程优化等级为默认的None,所测输出引脚为PC13和PC4
先测试什么也不做时输出的脉冲周期(此时系统时钟为72M)
//...
while (1)
{
GPIOC->BSRR = GPIO_PIN_13; //PC13为高
GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u; //PC13为低
}
//...
测得此时的脉冲周期
延时500us时
//...
while (1)
{
GPIOC->BSRR = GPIO_PIN_13; //PC13为高
delay_us(500);
GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u; //PC13为低
}
//...
延时50us时
//...
while (1)
{
GPIOC->BSRR = GPIO_PIN_13; //PC13为高
delay_us(50);
GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u; //PC13为低
}
//...
延时10us时
//...
while (1)
{
GPIOC->BSRR = GPIO_PIN_13; //PC13为高
delay_us(10);
GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u; //PC13为低
}
//...
加入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
//...
修改测试任务
//...
void StartDefaultTask(void *argument)
{
for(;;)
{
GPIOC->BSRR = GPIO_PIN_13;
delay_us(50); //PC13高电平时间为50us
GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16u;
}
}
void StartTask02(void *argument)
{
for(;;)
{
GPIOC->BSRR = GPIO_PIN_4;
osDelay(1); //PC4高电平时间为1ms
GPIOC->BSRR = (uint32_t)GPIO_PIN_4 << 16u;
}
}
//...
如图所示微秒级延时仍然工作正常
三、可移植性
//...
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:
//...
uwTimclock = 2*HAL_RCC_GetPCLK1Freq();
uwPrescalerValue = (uint32_t) ((uwTimclock / 1000000U) - 1U);
//...
因此本方案在绝大多数由cube生成工程的情况下应该是通用的。
总结
经过实验,我们发现本方案实现了精度还算可以接受的微秒级延时,不过本方案的延时方式和HAL_Delay差不多,不建议在任务中过多地调用。
不同时基下的初始化过程建议参阅HongAndYi大佬写的
《HAL和FreeRTOS的基础时钟》