原理讲解
输入捕获模式可以用来测量脉冲宽度或者测量频率。
STM32的定时器,除了TIM6和TIM7, 其他定时器都有输入捕获功能。STM32 的输入捕获,简单的说就是通过检测 TIMx_CHx
上的 边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器计数器的值(TIMx_CNT
) 存放到对应的通道的捕获/比较寄存器(TIMx_CCRx
)里面,完成一次捕获。同时还可以配置 捕获时是否触发中断/DMA 等。
脉宽计算方法
本章我们用到 TIM5_CH1 来捕获高电平脉宽,也就是要先设置输入捕获为上升沿检测,记 录发生上升沿的时候 TIM5_CNT 的值。然后配置捕获信号为下降沿捕获,当下降沿到来时,发 生捕获,并记录此时的 TIM5_CNT 值。这样,前后两次 TIM5_CNT 之差,就是高电平的脉宽, 同时 TIM5 的计数频率我们是知道的,从而可以计算出高电平脉宽的准确时间。
配置步骤
通过输入捕获,来获取TIM5_CH1(PA)上的高电平脉冲宽度
1)使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能 TIM5 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能 GPIOA 时钟
2)GPIO初始化
(和前几篇博客省略)
3)初始化TIM5,设置ARR和PSC
在开启了 TIM5 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来设置输入捕获的自动重装载值和计数频率。通过库函数 TIM_TimeBaseInit 实现,不再赘述看代码。
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化 Tim5
4)配置输入捕获模式
TIM_ICInitTypeDef TIM5_ICInitStructure;
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1 映射到 TI1 上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频,还有2,4,8分 //频可选
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
5)使能捕获和更新中断
因为我们要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获时下降 沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就 会溢出,对溢出必须做处理,否则结果就不准了。这两件事,我们都在中断里面做,所以必须开启捕获中断和更新中断。
这里我们使用定时器的开中断函数 TIM_ITConfig 即可使能捕获和更新中断:
TIM_ITConfig( TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断和捕获中断
6)设置中断分组,编写中断服务函数 (函数里怎么写很重要,放在下面单独讲解)
设置中断分组的方法前面多次提到这里我们不做讲解,主要是通过函数 NVIC_Init()来完成。分组完成后,我们还需要在中断函数里面完成数据处理和捕获设置等关键操作,从而实现高电平脉宽统计。在中断服务函数里面,跟以前的外部中断和定时器中断实验中一样,我们在中断开始的时候要进行中断类型判断,在中断结束的时候要清除中断标志位。
使用到的函数在上面的实验已经讲解过,分别为 TIM_GetITStatus()函数和 TIM_ClearITPendingBit()函数。
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET){}//判断是否为更新中断
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET){}//判断是否发生捕获事件
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);//清除中断和捕获标志位
7)使能定时器(设置 TIM5 的 CR1 寄存器)
最后,必须打开定时器的计数器开关, 启动 TIM5 的计数器,开始输入捕获。
TIM_Cmd(TIM5,ENABLE ); //使能定时器 5
通过以上 6 步设置,定时器 5 的通道 1 就可以开始输入捕获了。
中断服务函数
u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态
u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
//⑤定时器 5 中断服务程序
void TIM5_IRQHandler(void)
{
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获(第7位为0)才执行 0x80: 1000 0000
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) //检测是否发生更新中断
{
if(TIM5CH1_CAPTURE_STA&0X40) //已经捕获到高电平了(之前设置的高电平捕获)
{ //0x40: 0100 0000
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//定时器溢出,计数器(STA低6位)溢出
{ //0x3F: 0011 1111
TIM5CH1_CAPTURE_STA|=0X80; //直接标记为成功捕获了一次,STA最高位置1
TIM5CH1_CAPTURE_VAL=0XFFFF; //16位计数器全部置1(计满数)
}
else //定时器发生溢出,但计数器未溢出
TIM5CH1_CAPTURE_STA++; //对溢出次数进行计数(STA低6位)
}
}
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET) //捕获 1 发生捕获事件
{
if(TIM5CH1_CAPTURE_STA&0X40) //已经捕获到上升沿,现在捕获到一个下降沿
//为什么这里捕获到的是下降沿?看下面else
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次上升沿
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5); //读取此时定时器的值
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //设置为上升沿捕获
}
else //还未开始,第一次捕获上升沿 (
//逻辑是先进入这个else,才会有机会跑上面的if
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM5,0);
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //设置为下降沿捕获
}
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}
此函数用到了两个全局变量,用于辅助实现 高电平捕获。其中 TIM5CH1_CAPTURE_STA
,是用来记录捕获状态,该变量类似我们在 usart.c 里面自行定义的USART_RX_STA
寄存器(其实就是个变量,只是我们把它当成一个寄存器那样来使用)。TIM5CH1_CAPTURE_STA
各位描述如表所示
另外一个变量TIM5CH1_CAPTURE_VAL
,则用来记录捕获到下降沿的时候,TIM5_CNT
的值。
捕获高电平脉宽的思路:
首先,设置 TIM5_CH1
捕获上升沿,这在 TIM5_Cap_Init
函数执行的时候就设置好了,然后等待上升沿中断到来,当捕获到上升沿中断, 此时如果 TIM5CH1_CAPTURE_STA
的第 6 位为 0,则表示还没有捕获到新的上升沿,就先把 TIM5CH1_CAPTURE_STA
、TIM5CH1_CAPTURE_VAL
和TIM5_CNT
等清零,然后再设置TIM5CH1_CAPTURE_STA
的第 6 位为 1,标记捕获到高电平,最后设置为下降沿捕获,等待下降沿到来。如果等待下降沿到来期间,定时器发生了溢出,就在TIM5CH1_CAPTURE_STA
里面对溢出次数进行计数,当最大溢出次数来到的时候,就强制标记捕获完成(虽然此时还没有捕获到下降沿)。当下降沿到来的时候,先设置 TIM5CH1_CAPTURE_STA
的第 7 位为 1,标记成功捕获一次高电平,然后读取此时的定时器值到 TIM5CH1_CAPTURE_VAL
里面,最后设置为上升沿捕获,回到初始状态。 这样,我们就完成一次高电平捕获了,只要TIM5CH1_CAPTURE_STA
的第 7 位一直为 1, 那么就不会进行第二次捕获,我们在main函数处理完捕获数据后,将TIM5CH1_CAPTURE_STA
置零,就可以开启第二次捕获。 这里我们还使用到一个函数 TIM_OC1PolarityConfig
来修改输入捕获通道 1 的极性的。相信这个不难理解:
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity)
要设置为上升沿捕获,则为:
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //设置为上升沿捕获
还有一个函数用来设置计数器寄存器值,这个同样很好理解:
TIM_SetCounter(TIM5,0);
上行代码的意思就是计数值清零。
主函数
extern u8 TIM5CH1_CAPTURE_STA; //输入捕获状态
extern u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
int main(void)
{
u32 temp=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //LED 端口初始化
TIM3_PWM_Init(899,0); //不分频。PWM 频率=72000/(899+1)=80Khz
TIM5_Cap_Init(0XFFFF,72-1); //以 1Mhz 的频率计数
while(1)
{
delay_ms(10);
TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3)+1);
if(TIM_GetCapture2(TIM3)==300) TIM_SetCompare2(TIM3,0);
if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
{
temp=TIM5CH1_CAPTURE_STA&0X3F; //溢出次数
temp*=65536; //溢出时间总和
temp+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
printf("HIGH:%d us\r\n",temp); //打印总的高电平时间
TIM5CH1_CAPTURE_STA=0; //开启下一次捕获
}
}
}
保留了PWM章节的内容,TIM3用于PWM输出控制LED由暗变亮。
通过设置TIM5_Cap_Init(0XFFFF,72-1
,将 TIM5_CH1
的捕获计数器设计为 1us 计数一次,并设置重装 载值为最大,所以我们的捕获时间精度为 1us。