用STM32定时器测量信号频率——测频法和测周法[原创cnblogs.com/helesheng]

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
1 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 2 NVIC_InitTypeDef NVIC_InitStructure; 3 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能 4 //!!!!!定时器3,用于产生标准时长的对外部脉冲计数的窗口,从而计算外部脉冲的频率!!!!!// 5 TIM_TimeBaseStructure.TIM_Period = arr-1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 计数到5000 6 TIM_TimeBaseStructure.TIM_Prescaler =(psc-1); //设置用来作为TIMx时钟频率除数的预分频值 10Khz的计数频率 7 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim 8 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 9 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 10 TIM_ITConfig( //使能或者失能指定的TIM中断 11 TIM3, //TIM3 12 TIM_IT_Update, //数值溢出更新中断 13 ENABLE //使能 14 ); 15 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 16 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM2更新中断 17 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级 18 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级3级 19 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 20 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 21 TIM_SetCounter(TIM3,0); 22 TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
配置用于产生标准时长的TIM3
3)配置用于对外部被测脉冲进行计数的TIM2的时基单元和中断
1 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 2 NVIC_InitTypeDef NVIC_InitStructure; 3 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能 4 //!!!!!定时器3,用于产生标准时长的对外部脉冲计数的窗口,从而计算外部脉冲的频率!!!!!// 5 TIM_TimeBaseStructure.TIM_Period = arr-1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 计数到5000 6 TIM_TimeBaseStructure.TIM_Prescaler =(psc-1); //设置用来作为TIMx时钟频率除数的预分频值 10Khz的计数频率 7 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim 8 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 9 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 10 TIM_ITConfig( //使能或者失能指定的TIM中断 11 TIM3, //TIM3 12 TIM_IT_Update, //数值溢出更新中断 13 ENABLE //使能 14 ); 15 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 16 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM2更新中断 17 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级 18 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级3级 19 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 20 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 21 TIM_SetCounter(TIM3,0); 22 TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
配置用于对外部被测脉冲计数的TIM2
这里使用了全局变量top_watch,对外部脉冲计数器溢出次数进行清零,防止在标准时间长Tc1内发生计数溢出。
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的中断服务程序
TIM_TIxExternalClockConfig(TIM2,TIM_TIxExternalCLK1Source_TI2,TIM_ICPolarity_Rising,0);
TIM_SelectInputTrigger(TIM2,TIM_TS_ETRF);
根据图2所示的测周法原理,需要在被测信号周期(Tx2)内对STM32内部的最高频率72MHz(为获得最高测量精度和分辨率)的时钟进行计数。最简单的方式将被测信号作为外部中断源,并在外部中断服务程序中读取定时器中的计数值,但这样做会使中断入口时间也计算在Tx2以内。因此实现测周法的最佳方案,是使用STM32通用定时器或高级定时器的捕获功能(Input Capture)。STM32的输入捕获电路框图如图4所示,它能在定时器的某个通道TIMx_CHy发生指定脉冲边沿的时刻及时地将此时的计数器计数值锁存在“捕获/比较寄存器”中,从而有效地避免了上面提到的方法中进入中断时延造成的计时误差。
1 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能通用定时器TIM5时钟 2 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
配置GPIO
2)接下来还要将TIM5CH1的输入管脚PA0配置成输入模式,这里不再赘述。
1 TIM_TimeBaseStructure.TIM_Period = 65535; //设定计数器溢出值(自动重装值) 2 TIM_TimeBaseStructure.TIM_Prescaler = 0; //预分频器 3 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割因子 4 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式 5 TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); 6 //根据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);
配置输入捕获器
1 TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断
使能定时器中断
1 NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM5中断 2 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级 3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级 4 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 5 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
配置向量中断控制器NVIC
1 TIM_Cmd(TIM5, ENABLE); //使能TIM5
使能定时器
1 unsigned short i=0; 2 unsigned int pul_width[10];//脉冲周期 3 unsigned int pul_frq[10];//对应的脉冲频率 4 unsigned short ov_num;//定时器溢出的次数,用于记录之前溢出的次数 5 unsigned short last_cap_val=0,cur_cap_val;//当前捕获到的数值和上一次捕获到的数值 6 //定时器5中断服务程序(可以能由捕获或定时器溢出) 7 void TIM5_IRQHandler(void) 8 { 9 if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) 10 //为了增加测量频率的动态范围,定时器溢出次数也要计算,相当于增加了定时器的位数 11 ov_num++ ;//溢出次数加一 12 if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件 13 { 14 cur_cap_val = TIM_GetCapture1(TIM5);//读取当前捕获发生时的定时器数值 15 if(cur_cap_val >= last_cap_val)//如果发生过溢出,当前捕获结果就有可能比上次的捕获结果还小 16 pul_width[i] = (unsigned int)(ov_num<<16)+ (unsigned int)(cur_cap_val - last_cap_val); 17 else 18 pul_width[i] = (unsigned int)((ov_num-1)<<16) + (unsigned int)(65536 + cur_cap_val - last_cap_val); 19 pul_frq[i] = 72000000 /(float)pul_width[i] + 0.5; 20 //折算为频率,加0.5是为了防止强制类型转换带来的舍弃误差 21 last_cap_val = cur_cap_val;/将当前捕获结果复制到上次的捕获结果寄存,方便下次计算 22 ov_num = 0; 23 i++; 24 if(i == 10) 25 i = 0; 26 } 27 TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位 28 }
编写定时器中断服务程序(含寄出和捕获的操作)