目录
前言
在使用通讯协议来驱动外设的时候需要遵循严格的时序逻辑,往往用到微秒(us)级别的延时,在裸机编程中可以使用SysTick定时器来实现;在FreeRTOS中,SysTick定时器则是用来作为FreeRTOS系统时钟的,但也可以用来做延时使用,只是与裸机编程下方式不同。
SysTick定时器寄存器
要使用SysTick定时器,首先要了解一下其相关寄存器。
裸机编程下实现
步骤:
1、根据延时时间和定时器所选时钟频率,计算出定时器要计数的时间数值;
2、将该数值加载到重装载寄存器中;
3、将当前值寄存器清零,打开定时器开始计数;
4、等待控制及状态寄存器的位16变为1;
5、关闭定时器,退出。
函数实现(正点原子例程):
//延时nus
//nus为要延时的us数.
//注意:nus的值,不要大于798915us(最大值即2^24/fac_us@fac_us=21)
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载,fac_us与时钟频率有关,例如:168Mhz时
//fac_us=168,168/168M=1us
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL; //读取控制及状态寄存器的值
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达,使能且位16为0(未计到0)
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
FreeRTOS中实现
在FreeRTOS中,SysTick定时器的重装载值是固定的,且定时器是一直在工作的。因此像裸机编程中将时间数值加载到重装载寄存器是不可行的。既然定时器是一直在工作的,那么肯定有一个时间段定时器的计数值与延时时间计数值相等。可以用此来实现精准延时。
步骤:
1、根据延时时间和定时器所选时钟频率,计算出定时器要计数的时间数值;
2、获取当前数值寄存器的数值;
3、以当前数值为基准开始计数;
4、当所计数值等于(大于)需要延时的时间数值时退出。
计数时间值的计算:
时钟周期 = 1 / 频率(s)
指定时间的计数周期(值):时间值 / 时钟周期 = 时间值 x 频率
以时间值1us,频率168Mhz为例:
计数周期(值)= 0.000001(s) x 168000000
= 168000000 / 1000000
= 168
程序实现:
初始版:
//使用该函数前需先初始化sysytick定时器
//参照正点原子例程进行修改
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,reload,tcnt=0;
reload = SysTick->LOAD; //获取重装载寄存器值
ticks = nus * (SystemCoreClock / 1000000); //计数时间值
told=SysTick->VAL; //获取当前数值寄存器值(开始时数值)
while(1)
{
tnow=SysTick->VAL; //获取当前数值寄存器值
if(tnow!=told) //当前值不等于开始值说明已在计数
{
if(tnow<told) //当前值小于开始数值,说明未计到0
tcnt+=told-tnow; //计数值=开始值-当前值
else //当前值大于开始数值,说明已计到0并重新计数
tcnt+=reload-tnow+told; //计数值=重装载值-当前值+开始值 (已
//从开始值计到0)
told=tnow; //更新开始值
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
//SystemCoreClock为系统时钟(system_stmf4xx.c中),通常选择该时钟作为
//systick定时器时钟,根据具体情况更改
改进版:
//在此函数中加入初始化sysytick定时器步骤
//参照正点原子例程进行修改
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,reload,tcnt=0;
if((0x0001&(SysTick->CTRL)) ==0) //定时器未工作
vPortSetupTimerInterrupt(); //初始化定时器
reload = SysTick->LOAD; //获取重装载寄存器值
ticks = nus * (SystemCoreClock / 1000000); //计数时间值
told=SysTick->VAL; //获取当前数值寄存器值(开始时数值)
while(1)
{
tnow=SysTick->VAL; //获取当前数值寄存器值
if(tnow!=told) //当前值不等于开始值说明已在计数
{
if(tnow<told) //当前值小于开始数值,说明未计到0
tcnt+=told-tnow; //计数值=开始值-当前值
else //当前值大于开始数值,说明已计到0并重新计数
tcnt+=reload-tnow+told; //计数值=重装载值-当前值+开始值 (已
//从开始值计到0)
told=tnow; //更新开始值
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
//SystemCoreClock为系统时钟(system_stmf4xx.c中),通常选择该时钟作为
//systick定时器时钟,根据具体情况更改