输入捕获简介
输入捕获IC(Input Capture),是处理器捕获外部输入信号的功能。基于定时器抓取输入信号指定触发方式之间的长度。通过输入捕获功能,我们可以测量脉冲宽度和测量频率。
在每个高级定时器和通用器都有4个输入捕获通道。
当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数。
输入捕获功能主要涉及以下几个关键概念:
- 捕获通道(Capture Channel):定时器可以配置多个捕获通道,每个通道用于捕获一个外部事件的触发时间和脉冲宽度。
- 计数器(Counter):定时器的计数器用于记录定时器的计数值,表示经过的时间。
- 捕获寄存器(Capture Register):捕获寄存器用于存储捕获通道捕获到的时间戳或者脉冲宽度值。
- 触发源(Trigger Source):触发源可以是定时器的输入引脚(如外部信号源),或者是定时器自身的输出(如另一个定时器的通道输出)。
- 中断(Interrupt):当捕获事件发生时,定时器会触发相应的中断,可以在中断服务程序中处理捕获结果。
频率测量
测频法:在闸门时间T内,对上升沿计次,得到N,则频率f_x=N / T.
这种方法要求测量的频率比较快,计算频率的N越多,计算的越精确,也就表明频率越快;
测周法:两个上升沿内,以标准频率fc计次,得到N ,则频率f_x=f_c / N.
这种方法要求所测频率比较低,要求标准频率要远大于所测频率,这样N会算的越多,所测频率越准确。
中界频率:测频法与测周法误差相等的频率点f_m=√f_c / T
这种方法主要用来判断所测频率用哪种方法比较合适,根据所给的标准频率和时间T去验证。
输入捕获通道
先看总框图
最左边的是输入捕获的通道引脚,有4个,可以参考引脚定义表来确认通道;
进来之后有一个三输入的异或门,执行逻辑是当三个引脚有任何一个引脚有电平翻转的时候,输出引脚就发生电平翻转,之后输出通过数据选择器,到达输入捕获通道1,异或这里有两个选择,如果是上面那个,那输入捕获通道1就是三个引脚的异或值,如果选择下面一个,那么异或门就没有用,直接四个通道各用各的引脚。这个异或门主要用于三相无刷电机。
接着进入输入滤波器和边沿检测器,滤波器可以对输入信号进行滤波,避免毛刺和干扰信号;边沿检测器可以选择上升沿触发或者下降沿触发,触发后执行后续电路;并且这里是设计了两套滤波检测电路,第一套电路经过滤波和极性选择,得到TI1FP1,进入后续电路;第二套电路同样的操作,得到TI2FP2,进入后续电路;输入通道2也是同样的道理。对于TI1FP1会进入通道1的后续电路,TI2FP2会进入通道2的后续电路。这样做就能让一个引脚的输入映射到两个捕获单元了。下面还有一个TRC输入,也是用于无刷电机的驱动。
走到预分频器,每个通道都各有一个,可以对输入的频率进行分频,分频后的信号就能够触发捕获电路开始工作。每来一个值,CNT中的值就会转到捕获寄存器进行锁存。可以根据这个值进行时间,频率的计算。同时也会产生捕获事件(CC1I),还会产生标志位(U)到寄存器上,可以产生中断。
再看捕获通道:
通过输入引脚引入滤波器,TI1就是输入的引脚,输出的TI1F就是滤波后的信号,fDTS是滤波器的采样时钟来源,下面是CCMR1是控制滤波器参数的寄存器;
滤波后的信号进入边沿检测器,捕获上升沿或者下降沿,用下面的CCER寄存器就能选择极性。
得到的TI1FP1通过数据选择器,进入通道1的后续电路。
CC1S可以对数据选择器进行选择,IPPS可以对分频器进行配置,CCIE控制输出的使能。
这个TI1FP1信号触发进入通道1后续电路时,也可以同时触发从模式,从模式中有模式可以对CNT进行自动清0。
主模式将定时器的内部信号映射到TRGO引脚,用于触发别的外设;
从模式就是接收其他外设或者自身外设的信号,用于控制自身定时器的运行。
触发源可以选择一个信号到TRGI引脚上;
像我们刚才的选择,就是触发源选择TI1FP1,从模式的Reset;这样就完成对CNT自动清零的设置。
输入捕获基本结构
这里我们将采用上面的测周法进行测量;
举个例子,我们事先给时间单元的预分频器给72(接上内部时钟72MHz),那么频率就为1000000Hz。GPIO口输入一个频率为100Hz的PWM,通过滤波器和边缘检测极性选择,TI1FP1信号会触发后续通道电路并且触发从模式,以100Hz的一个周期来算,也就是在两个上升沿(100Hz)期间,1000000Hz的可以计算10000次(1000000/100),那么捕获寄存器就会得到10000的值,接着从模式会对CNT清零。这样得到的捕获值就可以对输入的频率进行计算。
当然,我们无法保证最后100Hz最后的周期是完整的,所以会有所误差(±1)。
PWMI的基本结构
相比于上面的基本结构,这里多了一条捕获通道,这个TI2FP2触发的极性与TI1FP1相反,我们利用这个TI2FP2可以计算PWM的占空比。
例如TI1FP1触发极性为上升沿,TI2FP2触发极性为下降沿,我们还是以上面的例子展开延申,一个100Hz的周期内可以计算到10000次的标准频率周期,假设占空比为50%,那么在100Hz周期内,以标准频率去算的话,将会有5000次,那么这样把5000除以10000就可以得到占空比了。
输入捕获模式测量PWM频率和占空比
接线模式:
我们将在A0口输出一个PWM,由A6进行对A0口的PWM频率和占空比的测量。
代码
PWM.h
#ifndef __PWM_H__ #define __PWM_H__ void PWM_Init(); void PWM_SetCompare(uint16_t Compare); void PWM_Prescaler(uint16_t Prescaler); #endif
PWM.c
#include "stm32f10x.h" // Device header void PWM_Init() { //开启APB1外设开关 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); //配置内部时钟TIM2 TIM_InternalClockConfig(TIM2); //时钟结构体初始化 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //划分TIM2 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //计时器模式 TIM_TimeBaseInitStructure.TIM_Period=100-1; //自动加载寄存器周期值 TIM_TimeBaseInitStructure.TIM_Prescaler=720-1; //预分频值 TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; //指定重复计时器的值,这里不用到 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); //配置输出比较结构体 TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1; //配置输出比较模式 TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High; //指定输出极性 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出比较状态 TIM_OCInitStructure.TIM_Pulse=0; //指定要捕获的脉冲值CCR TIM_OC1Init(TIM2,&TIM_OCInitStructure); //启用TIM2外设控制 TIM_Cmd(TIM2,ENABLE); } //可以设置占空比 void PWM_SetCompare(uint16_t Compare) { //配置TIM2比较值 TIM_SetCompare1(TIM2,Compare); } //配置TIMx的预调度器,可以设置PWM频率 void PWM_Prescaler(uint16_t Prescaler) { TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate); }
IC.h
#ifndef __IC_H__ #define __IC_H__ void IC_Init(); uint32_t IC_GetFreq(); uint16_t IC_GetDuty(); #endif
IC.c
#include "stm32f10x.h" // Device header #include "PWM.h" void IC_Init() { //开启外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //引脚输出初始化 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //Time内部时钟 TIM_InternalClockConfig(TIM3); //内部时钟初始化 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //输入捕获初始化 TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel=TIM_Channel_1; //选择通道 TIM_ICInitStructure.TIM_ICFilter=0xF; //指定输入捕获过滤器 TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising; //触发方式 TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //不分频 TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI; //直流方式 //配置TIM3外设测量通道2,输出初始化通道相反,流相反,触发方式相反的成员 TIM_PWMIConfig(TIM3,&TIM_ICInitStructure); //选择输入触发源 TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1); //选择从模式 TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset); TIM_Cmd(TIM3,ENABLE); } //获取输入捕获PWM频率 uint32_t IC_GetFreq() { //72M/72=1M return 1000000/(TIM_GetCapture1(TIM3)+1); //根据计算验证,捕获值为9999,有1的误差 } //获取输入捕获PWM占空比 uint16_t IC_GetDuty() { return ((TIM_GetCapture2(TIM3))*100/TIM_GetCapture1(TIM3)+1); }
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "PWM.h" #include "IC.h" int main() { OLED_Init(); PWM_Init(); IC_Init(); OLED_ShowString(1,1,"Freq:00000Hz"); OLED_ShowString(2, 1, "Duty:00%"); PWM_Prescaler(7200-1); //Freq=72M/(PSC+1)/100 PWM_SetCompare(75); //Duty=CCR/100;不能超过100 while(1) { OLED_ShowNum(1,6,IC_GetFreq(),5); OLED_ShowNum(2,6,IC_GetDuty(),2); } }
编码器接口
编码器接口常应用于测量、控制和位置检测等领域的传感器,用于监测旋转和线性运动的位置和速度。
每个高级定时器和通用定时器都拥有1个编码器接口。
两个输入引脚借用了输入捕获的通道1和通道2。
编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。
正交编码器
正交编码器通常由两个光电传感器组成,分别成为A相和B相。这两个相位差90°的信号输出,可以通过检测两个相位信号的变化来确认旋转方向和计数值。
在正交编码器中,两个相位信号的波形是基于格雷码(Gray Code)的。格雷码是一种二进制编码,相邻的数值只有一个位数发生变化。这样设计的好处是,在旋转过程中,即使出现脉冲跳变也不会导致计数错误。
通过对A相和B相信号进行解码,可以获取准确的旋转位置和方向。例如,当顺时针旋转时,A相先变化然后B相变化,而逆时针旋转时,B相先变化然后A相变化。
工作模式
我们主要用TI1和TI2上的计数模式。
举个例子,在TI1低电平时,TI2上升沿,那么计数减少;TI2下降沿,计数增加。在TI1高电平时,TI2上升沿,那么计数增加;TI2下降沿,计数减少;
这是极性不反向的情况,也就是TI1和TI2的触发极性相同时,
若是相反:
接口基本结构
这里的编码器接口充当外部时钟,通过捕获输入的频率作为时钟源。
TIM编码接口器测速
接线方式:
通过编码器的旋转速度,让它显示在屏幕上。
代码:
Timer.h
#ifndef __TIMER_H__ #define __TIMER_H__ void Timer_Init(); #endif
Timer.c
#include "stm32f10x.h" // Device header void Timer_Init() { //开启APB1外设开关 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //配置TIM2为内部时钟 TIM_InternalClockConfig(TIM2); //时钟结构体初始化 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //划分TIM2 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //计时器模式 TIM_TimeBaseInitStructure.TIM_Period=10000-1; //自动加载寄存器周期值 TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1; //预分频值 TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; //指定重复计时器的值,这里不用到 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); // TIM_ClearFlag(TIM2, TIM_FLAG_Update); //启用TIM2中断 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //配置优先级分组 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC初始化 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_Init(&NVIC_InitStructure); //启用TIM2外设控制 TIM_Cmd(TIM2,ENABLE); }
内部时钟中断,利用TIM2作为时间中断,每隔一秒钟,将会进入中断函数。
Encoder.h
#ifndef __ENCODER_H__ #define __ENCODER_H__ void Encoder_Init(); int16_t Encoder_Get(); #endif
Encoder.c
#include "stm32f10x.h" // Device header void Encoder_Init() { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //编码器接口模式相当于接入一个外设时钟 TIM_TimeBaseInitTypeDef TimeBaseInitStructure; TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; TimeBaseInitStructure.TIM_Period=65535-1; //ARR TimeBaseInitStructure.TIM_Prescaler=1-1; //PSC TimeBaseInitStructure.TIM_RepetitionCounter=0; TIM_TimeBaseInit(TIM3,&TimeBaseInitStructure); //输入捕获 TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel=TIM_Channel_1; TIM_ICInitStructure.TIM_ICFilter=0xF; TIM_ICInit(TIM3,&TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel=TIM_Channel_2; TIM_ICInitStructure.TIM_ICFilter=0xF; TIM_ICInit(TIM3,&TIM_ICInitStructure); //编码接口器 TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising); //设置两个触发极性为相同的 //启用TIM外设 TIM_Cmd(TIM3,ENABLE); } //获取速度值,获取后将计数器清0 int16_t Encoder_Get() { int16_t Temp; Temp=TIM_GetCounter(TIM3); TIM_SetCounter(TIM3,0); return Temp; }
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Encoder.h" #include "Timer.h" int16_t Speed; int main() { OLED_Init(); Timer_Init(); Encoder_Init(); OLED_ShowString(1,1,"Speed:"); while(1) { OLED_ShowSignedNum(1,7,Speed,4); } } //中断函数 void TIM2_IRQHandler() { if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) { //将数值赋给Speed Speed=Encoder_Get(); TIM_ClearITPendingBit(TIM2,TIM_IT_Update); } }