网站开发人员是干什么的百度推广
所用单片机:STM32F103C8T6
超声波测距模块链接(拼多多):https://mobile.yangkeduo.com/mall_page.html?ps=WIvk5gnEbW
一、模块使用说明
该链接的超声波模块---HC-SR04支持3种接口模式:GPIO、UART、IIC。本文章仅对常用的GPIO口驱动模式进行说明,读者可自行验证其它接口的可行性。
1.1 特性说明
1.2 工作方式
如果要用其它接口模式的话,需要对模块的电阻进行配置,如下图:
GPIO口驱动的工作方式为,单片机给超声波模块Trig引脚发送一个大于10us的高电平,触发HC-SR04发出8个40kHz的方波,并自动检测是否有信号返回,如果有信号返回,就会通过Echo对单片机输出一个高电平,高电平的持续时间就是超声波从发射到返回的时间。 如下时序图;
必须要注意的一点是,在下面的时序图中也可以看出,单片机给Trig引脚发送10us的高电平后,紧接着要给这个引脚发送低电平;周期最小是200ms!!!也就是说给Trig引脚发送高电平后,不能立刻就读取Echo引脚返回的高电平持续时间!!
等待Trig一个周期完后,检测Echo引脚高电平的持续时间T,如前所说,高电平的持续时间T就是超声波从发射到返回的时间;那么只要知道声波的速度C,用T*C/2就能得到设备与障碍物的距离D。商家提供的声速计算如下:
二、定时器输入捕获检测Echo高电平的原理
定时器输入捕获原理看B站这个up,几分钟就说明白了:【【STM32】动画讲解输入捕获 并实现超声波测距】https://www.bilibili.com/video/BV1HM4m1R75B/?share_source=copy_web&vd_source=91acb6de0cf2d9aa6a14eec0c8d82cdb
假定定时器工作在向上计数模式,上图中的t1~t2时间段就是我们要测量的高电平持续时间。
首先设置定时器输入通道X为上升沿捕获,这样t1时刻就会捕获到当前的cnt值。为了方便后续的计算,此时要将计数器的值清零,并且设置定时器输入通道X为下降沿捕获。等到t2时刻,又会触发下降沿捕获,此时的CNT值就是除去溢出时间外的高电平持续时间,记作CCR,再将输入通道X设为上升沿捕获,如此循环。。。。
如果障碍物距离实在太远,可能导致Echo引脚返回的高电平时间实在太长,以至于超出了定时器重装载计数器的最大值0xffff,此时就会发生溢出。如果代码没有设计考虑溢出的话,就会导致误差过大(整个溢出时间的高电平)。
因此,驱动这个模块,不仅要使用定时器输入捕获中断,还要使用定时器更新中断,记录溢出的次数。最终的脉宽时间 = 溢出次数 *(计数器重装载值+1)+ (CCR+1)
三、参考代码
3.1 硬件驱动实现
#include "stm32f10x.h" // Device header
#include "stdio.h"
#include "stdbool.h"
#include "delay.h"#define TIM_PERIOD 0XFFFFstatic void GpioCofig(void)
{/*GPIO时钟使能*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);/*GPIO引脚配置*/GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13; //超声波Trig触发引脚GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_WriteBit(GPIOB, GPIO_Pin_13, Bit_RESET);//默认上电不触发GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8; //超声波Echo引脚,默认下拉输入0 GPIO_Init(GPIOA, &GPIO_InitStruct);
}static void TimerCofig(void)
{/*- - - - - - - -使能定时器时钟- - - - - - - - */RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);TIM_InternalClockConfig(TIM1);/*- - - - - - - -时基单元初始化- - - - - - - - */TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;TIM_TimeBaseInitStruct.TIM_Period = TIM_PERIOD;TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);/*- - - - - - - -输入捕获结构体初始化- - - - - - - - */TIM_ICInitTypeDef TIM_ICInitStruct;TIM_ICStructInit(&TIM_ICInitStruct);TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;//上升沿捕获TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;//影子寄存器cc1映射到捕获通道TI1TIM_ICInit(TIM1, &TIM_ICInitStruct);/*设置中断*/TIM_ClearFlag(TIM1, TIM_FLAG_Update | TIM_IT_CC1);//清除更新和捕获中断标志位TIM_ITConfig(TIM1, TIM_IT_Update | TIM_IT_CC1, ENABLE);/*- - - - - - - -NVIC配置- - - - - - - - */
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//一个工程只分组一次NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = TIM1_CC_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&NVIC_InitStruct);NVIC_InitStruct.NVIC_IRQChannel = TIM1_UP_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;NVIC_Init(&NVIC_InitStruct);/*- - - - - - - -定时器使能,开始计数- - - - - - - - */TIM_Cmd(TIM1, ENABLE);
}/**
***************************************************
* @brief 超声波测距驱动初始化
* @param
* @return
***************************************************
*/
void UltrasoundDrvInit(void)
{GpioCofig();TimerCofig();
}
3.2 功能实现代码
这里我没有使用温度与声速的关系式计算声速,而是直接采取了20℃下的典型值,只是为了方便验证,如果要求更准确可以加温度校准。
typedef struct
{uint8_t capFinishFlag; //捕获结束标志位uint8_t capStartFlag; //捕获开始标志位uint16_t capCcrValue; //捕获寄存器的值uint16_t capPeriods; //自动重装载寄存器更新溢出次数
}TIM_ICUserValueType_t;static TIM_ICUserValueType_t g_icValueType =
{.capFinishFlag = 0,.capStartFlag = 0,.capCcrValue = 0,.capPeriods = 0,
};/**
***************************************************
* @brief 触发超声波探测
* @param
* @return
***************************************************
*/
void UltrasoundProc(void)
{GPIO_WriteBit(GPIOB, GPIO_Pin_13, Bit_SET);DelayNus(20);GPIO_WriteBit(GPIOB, GPIO_Pin_13, Bit_RESET);/*应当保证低电平时间符合周期要求,这里我有其它办法,就不用延时死等了*/
}static bool CalPulseWidth(uint32_t* pulseWidth)//触发探测后,计算高电平的持续时间
{if (g_icValueType.capFinishFlag == 0){return false;}*pulseWidth = g_icValueType.capPeriods * (TIM_PERIOD + 1) + (g_icValueType.capCcrValue + 1);g_icValueType.capFinishFlag = 0;//捕获结束标志位清零printf("capPeriods is %d\n",g_icValueType.capPeriods);return true;
}/**
***************************************************
* @brief 获取最终和障碍物的距离,cm
* @param
* @return
***************************************************
*/
void GetDistance(float* distance)
{uint32_t pulseWidth = 0;
// while ( CalPulseWidth(&pulseWidth) == false );(void)CalPulseWidth(&pulseWidth);*distance = ((float)pulseWidth*1.0f/10.0f*0.34f)/2.0f;
}
/**
***************************************************
* @brief 定时器1更新中断服务函数
* @param
* @return
***************************************************
*/
void TIM1_UP_IRQHandler(void)
{if ( TIM_GetITStatus(TIM1, TIM_IT_Update) ){/*实现功能*/g_icValueType.capPeriods ++;TIM_ClearITPendingBit(TIM1, TIM_IT_Update);}TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
}/**
***************************************************
* @brief 定时器1输入捕获中断服务函数
* @param
* @return
***************************************************
*/
void TIM1_CC_IRQHandler(void)
{if ( TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET ){//第一次捕获if ( g_icValueType.capStartFlag == 0 ){TIM_SetCounter(TIM1,0);//计数器清零g_icValueType.capPeriods = 0;//之前的自动重装载寄存器溢出次数清零g_icValueType.capCcrValue =0;//存放捕获比较寄存器值的变量清零TIM_OC1PolarityConfig(TIM1, TIM_ICPolarity_Falling);//第一次捕获到上升沿后就把捕获设置为下降沿g_icValueType.capStartFlag = 1;//捕获开始标志置一}//表示为下降沿捕获中断else{g_icValueType.capCcrValue = TIM_GetCapture1(TIM1);//获取捕获比较寄存器的数值,这就是除去溢出外的高电平时间TIM_OC1PolarityConfig(TIM1, TIM_ICPolarity_Rising);//把捕获设置为上升沿g_icValueType.capStartFlag = 0;//开始捕获标志清零g_icValueType.capFinishFlag = 1;//捕获结束标志置一}}TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);//无条件,必须要
}
3.3 .h头文件
#ifndef _ULTRASOUND_DRV_H_
#define _ULTRASOUND_DRV_H_#include "stdint.h"
/**
***************************************************
* @brief 超声波测距驱动初始化
* @param
* @return
***************************************************
*/
void UltrasoundDrvInit(void);/**
***************************************************
* @brief 触发超声波探测,两次触发之间最好间隔60ms,因此函数60ms调用一次
* @param
* @return
***************************************************
*/
void UltrasoundProc(void);/**
***************************************************
* @brief 获取最终和障碍物的距离,cm
* @param
* @return
***************************************************
*/
void GetDistance(float* distance);
#endif
3.4 主函数测试代码
能够显示数值,验证功能就行,下面的代码仅供参考,需要自己根据自己的工程进行修改。
注意,代码中有个60ms的延时一定是要放在UltrasoundProc();后不可改位置不可删除的,用于保证Trig的触发信号不会影响到Echo的回响信号,也就是之前提到的200ms周期!!
static void DrvInit(void)
{DelayInit();SystickInit();BleDrvInit();CarDrvInit();SteeringDrvInit();UltrasoundDrvInit();
}static void AppInit(void)
{RegTaskScheduleCb(TaskScheduleCb);
}int main(void)
{DrvInit();AppInit();float distance = 1.0f;while(1){TaskHandler();//这个不用管,忽略,你们不用写UltrasoundProc();DelayNms(60);//这个延时十分重要,不可省略,保证周期(虽然不是200ms,也能用)GetDistance(&distance);printf("DISTANCE:%.2fcm\r\n\n",distance);//需要会串口,不会可以搜文章、视频,或者用其它显示distance = 0;DelayNms(2000);}
}