提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、超声波模块的工作原理
- 二、超声波的时序图
- 三、利用STM32F407进行驱动编写
-
- 1.定时器配置
- 总结
前言
今天我们针对超声波测距模块进行学习,HC-SR04超声波测距模块可提供2cm-400cm的非接触式距离感测功能,测距精度可达高到3mm;模块包括超声波发射器、接收器与控制电路。像智能小车的测距以及转向,或是一些项目中,常常会用到。智能小车测距可以及时发现前方的障碍物,使智能小车可以及时转向,避开障碍物。在后续各种比赛中超声波模块必不可少。
一、超声波模块的工作原理
针对HC-SR04模块,依据其数据手册我们可以了解到它的工作原理: I、给超声波模块接入电源和地。 II、给脉冲触发引脚(trig)输入一个长为20us的高电平方波。 III、输入方波后,模块会自动发射8个40KHz的声波,与此同时回波引脚(echo)端的电平会由0变为1;(此时应该启动定时器计时)。 IV、当超声波返回被模块接收到时,回波引 脚端的电平会由1变为0;(此时应该停止定时器计数),定时器记下的这个时间即为超声波由发射到返回的总时长。 V、根据声音在空气中的速度为344米/秒,即可计算出所测的距离。
二、超声波的时序图
![在这里插入图片描述](https://img-blog.csdnimg.cn/bf13dcbdcac544d4a6171b07a53b767f.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NDQ4MDg4,size_16,color_FFFFFF,t_70#pic_center) 从时序图中,我们可以看到HC-SR04属于电平触发型模块,利用单片机对其输入10us的高电平触发其测距功能,然后利用定时器捕获得到回响电平的高电平时间,利用公式计算得出所测的距离,这里需要注意测出的距离需要除以2,因为超神波是一来一回的。
三、利用STM32F407进行驱动编写
1.定时器配置
因为考虑到F4的开发板的定时器捕获通道最多有4路,因此如果一个定时器只对一个超声波模块进行捕获,太浪费资源,因而如何让利用一个定时器对多个模块进行采集成为了我接下来主要解决的问题。 相关配置如下:
void TIM5_Cap_Init(u32 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE); //TIM5时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能PORTA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; //GPIOA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA0
GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5); //PA0复用位定时器5
GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM5);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_TIM5);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_TIM5);
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseStructure);
//初始化TIM5输入捕获参数
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 选择输入端 IC1映射到TI1上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_2;//CC1S=01 选择输入端 IC1映射到TI1上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_3; //CC1S=01 选择输入端 IC1映射到TI1上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_4; //CC1S=01 选择输入端 IC1映射到TI1上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE);//允许更新中断 ,允许CC1IE捕获中断
TIM_ITConfig(TIM5,TIM_IT_CC1,ENABLE);
TIM_ITConfig(TIM5,TIM_IT_CC2,ENABLE);
TIM_ITConfig(TIM5,TIM_IT_CC3,ENABLE);
TIM_ITConfig(TIM5,TIM_IT_CC4,ENABLE);
TIM_Cmd(TIM5,ENABLE ); //使能定时器5
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
}
## 2.捕获中断编写 相关配置如下:
//捕获状态
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到低电平;1,已经捕获到低电平了.
//[5:0]:捕获低电平后溢出的次数(对于32位定时器来说,1us计数器加1,溢出时间:4294秒)
u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态
u32 TIM5CH1_CAPTURE_VAL; //输入捕获值(TIM2/TIM5是32位)
u8 TIM5CH2_CAPTURE_STA=0; //输入捕获状态
u32 TIM5CH2_CAPTURE_VAL; //输入捕获值(TIM2/TIM5是32位)
u8 TIM5CH3_CAPTURE_STA=0; //输入捕获状态
u32 TIM5CH3_CAPTURE_VAL; //输入捕获值(TIM2/TIM5是32位)
u8 TIM5CH4_CAPTURE_STA=0; //输入捕获状态
u32 TIM5CH4_CAPTURE_VAL; //输入捕获值(TIM2/TIM5是32位)
//定时器5中断服务程序
void TIM5_IRQHandler(void)
{
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)//溢出
{
if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获了一次
TIM5CH1_CAPTURE_VAL=0XFFFFFFFF;
}else TIM5CH1_CAPTURE_STA++;
}
}
if(TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
{
if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);//获取当前的捕获值.
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_Cmd(TIM5,DISABLE ); //关闭定时器5
TIM_SetCounter(TIM5,0);
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
TIM_Cmd(TIM5,ENABLE ); //使能定时器5
}
}
}
if((TIM5CH2_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)//溢出
{
if(TIM5CH2_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM5CH2_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH2_CAPTURE_STA|=0X80; //标记成功捕获了一次
TIM5CH2_CAPTURE_VAL=0XFFFFFFFF;
}else TIM5CH2_CAPTURE_STA++;
}
}
if(TIM_GetITStatus(TIM5, TIM_IT_CC2) != RESET)//捕获1发生捕获事件
{
if(TIM5CH2_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH2_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM5CH2_CAPTURE_VAL=TIM_GetCapture2(TIM5);//获取当前的捕获值.
TIM_OC2PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM5CH2_CAPTURE_STA=0; //清空
TIM5CH2_CAPTURE_VAL=0;
TIM5CH2_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_Cmd(TIM5,DISABLE ); //关闭定时器5
TIM_SetCounter(TIM5,0);
TIM_OC2PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
TIM_Cmd(TIM5,ENABLE ); //使能定时器5
}
}
}
if((TIM5CH3_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)//溢出
{
if(TIM5CH3_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM5CH3_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH3_CAPTURE_STA|=0X80; //标记成功捕获了一次
TIM5CH3_CAPTURE_VAL=0XFFFFFFFF;
}else TIM5CH3_CAPTURE_STA++;
}
}
if(TIM_GetITStatus(TIM5, TIM_IT_CC3) != RESET)//捕获1发生捕获事件
{
if(TIM5CH3_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH3_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM5CH3_CAPTURE_VAL=TIM_GetCapture3(TIM5);//获取当前的捕获值.
TIM_OC3PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM5CH3_CAPTURE_STA=0; //清空
TIM5CH3_CAPTURE_VAL=0;
TIM5CH3_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_Cmd(TIM5,DISABLE ); //关闭定时器5
TIM_SetCounter(TIM5,0);
TIM_OC3PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
TIM_Cmd(TIM5,ENABLE ); //使能定时器5
}
}
}
if((TIM5CH4_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)//溢出
{
if(TIM5CH4_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM5CH4_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH4_CAPTURE_STA|=0X80; //标记成功捕获了一次
TIM5CH4_CAPTURE_VAL=0XFFFFFFFF;
}else TIM5CH4_CAPTURE_STA++;
}
}
if(TIM_GetITStatus(TIM5, TIM_IT_CC4) != RESET)//捕获1发生捕获事件
{
if(TIM5CH4_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH4_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM5CH4_CAPTURE_VAL=TIM_GetCapture4(TIM5);//获取当前的捕获值.
TIM_OC4PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM5CH4_CAPTURE_STA=0; //清空
TIM5CH4_CAPTURE_VAL=0;
TIM5CH4_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_Cmd(TIM5,DISABLE ); //关闭定时器5
TIM_SetCounter(TIM5,0);
TIM_OC4PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
TIM_Cmd(TIM5,ENABLE ); //使能定时器5
}
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4); //清除中断标志位
}
总结
利用串口进行仿真,误差和真实距离相差不大,但由于其器件特性精度无法太高,同时在使用中,我们还需要注意,超声波的探头需要与被测物体的接触面平行,不然会出现较大的误差。所以,在使用过程中经常和滤波算法一起使用。文章仅供参考,欢迎斧正。一些原理借鉴了这位博主:https://blog.csdn.net/m0_37655357/article/details/72934643