前言:本文为手把手教学基于STM32的超声波雷达项目——HC-SR04雷达。本次项目采用的是STM32作为MCU,搭配常用的HC-SR04超声波模块与舵机SG90实现模拟雷达检测的效果。模拟了雷达图UI可以拟合构建当前环境下的平面地图(超低配版SLAM构图)。本项目可能还存在可以改进与升级的地方,欢迎大家交流讨论。(文末有代码开源!)
实验硬件:STM32F103ZET6;8针2.4寸TFT-LCD(320×240);HC-SR04;SG90
硬件实物图:
效果图:
引脚连接:
LCD显示引脚:
VCC --> 3.3V
GND --> GND
CS --> PB11
Reset --> PB12
DC --> PB10
SDI --> PB15
SCK --> PB13
LED --> PB9(控制LCD背光,可以同PWM调节改变LCD亮暗)
HC-SR04引脚:
VCC --> 5.0V
GND --> GND
Trig --> PA5
Echo --> PA0
SG90引脚:
VCC --> 5.0V
GND --> GND
PWM(信号线) --> PA6
雷达,是英文Radar的音译,源于radio detection and ranging的缩写,意思为“无线电探测和测距”,即用无线电的方法发现目标并测定它们的空间位置。因此,雷达也被称为“无线电定位”。雷达是利用电磁波探测目标的电子设备。雷达发射电磁波对目标进行照射并接收其回波,由此获得目标至电磁波发射点的距离、距离变化率(径向速度)、方位、高度等信息。
本项目是利用超声波HC-SR04模块配合SG90舵机(舵机负责带动HC-SR04旋转)实现类似电磁波雷达的效果。配合TFT-LCD上的雷达UI界面画出障碍物距离点,可以模拟类似于雷达的效果图。同时,也实现了拟合构建平面地图的效果。(也可以扩展为避障小车的一部分)
超声波是振动频率高于20KHZ的机械波。它具有频率高、波长短、绕射现象小、方向性好、能够成为射线而定向传播等特点,应用广泛,适合大学生、工程师、技术人员以及电子爱好者等操作。(本项目中借用超声波HC-SR04模块代替电磁波去测距)
新版HC-SRO4,性能远超老版HC-SRO4、US-015,在测距精度高于老版HC-SRO4和US-015的情况下,测距范围更远,可达6米,远超一般超声波测距模块。采用CS-100A超声波测距SOC芯片,高性能,工业级,宽电压、低价格,只有普通超声波测距模块一半的价格,而性能远超普通超声波测距模块。性能与US-025相同,均采用CS100A芯片,接口完全兼容。
工作原理:
1、采用IO触发测量距离,给至少10us的高电平;
2、模块自动发送8个40khz的方波,自动检测是否有信号返回;
3、有信号返回、通过lo输出一高电平、高电平持续的时间就是超声波从发射到返回的时间,测试距离=(高电平时间*声速(340M/s)/2);
本模块有一个接口:4Pin供电及通信接口。为2.54mm间距的弯排针,如图所示:从左到右依次编号1、2、3、4,它们的定义如下∶
1号Pin:接VCC电源(直流3V-5.5V)。
2号Pin:接外部电路的Trig端,向此管脚输入一个10uS以上的高电平,可触发模块测距。
3号Pin:接外部电路的Echo端,当测距结束时,此管脚会输出一个高电平,电平宽度为超声波往返时间之和。
4号Pin:接外部电路的地。
舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。在高档遥控玩具,如飞机、潜艇模型,遥控机器人中已经得到了普遍应用。(本项目中使用舵机SG90去带动HC-SR04模块运动,实现多角度的动态实时测距)
SG90舵机外观:
工作原理:
舵机的控制一般需要一个20ms左右(50Hz)的时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。以180度角度伺服为例,那么对应的控制关系是这样的:
特别注意:市面上有180°舵机和360°舵机,两者有所区别,读者朋友购买的时候需要注意一下。
180°舵机版本:可以控制旋转角度、有角度定位。上电后舵机自动复位到0°,通过一定参数的脉冲信号控制它的角度。
360°舵机版本:不可控制角度,只能顺时针旋转、逆时针旋转、停止、调节转速。无角度定位,上电不会复位到0°。因为这是360°任意旋转的,没有0。通过一定参数的脉冲信号控制它的选择。
编程思路:读者朋友控制舵机的时候,只需要使用定时器去产生PWM调节。用PWM调节出对应ms数的脉冲即可实现对舵机的固定角度控制。
本项目中TFT-LCD作为雷达图显示的重要载体,需要使用TFT-LCD中各种API函数去首先模仿常规雷达图绘制,之后需要借助TFT-LCD屏幕去显示超声波扫描的角度与障碍物的距离显示。最后的重点,自己设计可以根据舵机角度(决定HC-SR04的测量角度)和HC-SR04测得的距离换算值去画点,乃至画线。
关于TFT-LCD使用与编程有不熟练的朋友,可以去阅读本人的另一篇文章,希望可以对您有所帮助。文章地址:【强烈推荐】基于STM32的TFT-LCD各种显示实现(内容详尽含代码)_混分巨兽龙某某的博客-CSDN博客_tftlcd显示屏引脚
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug设置成Serial Wire(否则可能导致芯片自锁);
3、GPIO配置:将PB9,PB10,PB11,PB12都设置为GPIO_OUTPUT,速度为:Hight;
4、SPI配置:配置使用SPI2作为TFT-LCD通讯方式(可以考虑GPIO模拟SPI)
5、GPIO配置:PA5接到了HC-SR04的TRIG触发引脚,默认输出低电平
6、TIM2配置:设置定时器TIM2每1us向上计数一次,通道1为上升沿捕获并连接到超声波模块的ECHO引脚,记得开启定时器中断(涉及到捕获中断+定时器溢出中断)。
7、TIM1配置:HC-SR04的使用都需要us级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器;
8、TIM3配置:使用TIM3的Channel1产生PWM信号(控制SG90);
数据参数意义:
此时产生PWM波形频率:72M / (719 +1)/ (1999+1) = 50Hz
定时器周期:1/50 = 20ms
9、时钟树配置:
10、工程配置
其实,超声波HC-SR04的驱动就是基于GPIO口的调用。同时,由于超声波测距模块是基于超声波的物理性质,去进行距离测量,故此其精度受到很多因素影响。(这里为短距离雷达测距模拟,精度足够无需考虑补偿)
HC-SR04.h:
#ifndef HCSR04_H_
#define HCSR04_H_
#include "main.h"
#include "delay.h"
typedef struct
{
uint8_t edge_state;
uint16_t tim_overflow_counter;
uint32_t prescaler;
uint32_t period;
uint32_t t1; // 上升沿时间
uint32_t t2; // 下降沿时间
uint32_t high_level_us; // 高电平持续时间
float distance;
TIM_TypeDef* instance;
uint32_t ic_tim_ch;
HAL_TIM_ActiveChannel active_channel;
}Hcsr04InfoTypeDef;
extern Hcsr04InfoTypeDef Hcsr04Info;
/**
* @description: 超声波模块的输入捕获定时器通道初始化
* @param {TIM_HandleTypeDef} *htim
* @param {uint32_t} Channel
* @return {*}
*/
void Hcsr04Init(TIM_HandleTypeDef *htim, uint32_t Channel);
/**
* @description: HC-SR04触发
* @param {*}
* @return {*}
*/
void Hcsr04Start();
/**
* @description: 定时器计数溢出中断处理函数
* @param {*} main.c中重定义void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
* @return {*}
*/
void Hcsr04TimOverflowIsr(TIM_HandleTypeDef *htim);
/**
* @description: 输入捕获计算高电平时间->距离
* @param {*} main.c中重定义void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
* @return {*}
*/
void Hcsr04TimIcIsr(TIM_HandleTypeDef* htim);
/**
* @description: 读取距离
* @param {*}
* @return {*}
*/
float Hcsr04Read();
#endif /* HCSR04_H_ */
HC-SR04.c:
#include "hc-sr04.h"
Hcsr04InfoTypeDef Hcsr04Info;
/**
* @description: 超声波模块的输入捕获定时器通道初始化
* @param {TIM_HandleTypeDef} *htim
* @param {uint32_t} Channel
* @return {*}
*/
void Hcsr04Init(TIM_HandleTypeDef *htim, uint32_t Channel)
{
/*--------[ Configure The HCSR04 IC Timer Channel ] */
// MX_TIM2_Init(); // cubemx中配置
Hcsr04Info.prescaler = htim->Init.Prescaler; // 72-1
Hcsr04Info.period = htim->Init.Period; // 65535
Hcsr04Info.instance = htim->Instance; // TIM2
Hcsr04Info.ic_tim_ch = Channel;
if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_1)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_1; // TIM_CHANNEL_4
}
else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_2)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_2; // TIM_CHANNEL_4
}
else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_3)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_3; // TIM_CHANNEL_4
}
else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_4)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_4; // TIM_CHANNEL_4
}
else if(Hcsr04Info.ic_tim_ch == TIM_CHANNEL_4)
{
Hcsr04Info.active_channel = HAL_TIM_ACTIVE_CHANNEL_4; // TIM_CHANNEL_4
}
/*--------[ Start The ICU Channel ]-------*/
HAL_TIM_Base_Start_IT(htim);
HAL_TIM_IC_Start_IT(htim, Channel);
}
/**
* @description: HC-SR04触发
* @param {*}
* @return {*}
*/
void Hcsr04Start()
{
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
DelayUs(10); // 10us以上
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
}
/**
* @description: 定时器计数溢出中断处理函数
* @param {*} main.c中重定义void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
* @return {*}
*/
void Hcsr04TimOverflowIsr(TIM_HandleTypeDef *htim)
{
if(htim->Instance == Hcsr04Info.instance) // TIM2
{
Hcsr04Info.tim_overflow_counter++;
}
}
/**
* @description: 输入捕获计算高电平时间->距离
* @param {*} main.c中重定义void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
* @return {*}
*/
void Hcsr04TimIcIsr(TIM_HandleTypeDef* htim)
{
if((htim->Instance == Hcsr04Info.instance) && (htim->Channel == Hcsr04Info.active_channel))
{
if(Hcsr04Info.edge_state == 0) // 捕获上升沿
{
// 得到上升沿开始时间T1,并更改输入捕获为下降沿
Hcsr04Info.t1 = HAL_TIM_ReadCapturedValue(htim, Hcsr04Info.ic_tim_ch);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, Hcsr04Info.ic_tim_ch, TIM_INPUTCHANNELPOLARITY_FALLING);
Hcsr04Info.tim_overflow_counter = 0; // 定时器溢出计数器清零
Hcsr04Info.edge_state = 1; // 上升沿、下降沿捕获标志位
}
else if(Hcsr04Info.edge_state == 1) // 捕获下降沿
{
// 捕获下降沿时间T2,并计算高电平时间
Hcsr04Info.t2 = HAL_TIM_ReadCapturedValue(htim, Hcsr04Info.ic_tim_ch);
Hcsr04Info.t2 += Hcsr04Info.tim_overflow_counter * Hcsr04Info.period; // 需要考虑定时器溢出中断
Hcsr04Info.high_level_us = Hcsr04Info.t2 - Hcsr04Info.t1; // 高电平持续时间 = 下降沿时间点 - 上升沿时间点
// 计算距离
Hcsr04Info.distance = (Hcsr04Info.high_level_us / 1000000.0) * 340.0 / 2.0 * 100.0;
// 重新开启上升沿捕获
Hcsr04Info.edge_state = 0; // 一次采集完毕,清零
__HAL_TIM_SET_CAPTUREPOLARITY(htim, Hcsr04Info.ic_tim_ch, TIM_INPUTCHANNELPOLARITY_RISING);
}
}
}
/**
* @description: 读取距离
* @param {*}
* @return {*}
*/
float Hcsr04Read()
{
// 测距结果限幅
if(Hcsr04Info.distance >= 500)
{
Hcsr04Info.distance = 500; //元器件资料说是600cm最高距离,这里保守一点
}
return Hcsr04Info.distance;
}
由于利用中断去读取定时测算的脉冲距离,所以这里需要重写定时器的中断服务函数。(这部分放在main.c最后即可)
/* USER CODE BEGIN 4 */
/**
* @description: 定时器输出捕获中断
* @param {TIM_HandleTypeDef} *htim
* @return {*}
*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) //捕获回调函数
{
Hcsr04TimIcIsr(htim);
}
/**
* @description: 定时器溢出中断
* @param {*}
* @return {*}
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) //在中断回调函数中添加用户代码
{
Hcsr04TimOverflowIsr(htim);
}
/* USER CODE END 4 */
HAL_TIM_PwM_Start ( &htim3,TIM_CHANNEL_1); //PWM初始化定时器
while(1) //while循环中放入
{
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,i); //舵机运动
//i的取值代表舵机指向的角度(0°~180°)
}
注意事项:
模拟舵机SG90的使用就是利用PWM的计数数值去控制舵机所指的角度。
通过修改句柄__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,value)里的参数value可以实现舵机角度控制。
这里以SG90舵机为例给大家讲解:
已知0.5ms指向0°位置,2.5ms指向180°位置。
比如需要指向m°处:
2.5-0.5=2ms --> 对应于180°
value/(1999+1)*20=0.5+(m/180)× 2
LCD显示部分其实都是非常基础的操作,不熟悉的可以去看看笔者另一篇文章了解一下。作者这里把工程中不一样的地方指出来一下。【强烈推荐】基于STM32的TFT-LCD各种显示实现(内容详尽含代码)_混分巨兽龙某某的博客-CSDN博客_lcd显示屏显示代码
这部分代码主要绘制出常规雷达图的UI界面,需要注意的是画出多组同心半圆。
其中,难点就是需要自己设计出函数:可以用角度与长度(半径)去画出斜线。
API函数:void Radarline(double k,int r)
radar.h:
#ifndef __RADAR_H
#define __RADAR_H
void Radarline(double k,int r);
void radar_picture();
#endif
radar.c:
#include "radar.h"
#include "LCDAPI.h"
#include "lcd.h"
#include "math.h"
#include "hc-sr04.h"
void Radarline(double k,int r)
{
double x,y;
x=160+r*(double)cos(k/180*3.1415926);
y=200-r*(double)sin(k/180*3.1415926);
Gui_DrawLine(160,200,x,y,GREEN);
}
void radar_picture()
{
Gui_DrawFont_GBK24(307,98,GREEN,BLACK,"°");
Gui_DrawFont_GBK24(263,58,GREEN,BLACK,"°");
Gui_DrawFont_GBK24(181,28,GREEN,BLACK,"°");
Gui_DrawFont_GBK24(110,40,GREEN,BLACK,"°");
Gui_DrawFont_GBK24(35,90,GREEN,BLACK,"°");
//雷达图
Gui_Circle(160,200,152,GREEN);
Gui_Circle(160,200,114,GREEN);
Gui_Circle(160,200,76,GREEN);
Gui_Circle(160,200,38,GREEN);
Radarline(30,170);
Radarline(60,170);
Radarline(90,170);
Radarline(120,170);
Radarline(150,170);
rectangle(0,200,320,240,BLACK);
Gui_DrawLine(0,200,320,200,GREEN);
//数据信息
Gui_DrawFont_GBK24(0,212,GREEN,BLACK,"距离");
Gui_DrawFont_GBK24(50,219,GREEN,BLACK,":");
Gui_DrawFont_GBK24(0,0,GREEN,BLACK,"角度");
Gui_DrawFont_GBK24(50,7,GREEN,BLACK,":");
Gui_DrawFont_GBK24(117,211,GREEN,BLACK,"CM");
Gui_DrawFont_GBK24(100,0,GREEN,BLACK,"°");
Gui_DrawFont_GBK16(175,205,GREEN,BLACK,"25");
Gui_DrawFont_GBK16(210,205,GREEN,BLACK,"50");
Gui_DrawFont_GBK16(245,205,GREEN,BLACK,"75");
Gui_DrawFont_GBK16(285,205,GREEN,BLACK,"100");
Gui_DrawFont_GBK16(290,100,GREEN,BLACK,"30");
Gui_DrawFont_GBK16(247,60,GREEN,BLACK,"60");
Gui_DrawFont_GBK16(165,30,GREEN,BLACK,"90");
Gui_DrawFont_GBK16(85,42,GREEN,BLACK,"120");
Gui_DrawFont_GBK16(10,92,GREEN,BLACK,"150");
Gui_DrawFont_GBK24(190,0,GREEN,BLACK,"超声波雷达");
}
代码核心:
协调处理SG90舵机和HC-SR04超声波模块,根据SG90舵机的转动角度特性,i=50是0°,i=250是180°,200的差值对应给了180°。注意我们的画线函数需要知道2个点起始点与终止点,起始点肯定是我们测距的起始点,也是雷达图上的圆心(160,200),现在问题的关键就变成了寻找终止点,终止点的计算可以通过直角坐标系利用三角函数转换得到。求得终止点之后就可以使用常规的画线亦或是画点函数去实时反馈环境距离情况了。
main.c:
int main(void)
{
/* USER CODE BEGIN 1 */
int i=50;
double k;
int d;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI2_Init();
MX_TIM1_Init();
MX_TIM2_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
Lcd_Init();
LCD_LED_SET;
LCD_RST_SET;
Lcd_Clear(BLACK);
//雷达图
radar_picture();
Hcsr04Init(&htim2, TIM_CHANNEL_1);
Hcsr04Start();
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); //初始化
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(i==50)
{
for(i=50;i<250;i++)
{
Hcsr04Start();
d = (int)Hcsr04Read()*152/100;
if(d>152) //距离越界
{
d=155;
}
LCD_Showdecimal(63,215,Hcsr04Read(),3,2,16);
LCD_ShowNum(65,5,(double)(i-50)/200*180,3);
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,i); //舵机转动
HAL_Delay(100);
Radarline2((double)((i-50)*180/200),d);
// Radarline((double)((i-50)*180/200),(int)Hcsr04Read()*152/300);
}
}
if(i==250)
{
for(i=250;i>50;i--)
{
Hcsr04Start();
d = (int)Hcsr04Read()*152/100;
if(d>152) //距离越界
{
d=155;
}
LCD_Showdecimal(63,215,Hcsr04Read(),3,2,16);
LCD_ShowNum(65,5,(double)(i-50)/200*180,3);
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,i); //舵机转动
HAL_Delay(100);
Radarline2((double)((i-50)*180/200),d);
}
}
}
/* USER CODE END 3 */
}
超声波雷达
代码地址: 基于STM32的超声波雷达项目(TFT-LCD)-嵌入式文档类资源-CSDN文库
如果积分不够的朋友,点波关注,评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!