在MCU编程中,微秒延时和毫秒延时使用最为频繁,在RTOS中,毫秒延时可以由系统提供,可是微秒延时却需要开发人员编写。本文基于STM32F407ZG芯片实现几种微秒延时操作。
1、定时器延时
STM32F407里提供的定时器有:
- 高级定时器:TIM1和TIM8,16位定时器
- 通用定时器:TIM2到TIM5,TIM9到TIM14,16位定时器
- 基本定时器:TIM6和TIM7,16位定时器
时钟总线:
本文采用基本定时器TIM6来作为微秒延时的定时器。
定时器6初始化:
/**
* @brief TIM6初始化
* @note 84MHz时钟频率,时钟分频84,每个CNT计数1us,16位定时器,最大可计数65535us
*/
void tim6Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE); /* 初始化时钟 */
TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);
TIM_TimeBaseInitStructure.TIM_Period = 65535; /* 最大计数 */
TIM_TimeBaseInitStructure.TIM_Prescaler = (84-1); /* 定时器分频,分频后频率为1MHz,最低1us定时 */
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Down; /* 向下计数 */
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; /* 时钟分频 */
TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure); /* 初始化 */
TIM_Cmd(TIM6,ENABLE); /* 使能定时器 */
}
关闭定时器:
/**
* @brief 关闭定时器
* @note 延时结束后关闭定时器
*/
void tim6Stop(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,DISABLE);
TIM_Cmd(TIM6,DISABLE);
}
微秒延时:
/**
* @brief 微秒延时
* @param uint32_t nUs 延时数
*/
void delayUs(uint32_t nUs)
{
volatile uint32_t startCnt = 0;
(nUs >= 65535) ? (nUs = 65536) : (nUs = nUs); /* 16位定时器,最大计数延时65535us */
tim6Init(); /* 初始化定时器 */
startCnt = TIM6->CNT;
while((TIM6->CNT-startCnt) < nUs);
tim6Stop();
}
以上代码在单线程裸机系统中,在准确度上可以满足MCU的微秒延时使用;但是在多线程的RTOS中,会因为在多线程中同时调用微秒延时,导致线程A在while里等待延时时,定时器被线程B关闭,CPU无法释放。
微秒延时(代码段保护):
/**
* @brief 微秒延时
* @param uint32_t nUs 延时数
*/
void delayUs(uint32_t nUs)
{
volatile uint32_t startCnt = 0;
(nUs >= 65535) ? (nUs = 65535) : (nUs = nUs); /* 16位定时器,最大计数延时65535us */
rt_enter_critical(); /* 进入保护段,禁止系统调度 */
tim6Init(); /* 初始化定时器 */
startCnt = TIM6->CNT;
while((TIM6->CNT-startCnt) < nUs);
tim6Stop();
rt_exit_critical(); /* 离开保护段 */
}
2、嘀嗒定时器(中断方式)
STM32的cotex内核上提供了一个嘀嗒定时器(SysTick),嘀嗒定时器是一个24位的计时器,向下计数的方式,当计数倒数到0时,将从RELOAD寄存器里自动重装载值。只要CTRL寄存器的ENABLE位不被清除,则嘀嗒定时器用不停止。
可以用嘀嗒定时器为系统提供微秒延时。
嘀嗒定时器初始化:
/**
* @brief 嘀嗒定时器初始化
* @param {type}
* @retval none
*/
void sysTickInit(void)
{
RCC_GetClocksFreq(&RCC_Clocks); /* 获取系统时钟 */
SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000); /* 配置嘀嗒为1ms */
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; /* 关闭嘀嗒 */
}
延时实现:
volatile uint32_t delayTimer;
/**
* @brief 嘀嗒中断服务函数
* @param {type}
* @retval none
*/
void SysTick_Handler(void)
{
if(delayTimer)
{
delayTimer --;
}
}
/**
* @brief 延时函数
* @param {type}
* @retval none
*/
void delay(uint32_t nTimer)
{
delayTimer = nTimer;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; /* 开启嘀嗒 */
while(delayTimer);
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; /* 关闭嘀嗒 */
}
3、嘀嗒定时器(查询)
中断方式会频繁打断系统,可以使用查询嘀嗒计数器的方式完成延时。
在此贴上一段RT-TRHEAD的嘀嗒初始化函数:
/**
* @brief 嘀嗒定时器初始化
* @note 使用RT-THREAD的嘀嗒初始化,嘀嗒超时事件1ms,嘀嗒时钟8分频
*/
void SysTick_Configuration(void)
{
RCC_ClocksTypeDef rcc_clocks;
rt_uint32_t cnts;
RCC_GetClocksFreq(&rcc_clocks);
cnts = (rt_uint32_t)rcc_clocks.HCLK_Frequency / 1000;
cnts = cnts / 8;
SysTick_Config(cnts);
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
}
/**
* This is the timer interrupt service routine.
*
*/
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase(); /* 系统时间片累加 */
/* leave interrupt */
rt_interrupt_leave();
}
时钟8分频,每1ms完成一次中断,每1ms完成一次系统时间片的累加计数。
初始化后的嘀嗒重装载值(SysTick->RELOAD)为:
168000000
/
1000
/
8
=
21000
168000000 / 1000 / 8 = 21000
168000000/1000/8=21000
嘀嗒计数满21000个值,时间刚好是1ms,故每一次计数的时间为:
(
0.001
s
/
21000
)
s
(0.001 s / 21000) s
(0.001s/21000)s
嘀嗒定时器延时实现:
/**
* @brief 微秒延时
* @param {type}
* @retval none
*/
void delayUs(uint32_t nUs)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=SysTick->LOAD;
rt_enter_critical();
ticks = nUs*(168 >> 3); //168 MHZ
told = SysTick->VAL;
while(1)
{
tnow = SysTick->VAL;
if(tnow != told)
{
if(tnow < told)
{
tcnt += told - tnow;
}
else
{
tcnt += reload - tnow + told;
}
told = tnow;
if(tcnt >= ticks)
{
break;
}
}
}
rt_exit_critical();
}
最低的延时时间为1us。
-
完成1us需要嘀嗒定时器VAL的计数值:
前面计算的每一次计数时间为:(0.001 s / 21000) s,
完成1us需要的计数为:
t i c k s = 1 u s / ( 0.001 s / 21000 ) = 21 ticks = 1us / (0.001s / 21000) = 21 ticks=1us/(0.001s/21000)=21