一. 采用stm32F103和HC-SR04超声波模块, 使用标准库或HAL库+ 定时器中断,完成1或2路的超声波障碍物测距功能。
CubeMX配置:
1、RCC配置外部高速晶振——HSE
2、SYS配置:Debug设置成Serial Wire
ADC1配置:配置ADC-IN1模数转换
TIM2配置:设置定时器TIM2每1us向上计数一次,通道1为上升沿捕获并连接到超声波模块的ECHO引脚
GPIO配置:PA5接到了HC-SR04的TRIG触发引脚,默认输出低电平
TIM1配置:由上面可知HC-SR04的使用需要us级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器
超声波模块代码:
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;
}
/* 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 */
ranging.h:
#ifndef __SHARP2Y0A21_H
#define __SHARP2Y0A21_H
#include "main.h"
#define Adc1IN1Distance_READ_TIMES 10 //定义红外传感器读取次数,以便取平均值
void DistanceSensor_Init(void); //初始化红外传感器
float DistanceSensor_Get_Val(void); //读取红外传感器的值
#endif
ranging.c:
#include "sharp.h"
#include "adc.h"
#include "main.h"
#include "stdio.h"
//初始化ADC,不用修改
//这里我们仅以规则通道为例
//初始化传感器,需要修改端口和引脚号,这里是c出口,c1引脚,ADC1的IN1
void DistanceSensor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
__HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIOA时钟
//先初始化ADC1通道11 IO口
GPIO_InitStructure.Pin = GPIO_PIN_1;//PA1
GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;//模拟输入
GPIO_InitStructure.Pull = GPIO_NOPULL ;//不带上下拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
MX_ADC1_Init();//初始化ADC1
}
//PA1=IN1
float DistanceSensor_Get_Val(void)
{
uint32_t temp_val=0;
float distemp=0.0;
uint8_t t;
for(t=0;t<Adc1IN1Distance_READ_TIMES;t++)
{
temp_val+=HAL_ADC_GetValue(&hadc1); //读取ADC值,通道1
HAL_Delay(5);
}
temp_val/=Adc1IN1Distance_READ_TIMES;//得到平均值,这个是平均的ADC,
distemp=temp_val*3.3/4095;
//电压对应距离
distemp=(-13.2*distemp*distemp*distemp)+72.84*distemp*distemp-140*distemp+107.12;
return distemp;
}
main 函数:
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_I2C2_Init();
MX_ADC1_Init();
MX_TIM2_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
OLED_CLS();
Hcsr04Init(&htim2, TIM_CHANNEL_1); // 通道选择
Hcsr04Start(); // 启动超声波
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//显示实验对比
OLED_ShowCN_STR(30,0,0,4);
//红外测距显示
HAL_ADC_Start(&hadc1);
adcx=HAL_ADC_GetValue(&hadc1);
distance=DistanceSensor_Get_Val();
OLED_ShowCN_STR(0,2,4,5);
OLED_Showdecimal(80,2,distance,3,2,16);
//超声波测距显示
Hcsr04Start();
OLED_ShowCN_STR(0,4,9,5);
OLED_Showdecimal(80,4,Hcsr04Read(),3,2,16);
//显示单位
OLED_ShowCN_STR(30,6,14,5);
}
/* USER CODE END 3 */
}
二. 当前智能汽车上一般配置有12路超声波雷达,这些专用超声波雷达内置了MCU,直接输出数字化的测距结果,一般硬件接口采用串口RS485,通信协议采用modbus。请思考:
1)RS485与RS232(UART)有什么不同?
-
电气特性:
- RS485:RS485是一种差分信号标准,可以支持多个设备(最多32个),并且可以在较长距离(最高达1200米)内进行通信。它使用两根信号线(A和B线)来传输数据,具备良好的抗干扰能力。
- RS232:RS232是单端信号标准,一般用于较短距离(通常不超过15米)的通信。它使用一个发送线(TX)和一个接收线(RX),并且通常包括地线(GND)作为信号参考。
-
信号电平:
- RS485:RS485的逻辑高低电平范围较广,典型的逻辑高电平为+1.5V至+5V,逻辑低电平为-1.5V至-5V。
- RS232:RS232的逻辑高低电平一般为+3V至+15V的正电平和-3V至-15V的负电平。
-
通信距离:
- RS485:由于其差分信号特性和抗干扰能力,RS485可以支持较长的通信距离,通常在工业控制和长距离传输中被广泛应用。
- RS232:由于是单端信号,通信距离相对较短,主要用于个人计算机和外围设备之间的短距离通信。
-
应用领域:
- RS485:常用于需要多设备连接和远距离传输的场合,例如工业控制系统、自动化设备、仪器仪表等。
- RS232:常见于个人计算机与打印机、调制解调器、串口设备等之间的连接。
2)Modbus协议是什么?
Modbus协议是一种通信协议,通常用于工业自动化领域中的设备间通信。它是一种开放的串行通信协议,最初由Modicon公司开发,现在由Modbus组织(Modbus Organization)管理和推广。
主要特点包括:
-
通信方式:Modbus协议可以通过串口(如RS485、RS232)或以太网(TCP/IP)进行通信,因此具有较好的灵活性。
-
简单性:它设计简单直接,易于实现和维护,适合于各种工业设备之间的数据交换和控制。
-
协议结构:Modbus协议通常包括请求-响应的结构,主要有以下几种常见的变体:
- Modbus RTU:基于二进制方式在串口上通信,通常用于RS485等串口通信。
- Modbus ASCII:基于ASCII码在串口上传输,用于一些特殊的应用场景。
- Modbus TCP/IP:在以太网上使用TCP/IP协议进行通信,支持更高的通信速度和远程访问。
-
功能码:Modbus协议使用功能码来指定执行的操作,如读取数据、写入数据或执行特定的控制功能。
应用领域:
- 工业自动化:控制系统中常用于PLC(可编程逻辑控制器)、传感器、执行器等设备之间的数据交换和控制。
- 能源管理:用于监测和控制电力系统、能源分配系统等。
- 建筑自动化:在楼宇自动化系统中应用广泛,用于监控和控制照明、空调、安防系统等。
总体来说,Modbus协议由于其简单性、可靠性和广泛应用性,成为了工业控制领域中的一种主流通信标准,支持多种设备间的数据交换和控制操作。
3)如果让你设计一款 12路车载超声波雷达,采用 stm32F103+HC-SR04超声波模块,对外提供RS485和Modbus协议,你的设计方案是什么?
设计一款12路车载超声波雷达,采用STM32F103和HC-SR04超声波模块,同时支持RS485通信和Modbus协议,可以考虑以下设计方案:
硬件设计:
-
STM32F103微控制器:
- 使用STM32F103作为主控制器,它具有足够的计算能力和丰富的外设接口,适合于实时数据处理和通信任务。
-
HC-SR04超声波模块:
- 每路超声波雷达使用HC-SR04模块进行测距,该模块简单实用,能够准确测量距离并输出数字化的距离数据。
-
RS485通信接口:
- 集成RS485驱动芯片,确保稳定的长距离通信能力。
- 使用STM32F103的USART(通用同步/异步收发器)接口实现RS485通信,同时支持多个设备连接。
软件设计:
-
Modbus协议栈实现:
- 在STM32F103上实现Modbus RTU协议栈,支持读取和写入超声波雷达的距离数据。
- 实现Modbus功能码,包括读取测距数据、设备配置等功能,确保与其他Modbus设备的兼容性。
-
实时数据处理:
- 使用STM32F103的定时器和中断功能,实现超声波模块的触发和测量控制,确保精准的距离测量和数据更新。
-
多路数据管理:
- 设计合适的数据结构和算法,管理和处理12路超声波雷达的距离数据,确保高效的数据采集和传输。
-
通信接口管理:
- 在STM32F103上实现RS485通信协议,包括物理层控制和数据帧格式处理,确保稳定可靠的通信连接。
功能和性能:
- 实时性:保证测距数据的实时性和精确性,适应车载环境的复杂条件。
- 稳定性:使用STM32F103的硬件看门狗和软件复位机制,提高系统的稳定性和可靠性。
- 可扩展性:设计硬件和软件接口,方便将来扩展更多的超声波雷达通道或其他传感器。
软件架构:
- 采用模块化设计,包括底层驱动、通信协议栈、数据处理模块和应用层接口,确保易于维护和升级。
这样设计的车载超声波雷达能够稳定、精确地测量距离,并通过RS485和Modbus协议与其他设备进行通信,适用于智能汽车等各种应用场景。
部分参考链接:https://blog.csdn.net/qq_65913626/article/details/139239465