最近在做小车避障的功能,需要用STM32F407控制5个传感器,结合板子的示例程序,调试了一段时间,终于成功读出5路传感器的测距信息。
传感器原理不详细说了,基本上从Trig脚给一个脉宽10us以上的脉冲,然后发射探头发出8个40KHz的脉冲,检测到回波后,从Echo管脚输出高电平,高电平的持续时间与距离值关系为:(高电平时间*340m/s)/2。使用中,只要采集到Echo脚高电平的持续时间,就能测距了。
在STM32F407上测量某一管脚的电平持续时间需要使用定时器的捕获功能(不知道有没有更简单的实现方法),查了手册,对于捕获模式的解释:在输入捕获模式下,当相应的ICx 信号检测到跳变沿后,将使用捕获/比较寄存器(TIMx_CCRx)来锁存计数器的值。发生捕获事件时,会将相应的CCXIF 标志(TIMx_SR 寄存器)置1,并可发送中断或DMA 请求(如果已使能)。如果发生捕获事件时CCxIF 标志已处于高位,则会将重复捕获标志CCxOF(TIMx_SR 寄存器)置1。可通过软件向CCxIF 写入0 来给CCxIF清零,或读取存储在TIMx_CCRx寄存器中的已捕获数据。向CCxOF 写入0 后会将其清零。其实就是在上升沿和下降沿分别记录定时器计数器的值,其差值相当于脉冲宽度。关于指点,网上找的图可以较清晰地表达:
首先设置定时器通道 x 为上升沿捕获,这样, t1 时刻,就会捕获到当前的 CNT 值,然后立即清零 CNT,并设置通道 x为下降沿捕获,这样到 t2 时刻,又会发生捕获事件,得到此时的 CNT 值,记为 CCRx2。 这样,根据定时器的计数频率,可以算出 t1~t2 的时间,从而得到高电平脉宽。 在t1~t2时,可能计数器会超过设定值arr,产生定时器溢出,这样就要对定时器溢出处理。最终,如图所示,t1~t2之间,CNT计数的次数等于:N*ARR+CCRx2,有了这个计数次数,再乘以 CNT 的计数周期,即可得到 t2-t1 的时间长度,即高电平持续时间。
我用了TIM2和TIM5两个定时器,这两个定时器可提供8路通道,我用了5个。其中TIM5的CH2通道被占用,只要开中断就会不停进中断服务程序,故不用。定时器的设置是最重要的部分,放在TIM_Cap_Init()中,代码流程如下:
首先配置TIM2和TIM5的中断优先级(NVIC_InitStructure),注册中断类型。然后配置TIM2和TIM5对应通道时钟,使能TIM时钟和GPIO时钟,配置GPIO工作模式(GPIO_Init),包括管脚、下拉、功能、速度等,再将管脚复用映射到对应定时器上(GPIO_PinAFConfig)。设置定时器的计数频率和计数模式等(TIM_TimeBaseInit)。之后设置捕获参数(TIM_ICInit):上升沿捕获、分频、滤波、用到的通道等等。设置完成后允许中断(TIM_ITConfig),使能定时器(TIM_Cmd),定时器开始计数。
当进入中断服务程序时,先判断引起中断的类型,如果是某个通道引起的中断,调用单通道捕获处理函数,否则表示update中断,需要清除中断标志位。根据实际情况,上电时进入中断服务程序,中断类型TIM_IT_Update,要是不清除标志位,会不停进入中断。中断服务程序代码:
void TIM5_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM5, TIM_IT_Update); //Çå³ýÖжϱê־λ
if(TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)
{
SingleChannelHandler( &TIM5CH1);
}
if(TIM_GetITStatus(TIM5, TIM_IT_CC3) !=RESET)
{
SingleChannelHandler( &TIM5CH3);
}
}
void TIM2_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //Çå³ýÖжϱê־λ
if(TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
{
SingleChannelHandler( &TIM2CH2);
}
if(TIM_GetITStatus(TIM2, TIM_IT_CC3) !=RESET)
{
SingleChannelHandler( &TIM2CH3);
}
if(TIM_GetITStatus(TIM2, TIM_IT_CC4) !=RESET)
{
SingleChannelHandler( &TIM2CH4);
}
}
主要的捕获处理放在SingleChannelHandler()中,参考了开发板的例程改的,代码如下:
void SingleChannelHandler(TIM_CAPTURE * tch )
{
if((tch->CAPTURE_STA&0X80)==0)//»¹Î´³É¹¦²¶»ñ
{
if(TIM_GetITStatus(tch->timX, TIM_IT_Update) != RESET)//Òç³ö
{
if(tch->CAPTURE_STA&0X40)//ÒѾ²¶»ñµ½¸ßµçƽÁË
{
if((tch->CAPTURE_STA&0X3F)==0X3F)//¸ßµçƽ̫³¤ÁË
{
tch->CAPTURE_STA|=0X80; //±ê¼Ç³É¹¦²¶»ñÁËÒ»´Î
tch->CAPTURE_VAL=0XFFFFFFFF;
}else tch->CAPTURE_VAL++;
}
}
if(TIM_GetITStatus(tch->timX, tch->TIM_IT_CCX) != RESET)//²¶»ñ1·¢Éú²¶»ñʼþ
{
if(tch->CAPTURE_STA&0X40) //ÒѾ²¶»ñµ½¸ßµçƽ£¬±¾´Î²¶»ñµ½Ï½µÑØ
{
tch->CAPTURE_STA|=0X80; //±ê¼Ç³É¹¦²¶»ñµ½Ò»´Î¸ßµçƽÂö¿í
//tch->CAPTURE_VAL=TIM_GetCapture1(tch->timX);//»ñÈ¡µ±Ç°µÄ²¶»ñÖµ.
switch( tch->TIM_IT_CCX ) // ¸ù¾Ý²»Í¬Í¨µÀÑ¡Ôñº¯Êý
{
case TIM_IT_CC1:
tch->CAPTURE_VAL=TIM_GetCapture1(tch->timX);//»ñÈ¡µ±Ç°µÄ²¶»ñÖµ.
TIM_OC1PolarityConfig(tch->timX,TIM_ICPolarity_Rising); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
break;
case TIM_IT_CC2:
tch->CAPTURE_VAL=TIM_GetCapture2(tch->timX);//»ñÈ¡µ±Ç°µÄ²¶»ñÖµ.
TIM_OC2PolarityConfig(tch->timX,TIM_ICPolarity_Rising); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
break;
case TIM_IT_CC3:
tch->CAPTURE_VAL=TIM_GetCapture3(tch->timX);//»ñÈ¡µ±Ç°µÄ²¶»ñÖµ.
TIM_OC3PolarityConfig(tch->timX,TIM_ICPolarity_Rising); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
break;
case TIM_IT_CC4:
tch->CAPTURE_VAL=TIM_GetCapture4(tch->timX);//»ñÈ¡µ±Ç°µÄ²¶»ñÖµ.
TIM_OC4PolarityConfig(tch->timX,TIM_ICPolarity_Rising); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
break;
default:
break;
}
//printf(" tch->TIM_IT_CCX = %d ",tch->TIM_IT_CCX); //´òÓ¡×ܵĸߵãƽʱ¼ä
//TIM_OC1PolarityConfig(tch->timX,TIM_ICPolarity_Rising); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
}else //»¹Î´¿ªÊ¼,µÚÒ»´Î²¶»ñÉÏÉýÑØ
{
tch->CAPTURE_STA=0; //Çå¿Õ
tch->CAPTURE_VAL=0;
tch->CAPTURE_STA|= 0X40; //±ê¼Ç²¶»ñµ½ÁËÉÏÉýÑØ
TIM_Cmd(tch->timX,DISABLE ); //¹Ø±Õ¶¨Ê±Æ÷
TIM_SetCounter(tch->timX,0);
// printf(" tch->CAPTURE_STA = %d ",tch->CAPTURE_STA); //´òÓ¡×ܵĸߵãƽʱ¼ä
//TIM_OC1PolarityConfig(tch->timX,TIM_ICPolarity_Falling); //CC1P=1 ÉèÖÃΪϽµÑز¶»ñ
switch( tch->TIM_IT_CCX )
{
case TIM_IT_CC1:
TIM_OC1PolarityConfig(tch->timX,TIM_ICPolarity_Falling); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
break;
case TIM_IT_CC2:
TIM_OC2PolarityConfig(tch->timX,TIM_ICPolarity_Falling); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
break;
case TIM_IT_CC3:
TIM_OC3PolarityConfig(tch->timX,TIM_ICPolarity_Falling); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
break;
case TIM_IT_CC4:
TIM_OC4PolarityConfig(tch->timX,TIM_ICPolarity_Falling); //CC1P=0 ÉèÖÃΪÉÏÉýÑز¶»ñ
break;
default:
break;
}
TIM_Cmd(tch->timX,ENABLE ); //ʹÄܶ¨Ê±Æ÷s
}
}
}
TIM_ClearITPendingBit(tch->timX, tch->TIM_IT_CCX | TIM_IT_Update); //Çå³ýÖжϱê־λ
}
首先自定义了标志字CAPTURE_STA,共8位,最高位D7=1表示成功捕获到高电平脉宽,即捕获到上升沿后又捕获到下降沿,D6=1表示捕获到上升沿,D5~D0表示定时器溢出次数。CAPTURE_STA初始为0,程序开始判断是否成功捕获,未成功捕获则继续,这样捕获成功后就不再执行了。然后判断捕获类型,如果是某个通道引起的,则查看标志字,如果D6=0,说明是由于首次捕获到上升沿而进入中断,设置D6=1,计数器清零,然后配置捕获下降沿,这样再次进入中断时,可以看到标志字D6=1,说明此时捕获到了下降沿,读取计数器的值,可获得高电平时间。如果进入中断的类型是TIM_IT_Update,说明定时器计数溢出,根据之前定时器的设置,TIM2和TIM5均为1us累加1次,32位的定时器,溢出需要4292s,这样现实中是不会出现的,所以可以不做处理,但是为了以防万一,强制设置捕获成功。
在main函数中循环判断CAPTURE_STA状态,成功捕获后读取计数值可以计算出距离信息,将结果通过串口打印出来,如图:
5个传感器能检测到距离信息,下一步考虑准备安装在小车上了。