思路:采用TIM3,设置为向上计数模式,每次计数溢出(因为以72Mhz计数,stm32全为16位定时器,0.9ms就会溢出了),则变量加一,如此来测量。核心代码如下,测试过了,非常准确。但是此方法的误差在于,由于stm32没有32位定时器,所以0.9ms就会进入中断一次进行cntPeriod++,这个也会消耗时间的,实际上就是一种误差了,但是一般而言,误差效果可以忽略不计。
extern u32 cntPeriod;
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
DBGMCU_Config(DBGMCU_TIM3_STOP,ENABLE);
// 使得调试模式下,定时器也跟着暂停
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
//这条语句之后,定时器3的CNT计数值就为0了,对的
TIM_ClearFlag(TIM3,TIM_FLAG_Update);
//加入这么一句,就不会事先进入一次中断了
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
// 注意:第二次之后,此条语句执行完后,就马上进入了中断(因为第一次NVIC_Init已经配置完了),
// 所以后面统计真正的中断次数得减去1
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
// 注意:第一次配置时候,此条语句执行完后,就马上进入了中断,原因是,上面TIM_ITConfig配置了
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
void TIM3_Int_Close()
{
NVIC_InitTypeDef NVIC_InitStructure;
//中断优先级NVIC设置
TIM_Cmd(TIM3, DISABLE); //停止TIM3
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE; //IRQ通道被关闭
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
}
//定时器3中断服务程序
void TIM3_IRQHandler(void) //TIM3中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中断标志
++cntPeriod;
}
}
void MeasureTimeStart()
{
cntPeriod = 0;
TIM3_Int_Init(64799,0);//1分频,即72Mhz的计数频率,计数到64800为0.9ms ,64800/72=900us
}
float MeasureTimeEnd() //别用double类型返回,用float,因为对keil对double支持不友好,尽量别用,
{
float runTime;
u32 cntTmp;
u32 cntTim3 = TIM3->CNT;//为了尽可能的减小偏移误差,这里尽快拿到这个CNT值,实测函数跳转特别耗时
TIM3_Int_Close();
printf("\r\ncntTim3:%d \r\n",cntTim3);
cntTmp = cntPeriod*64800 + cntTim3;
runTime = (float)cntTmp/72.0/1000.0; //返回多少ms
return runTime;
}
int main(void)
{
float val;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
MeasureTimeStart();
delay_1ms_my(1000);
val = MeasureTimeEnd();
printf("%f",val);
// test();
while(1);
}
注:delay_1ms_my()是我自己写的延时函数,测量后发现非常准确。
void delay_1us_my(void) //64个nop指令,加上跳转等执行时间,差不多就是72个时钟周期了,stm32f1主频72Mhz,即1us
{
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop();
}
void delay_1ms_my(u32 nms)
{
u32 nus=nms*1000;
while(--nus)
{
delay_1us_my();
}
}
效果如下所示:
此外,测了一下中间不加任何指令的运行时间,如下图所示:
即两条空语句之间会用掉49个时钟周期,约0.681us(固定的多余消耗,提高测量精度时候可以直接减去这个值),这是因为其实中间有函数跳转等操作,可以看出也还是挺快的了,而且可以看出这个时间测量方法精度非常的高,达到1/72us
其中发现一个很奇怪的现象,
MeasureTimeStart(); // 假如是测空语句,这个放前面和放这里测出来的会有细微的差别,这个没关系的了,知道就好
delay_1ms_my(1000);
// _VZ(Smix,numOfStrongTask+numOfHardTask);
timeRunVzop = MeasureTimeEnd();
printf("%f",timeRunVzop);
while(1)
{
// TaskDataClear(); //这句代码有效的话,上面测量出来的时间会偏小3ms,想了很久,没想明白为什么,按道理说这后面的代码不会影响到上面的时间测量,只有一种解释,那就是编译后代码所占空间变大?导致前面的函数地址跳转变多了?我也不知道具体原因,反之就当真粗略使用吧
}