系列目录

点击查看目录

模块介绍

引脚 作用
VCC 电源,3.3-5V
Trig (Trigger) 触发超声波脉冲
Echo 回声当接收到反射信号时,引脚产生一个脉冲。脉冲的长度与检测发射信号所需的时间成正比。
GND 接地

(1)超声波测距原理
1)超声波测距原理是在超声波发射装置发出超声波,它的根据是接收器接到超声波时的时间差,与雷达测距原理相似。 超声波发射器向某一方向发射超声波,在发射时刻的同时开始计时,超声波在空气中传播,途中碰到障碍物就立即返回来,超声波接收器收到反射波就立即停止计时。(超声波在空气中的传播速度为340m/s,根据计时器记录的时间t,就可以计算出发射点距障碍物的距离(s),即:s=340t/200)
(2)HC-SR04超声波测距模块工作原理
1)采用IO触发测距,给至少10us的高电平信号。
2)模块自动发送8个40KHz的方波,自动检测是否有信号返回。
3)有信号返回,通过IO输出一高电平,高电平持续时间就是超声波从发射到返回的时间声波从发射到返回的时间
————————————————
版权声明:本文为CSDN博主「精神小火君」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43853307/article/details/104065508

接线

Trig接到SA5上即PA4上
Echo接到SD5即PA6上

CubeMX配置

复制上一节工程,新增两个GPIO,PA4设置为推挽输出,初始高电平,标签Trig
PA6设置为输入,浮空模式,标签Echo

编写超声波代码

由于驱动模块涉及到多次翻转电平,为了省事,我们可以define一下GPIO输入输出代码。
打开sensor.h,将WritePin定义为Trig(),这样可以直接Trig(1)进行输出高电平。将ReadPin定义为Echo()

#define Trig(x) HAL_GPIO_WritePin(Trig_GPIO_Port,Trig_Pin,(GPIO_PinState)x)
#define Echo() HAL_GPIO_ReadPin(Echo_GPIO_Port,Echo_Pin)

然后打开sensor.c编写超声波模块驱动代码。
hcsr04的驱动思路为:
1.Trig引脚输出一个周期为20us的方波
2.等待Echo引脚从高拉低
3.开启定时器,等待Echo从低拉高
4.计算定时时间,算出距离。
输出方波很简单,

	Trig(1);
	csb_Delay_Us(20);
	Trig(0);

即可。
这里涉及个问题,即STM32HAL里面的延时是ms,没有us。参考51延时,我们可以用两个for循环进行延时。

void csb_Delay_Us(uint16_t time)  //延时函数
{ 
	uint16_t i,j;
	for(i=0;i<time;i++)
  		for(j=0;j<9;j++);
}

然后用while(Echo() == 0);来等待电平变换。
前面驱动arm时我们已经开启了三个基本定时器用于输出PWM,这里我们可以直接拿来用于计数。
在.c顶部引入"tim.h"。
__HAL_TIM_SetCounter(&htim2,0);将定时器2计数值归零,
然后用while(Echo() == 1);来等待Echo从低拉高。完毕后csb_t =__HAL_TIM_GetCounter(&htim2);来获取计时时间。注意我们定时器分频72,因此时间分辨率为1us,即距离为340/2m/s=0.017cm/us(声波来回时间)
进行下单位换算csb_t = csb_t*0.017;,获取距离的代码就完成了。
完整如下:

 /**
 * @brief  :采集超声波数据
 * @param  :None
 * @retval 采集的数据  
**/
uint16_t get_csb_value(void) {
	uint16_t csb_t;
	Trig(1);
	csb_Delay_Us(20);
	Trig(0);
	while(Echo() == 0);      //等待接收口高电平输出
	__HAL_TIM_SetCounter(&htim2,0);
	while(Echo() == 1);     
	csb_t =__HAL_TIM_GetCounter(&htim2);//获取时间,分辨率为1US
	//340m/s = 0.017cm/us
	if(csb_t < 20000) {
		csb_t = csb_t*0.017;
		return csb_t;
	}
	return 0;
}

一般由于震动等干扰,获取一次值正确的概率很低。我们可以获取多次数据,然后取中间值。
代码如下:

/**
 * @brief  :对于给定元素长度数组,从小到大进行排序
 * @param  :数组地址,元素个数
 * @retval None
**/
void selection_sort(int *a, int len) {
    int i,j,mi,t;
    for(i=0;i<len-1;i++) {
        mi = i;
        for(j=i+1;j<len;j++) {
            if(a[mi] > a[j]) {
                mi = j;
            }
        }

        if(mi != i) {
            t = a[mi];
            a[mi] = a[i];
            a[i] = t;
        }
    }
}
/**
 * @brief  :处理超声波采集到的数据,取采集到的中间值
 * @param  :数组地址,元素个数
 * @retval 处理后的超声波数据
**/
int get_adc_csb_middle() {
	uint8_t i;
	static int ad_value[5] = {0}, myvalue;
	for(i=0;i<5;i++)ad_value[i] = get_csb_value();
	selection_sort(ad_value, 5);
	myvalue = ad_value[2];
	return myvalue;
}

自由避障设计

得到超声波返回的数据后,我们可以根据机器人前方障碍物的距离来设计路径规划。一个比较 智障 简单的方案就是,有障碍物就右转。

/**
 * @brief  : 根据超声波返回的数据执行自动避障功能。
 * @param  :数组地址,元素个数
 * @retval 处理后的超声波数据  
**/  
void hcsr_task(void)
{
	static uint32_t systick_ms_bak = 0;
	uint8_t cmd_return[100];
	int speed = 500, adc_csb;
	int millis=HAL_GetTick();//获取系统时间
	if(millis - systick_ms_bak > 20) {
		systick_ms_bak = HAL_GetTick();
		adc_csb = get_adc_csb_middle();//获取a0的ad值,计算出距离
		sprintf((char *)cmd_return, "adc_csb = %d\r\n", adc_csb);
		usart_send_str(&huart3,cmd_return);
		if(adc_csb < 30) {//距离低于20cm就右转
			motor_set(speed+500, -speed-500);
			HAL_Delay(500);
		}else if(adc_csb>80){
			motor_set(1000, 1000);
		}else{
			motor_set(speed,speed);
		}
	}
}

然后在.h中声明该函数,即可在其他地方引用。

#include "stdint.h"
#define sound_vol HAL_GPIO_ReadPin(sound_GPIO_Port,sound_Pin)
#define Trig(x) HAL_GPIO_WritePin(Trig_GPIO_Port,Trig_Pin,(GPIO_PinState)x)
#define Echo() HAL_GPIO_ReadPin(Echo_GPIO_Port,Echo_Pin)
void sound_task(void);
void hcsr_task(void);
uint16_t get_csb_value(void);

例如在主函数中,接收到H字符开启自动避障功能。

case 'H':while(1){hcsr_task();};break;

工程源码

国内用户请使用gitee克隆或是使用代理访问Github
https://github.com/USTHzhanglu/stm32-hal/tree/main/hcsr-04