1 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); 2 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 3 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能porta 4 //PA1-> TIM2_CH2外部时钟输入 5 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//PA1 6 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 7 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //10M时钟速度 8 GPIO_Init(GPIOA, &GPIO_InitStructure);使能相关定时器和GPIO时钟,配置GPIO 2)配置用于产生标准时长的TIM3的时基单元和中断
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能 //!!!!!定时器3,用于产生标准时长的对外部脉冲计数的窗口,从而计算外部脉冲的频率!!!!!// TIM_TimeBaseStructure.TIM_Period = arr-1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 计数到5000 TIM_TimeBaseStructure.TIM_Prescaler =(psc-1); //设置用来作为TIMx时钟频率除数的预分频值 10Khz的计数频率 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 TIM_ITConfig( //使能或者失能指定的TIM中断 TIM3, //TIM3 TIM_IT_Update, //数值溢出更新中断 ENABLE //使能 ); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM2更新中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级3级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 TIM_SetCounter(TIM3,0); TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
3)配置用于对外部被测脉冲进行计数的TIM2的时基单元和中断
配置用于对外部被测脉冲计数的TIM2
这里使用了全局变量top_watch,对外部脉冲计数器溢出次数进行清零,防止在标准时间长Tc1内发生计数溢出。 4)编写标准时长定时器TIM3的中断服务程序1 unsigned char i=0; 2 unsigned int frq[10];//连续存取10次的测频法得到的频率 3 unsigned int pul_num;//标准时间内的脉冲数量 4 unsigned int cnt;//读取当前计数值 5 unsigned int last_cnt=0;//上一次的计数值 6 void TIM3_IRQHandler(void) //TIM3中断,达到定时时间 7 { 8 if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 9 { 10 TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位:TIM 中断源 11 cnt = TIM_GetCounter(TIM2);//读取定时器2对外部脉冲的计数结果 12 if(cnt >= last_cnt)//如果发生过溢出,当前计数结果就有可能比上次的计数结果还小 13 pul_num = (unsigned int)(top_watch<<16)+ (unsigned int)(cnt - last_cnt); 14 else 15 pul_num = (unsigned int)((top_watch-1)<<16) + (unsigned int)(65536 + cnt - last_cnt); 16 last_cnt = cnt;//将当前计数结果复制到上次的复制结果寄存,方便下次计算 17 frq[i] = pul_num * 100;//由于定时器3是1/100秒溢出一次,所以频率时脉冲个数的100倍 18 i++; 19 if(i == 10) 20 i=0; 21 top_watch=0; 22 } 23 }TIM3的中断服务程序 这里使用数组 frq[10]连续存取10次的测频法得到的频率。每次执行上面的中断服务程序,意味着时间刚好过去了一个Tc1(在这里是10ms,即1/100秒),可以通过函数TIM_GetCounter(TIM2);读取定时器2对外部被测脉冲的计数结果。但该方法的问题是:用于对外部被测脉冲进行计数的TIM2有可能在临近的两次Tc1中断之间发生一次乃至多次溢出/更新,从而造成被测脉冲数计算错误。解决的办法是:在考虑外部被测脉冲计数定时器TIM2计数值的同时,也考虑TIM2溢出的次数top_watch(如上所述,top_watch会在每次TIM2溢出时自动加1)。另外,当前计数值(cnt)和上一次TIM3定时中断发生时的计数值(last_cnt)来计算标准时间Tc1内的外部被测脉冲。 注意:a、当Tc1内发生过TIM2计数溢出时,当前计数值cnt可能小于上一次的计数值last_cnt。 b、这里没有在中断服务程序中对计数器TIM2清零,而是任由TIM2*计数,反而采用当前计数值和上次计数值求差的办法。这种方法看似繁琐,但保证了TIM2能够不间断的连续计数,而不会使从发生TIM3定时中断,到进入TIM3中服务程序对TIM2清零,这两个事件间的脉冲数被漏记,从而提高了计数精度。 2、外部时钟源模式2 STM32的每个通用定时器和定时器,除了支持从输入通道1和2(TMRxCH1、TMRxCH2)输入外部时钟外,还各自拥有一个单独的外部时钟输入管脚TIMx_ETR。例如,TIM1_ETR在PA12,TIM2_ETR在PA0,TIM3_ETR在PD2,TIM4_ETR在PE0。我最初也对这种设计感到奇怪——既然已经支持从TMRxCH1、TMRxCH2输入外部时钟,为什么还要支持使用专用管脚输入?随着使用的深入才逐渐明白STM32设计者的初衷,是想支持用外部时钟驱动的捕获输入和比较输出。因为如果TMRxCH1、TMRxCH2管脚被占用为外部时钟输入,则定时器的捕获输入和比较输出管脚就会变少。 外部时钟源模式1和外部时钟源模式2之间的关系既然是这样的,那么用两种外部时钟源模式来实现测频法(计频法)测量信号频率的思路和代码就几乎相同了。不同点只是在配置定时器时钟源的一句:
TIM_TIxExternalClockConfig(TIM2,TIM_TIxExternalCLK1Source_TI2,TIM_ICPolarity_Rising,0);要更改为:
TIM_SelectInputTrigger(TIM2,TIM_TS_ETRF);如果外部时钟模式仍然使用定时器2,由于PA0管脚既是TIM2的通道1管脚,又是TIM2的ETR管脚,那么连管脚的配置都可以省去。也就是说,代码其他部分和前面采用外部时钟源模式1进行测频法(计频法)完全相同。 五、用STM32定时器实现测周法(计时法)的思路和源码
根据图2所示的测周法原理,需要在被测信号周期(Tx2)内对STM32内部的最高频率72MHz(为获得最高测量精度和分辨率)的时钟进行计数。最简单的方式将被测信号作为外部中断源,并在外部中断服务程序中读取定时器中的计数值,但这样做会使中断入口时间也计算在Tx2以内。因此实现测周法的最佳方案,是使用STM32通用定时器或高级定时器的捕获功能(Input Capture)。STM32的输入捕获电路框图如图4所示,它能在定时器的某个通道TIMx_CHy发生指定脉冲边沿的时刻及时地将此时的计数器计数值锁存在“捕获/比较寄存器”中,从而有效地避免了上面提到的方法中进入中断时延造成的计时误差。
通过定时器输入捕获实现测周法的代码大致如下: 1)使能定时器和GPIO时钟,配置GPIO配置GPIO
接下来还要将TIM5CH1的输入管脚PA0配置成输入模式,这里不再赘述。 2)配置定时器时基单元TIM_TimeBaseStructure.TIM_Period = 65535; //设定计数器溢出值(自动重装值) TIM_TimeBaseStructure.TIM_Prescaler = 0; //预分频器 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割因子 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基单元这里将自动重装值设置为16位计数器的最大值65535(0xFFFF),以增加计数器计数范围,降低自动重装次数。
3)配置输入捕获器
标准外设库使用时输入捕获器初始化结构体来配置其参数,该结构体的声明代码为:1 TIM_ICInitTypeDef TIM5_ICInitStructure; //定义输入捕获器初始化结构体 2 对初始化结构体TIM5_ICInitStructure中的参数赋值,例如如下代码: 3 TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入捕获通道为TIM5_CH1 4 TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获 5 TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上 6 TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入捕获脉冲分频,不分频 7 TIM5_ICInitStructure.TIM_ICFilter = 0x00;//配置输入滤波器 不滤波 8 TIM_ICInit(TIM5, &TIM5_ICInitStructure);配置输入捕获器 4)使能定时器中断
1 TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断使能定时器中断 其中,第二个参数代表触发中断事件,其可选参数已经在前面定时模式介绍过了,这里使用了更新中断或输入捕捉通道1中断都会触发中断的方式。这意味着,在中断服务程序中应检测到底是定时器自动重装更新引起的中断还是捕获引起的中断,并采取相应的应对措施。