基于STM32F103超声波测距 、和HC-SR04的12路RS485与Modbus设计方案

一. 采用stm32F103和HC-SR04超声波模块, 使用标准库或HAL库+ 定时器中断,完成1或2路的超声波障碍物测距功能。
1)测试数据包含噪声,程序需要进行滤波处理;将测距数值通过串口上传到上位机串口助手;
2)根据障碍物距离远近,控制一个蜂鸣器(可以用LED灯代替)发出频率不同的声音(或LED不同闪烁),即输出占空比变化的PWM波形;
3)在没有超声波模块硬件的场景下,先使用Keil中的仿真逻辑分析仪,观察分析对应管脚上的时序波形,判读是否符合协议规范。
二. 当前智能汽车上一般配置有12路超声波雷达,这些专用超声波雷达内置了MCU,直接输出数字化的测距结果,一般硬件接口采用串口RS485,通信协议采用modbus。请思考:
1)RS485与RS232(UART)有什么不同?
2)Modbus协议是什么?
3)如果让你设计一款 12路车载超声波雷达,采用 stm32F103+HC-SR04超声波模块,对外提供RS485和Modbus协议,你的设计方案是什么?

一. 采用stm32F103和HC-SR04超声波模块, 使用标准库或HAL库+ 定时器中断,完成1路的超声波障碍物测距功能。

1. HC-SR04超声波模块电气参数

在这里插入图片描述

2. 工作原理

在这里插入图片描述
在这里插入图片描述

3. 使用HAL库实现

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
选一个GPIO,这里选PA2

在这里插入图片描述
配置串口
在这里插入图片描述
使用定时器2 通道1 并开启定时器中断在这里插入图片描述
在这里插入图片描述
到这就可以生成工程了。
在这里插入图片描述

SR04.h

#ifndef __SR04_H
#define __SR04_H
#include "main.h"
#include "tim.h"
#include "stdio.h"
 
#define TRIG_H  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET)
#define TRIG_L  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET)
 
void delay_us(uint32_t us);
void SR04_GetData(void);
 
#endif

SR04.c

#include "SR04.h"
 
float distant;      //测量距离
uint32_t measure_Buf[3] = {0};   //存放定时器计数值的数组
uint8_t  measure_Cnt = 0;    //状态标志位
uint32_t high_time;   //超声波模块返回的高电平时间
 
 
//===============================================读取距离
void SR04_GetData(void)
{
switch (measure_Cnt){
	case 0:
         TRIG_H;
         delay_us(30);
         TRIG_L;
    
		measure_Cnt++;
		__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
		HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);	//启动输入捕获       或者: __HAL_TIM_ENABLE(&htim5);                                                                                    		
        break;
	case 3:
		high_time = measure_Buf[1]- measure_Buf[0];    //高电平时间
         printf("\r\n----高电平时间 %d-us----\r\n",high_time);							
		distant=(high_time*0.034)/2;  //单位cm
        printf("\r\n-检测距离为 %.2f-cm-\r\n",distant);          
		measure_Cnt = 0;  //清空标志位
        TIM2->CNT=0;     //清空计时器计数
		break;
				
	}
}
 
 
//===============================================us延时函数
    void delay_us(uint32_t us)//主频72M
{
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
    while (delay--)
	{
		;
	}
}
 
//===============================================中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//
{
	
	if(TIM2 == htim->Instance)// 判断触发的中断的定时器为TIM2
	{
		switch(measure_Cnt){
			case 1:
				measure_Buf[0] = HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);//获取当前的捕获值.
				__HAL_TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);  //设置为下降沿捕获
				measure_Cnt++;                                            
				break;              
			case 2:
				measure_Buf[1] = HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);//获取当前的捕获值.
				HAL_TIM_IC_Stop_IT(&htim2,TIM_CHANNEL_1); //停止捕获   或者: __HAL_TIM_DISABLE(&htim5);
				measure_Cnt++;  
                         
		}
	
	}
	
}
 

main.c添加代码

#include "SR04.h"

  while (1)
  {
    /* USER CODE END WHILE */
		SR04_GetData(  );
      HAL_Delay(1000);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

usart.c添加代码

#include "stdio.h"
/* USER CODE BEGIN 0 */
int fputc(int ch,FILE *f)
{
	HAL_UART_Transmit (&huart1 ,(uint8_t *)&ch,1,HAL_MAX_DELAY );
	return ch;
}

4. 运行效果

如图是一段长度为12cm的测量距离
在这里插入图片描述
串口接收到的测量结果如下
在这里插入图片描述
可以观察到测试数据包含噪声,程序需要进行滤波处理

5. 滤波处理

数字滤波是数据处理是常用、灵活、有效的方法。前面的按键程序已经用到了滤波,属于开关量滤波,这里要讨论的是模拟量滤波程序,包括最常用的两种方法,中值滤波和平均值滤波。

中值滤波的原理是,每次取最近几个数的中间值作为输出数据,每个波形的最高和最低几个数被滤掉,优点是基本保留原有数据,能有效抑制大幅值低频尖峰干扰,俗称椒盐噪声。

平均值滤波,就是对最近一些数求平均,是最常用最简单的方法,对高频低幅值随机噪声有效,缺点是会损失原始数据中的高频分量,对高幅值干扰会扩大影响。

采用平均值滤波,输出变稳定了

每三次取一次平均值输出,但会有一些延时。
在这里插入图片描述

#include "SR04.h"
 #include <stdint.h>
float distant;      //测量距离
uint32_t measure_Buf[3] = {0};   //存放定时器计数值的数组
uint8_t  measure_Cnt = 0;    //状态标志位
uint32_t high_time;   //超声波模块返回的高电平时间

 
//===============================================读取距离
void SR04_GetData(void)
{
	static uint8_t measure_Index = 0;  // 用于记录当前测量次数的索引
switch (measure_Cnt){
	case 0:
         TRIG_H;
         delay_us(30);
         TRIG_L;
    
		measure_Cnt++;
		__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
		HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);	//启动输入捕获       或者: __HAL_TIM_ENABLE(&htim5);                                                                                    		
        break;
	case 3:
		// 完成三次测量,计算平均值
        float total_high_time = 0;
        for (int i = 0; i < 3; ++i)
        {
            high_time = measure_Buf[(i + 1) % 3] - measure_Buf[i];  // 计算相邻两个计数值的差
            total_high_time += high_time;  // 累加高电平时间
        }
        float average_high_time = total_high_time / 3;  // 计算平均高电平时间

        // 将平均高电平时间转换为距离
        float average_distant = (average_high_time * 0.034f) / 2;  // 单位cm

        printf("\r\n----平均高电平时间 %d-us----\r\n", (uint32_t)average_high_time);
        printf("\r\n-平均检测距离为 %.2f-cm-\r\n", average_distant);

        // 准备下一次测量
        measure_Cnt = 0;
        measure_Index = 0;  // 重置索引
        TIM2->CNT = 0;  // 清空计时器计数
        break;
    }
}

//======================其他代码不变========================

6.pwm控制led呼吸灯,反映距离的远近

在原来的项目基础上进行修改
在这里插入图片描述
在这里插入图片描述
添加根据距离输出不同的pwm波形的代码
tim2 通道二产生pwm波

TIM_HandleTypeDef htim2;
GPIO_InitTypeDef GPIO_InitStruct = {0};

void TIM2_PWM_Init(uint16_t arr, uint16_t psc)
{
    // 初始化TIM2
    __HAL_RCC_TIM2_CLK_ENABLE();

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = psc;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = arr;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
    {
        // 初始化错误处理
        Error_Handler();
    }

    // 配置PWM通道2
    TIM_OC_InitTypeDef sConfigOC = {0};
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 0;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
    {
        // 配置错误处理
        Error_Handler();
    }

    // 启动PWM信号通道2
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);

    // 初始化GPIO,假设PA1为TIM2的CH2引脚
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

控制超声波的模块

#include "SR04.h"
#include <stdint.h>


float distant;      //测量距离
uint32_t measure_Buf[3] = {0};   //存放定时器计数值的数组
uint8_t  measure_Cnt = 0;    //状态标志位
uint32_t high_time;   //超声波模块返回的高电平时间



  /* USER CODE BEGIN 2 */

//===============================================读取距离
void SR04_GetData(void)
{
		
	static uint8_t measure_Index = 0;  // 用于记录当前测量次数的索引
	switch (measure_Cnt){
	case 0:
         TRIG_H;
         HAL_Delay(1100);
         TRIG_L;
    
		measure_Cnt++;
		__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
		HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);	//启动输入捕获                                                                                        		
        break;
	case 3:
		high_time = measure_Buf[1]- measure_Buf[0];    //高电平时间
         printf("\r\n----高电平时间 %d-us----\r\n",high_time);							
		distant=(high_time*0.034)/2;  //单位cm
         printf("\r\n-检测距离为 %.2f-cm-\r\n",distant);  
		//PWM驱动led灯
			if(distance <10){
				k_4k();//4k
			}else if(distance >=10 && distance<20){
				k_2k();//2k
			}else if(distance >=20 && distance<40){
				k_1k();//1k
			}
		measure_Cnt = 0;  //清空标志位
		HAL_Delay(1100);
		break;
				
	}
}
 
 
//===============================================us延时函数
    void delay_us(uint32_t us)//主频72M
{
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
    while (delay--)
	{
		;
	}
}
 
//===============================================中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//
{
	
	if(TIM2 == htim->Instance)// 判断触发的中断的定时器为TIM2
	{
		switch(measure_Cnt){
			case 1:
				measure_Buf[0] = HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);//获取当前的捕获值.
				__HAL_TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);  //设置为下降沿捕获
				measure_Cnt++;                                            
				break;              
			case 2:
				measure_Buf[1] = HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);//获取当前的捕获值.
				HAL_TIM_IC_Stop_IT(&htim2,TIM_CHANNEL_1); //停止捕获   或者: __HAL_TIM_DISABLE(&htim5);
				measure_Cnt++;                   
		}
	
	}
	
}

void PWM_SetCompare2(uint16_t Compare)
{
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, Compare);
}

void k_4k(void)
{
    // 4000Hz = 72M / (72 * 250) = 4k
    TIM2_PWM_Init(249, 71); // 4000Hz的声音 比较尖锐
    for(int i = 0; i < 3; i++)
    {
        PWM_SetCompare2(125);
        HAL_Delay(1000);
        PWM_SetCompare2(0);
        HAL_Delay(1000);
    }
	HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);
}

void k_1k(void)
{
    // 1000Hz = 72M / (72 * 1000) = 1k
    TIM2_PWM_Init(999, 71); // 1KHz 声音比较低
    PWM_SetCompare2(500);
    HAL_Delay(1000);
    PWM_SetCompare2(0); // 不发生占空比设为0
    HAL_Delay(1000);
	HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);
}

void k_2k(void)
{
    // 72M / (72 * 500) = 2k
    TIM2_PWM_Init(499, 71);
    for(int i = 0; i < 2; i++)
    {
        PWM_SetCompare2(250);
        HAL_Delay(1000);
        PWM_SetCompare2(0);
        HAL_Delay(1000);
    }
	HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);
}

// 错误处理函数
void Error_Handler(void)
{
    // 这里可以实现自定义的错误处理逻辑
    while(1)
    {
    }
}

主函数修改的部分代码

 MX_GPIO_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  TIM2_PWM_Init(999, 71);
  /* USER CODE BEGIN 2 */
  //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 */
	  SR04_GetData(  );
      HAL_Delay(1000);
	  
  /* USER CODE END 3 */
}
  }

不知道是不是通道间会有干扰,偶尔会输出一些错误的信息。
在这里插入图片描述
在这里插入图片描述
根据检测是距离不同led灯亮度会产生变化,从而模拟蜂鸣器发出不同的声音。

二、RS485与RS232(UART)有什么不同?

RS485和RS232(UART)是两种常见的串行通信协议,主要区别如下:

  1. 通信距离和速率
    • RS485:支持更长的通信距离(最长可达1200米)和较高的通信速率(最高可达10Mbps)。
    • RS232:通常适用于较短距离(最长约15米)和较低速率的通信(最高约115.2kbps)。
  2. 信号传输方式
    • RS485:采用差分信号传输,使用两根线(A和B)进行数据传输。差分信号具有较强的抗干扰能力,适合在噪声环境中使用。
    • RS232:使用单端信号传输,通常有一条发送线(Tx),一条接收线(Rx),和一条地线(GND)。单端信号容易受到干扰,不适合长距离传输。
  3. 连接设备数量
    • RS485:支持多点通信,即在一条总线上可以连接多达32个(标准)或更多(扩展)的设备,适合多设备网络环境。
    • RS232:一般为点对点通信,只能连接两个设备(一个发送端,一个接收端)。
  4. 接口定义
    • RS485:通常使用双绞线或屏蔽双绞线,有时会使用DB-9或接线端子等物理接口。
    • RS232:常见物理接口为DB-9或DB-25连接器。
  5. 电气特性
    • RS485:电压范围通常为-7V到+12V,逻辑电平定义为差分电压(如A-B > 200mV表示逻辑1,A-B < -200mV表示逻辑0)。
    • RS232:电压范围为-15V到+15V,逻辑电平定义为单端电压(如-3V到-15V表示逻辑1,+3V到+15V表示逻辑0)。
      总结
    • RS485适用于长距离、多点、多设备的通信环境,具有较强的抗干扰能力。而RS232适用于短距离、点对点的通信场景,结构简单,成本较低。

选择哪种协议取决于具体的应用需求,如通信距离、速率、设备数量和环境噪声等因素。

三、Modbus协议是什么

Modbus协议是一种用于工业自动化和控制系统中的通信协议。它是一种主/从(Master/Slave)协议,允许多个设备在同一网络上进行通信。Modbus最早由Modicon(现属于施耐德电气)在1979年开发,用于可编程逻辑控制器(PLC)之间的通信。

Modbus协议的主要特点:

  • 开放性:Modbus是一个公开的通信协议,任何厂商都可以实现和使用,无需支付许可费用。

  • 简单性:Modbus协议结构简单,易于实现和使用。它采用简单的命令/响应格式,便于设备间的数据交换。

多种传输方式:

  • Modbus RTU(Remote Terminal Unit):基于RS485或RS232进行通信,数据以二进制形式传输,适用于需要高效传输的场合。
  • Modbus ASCII:同样基于RS485或RS232进行通信,但数据以ASCII码形式传输,便于人类阅读和调试。
  • Modbus TCP/IP:基于以太网进行通信,数据封装在TCP/IP报文中,适用于现代工业网络环境。

主从架构: 在Modbus网络中,主设备(如PLC、计算机)发起通信请求,从设备(如传感器、执行器)响应请求。一个主设备可以与多个从设备通信。

数据模型:

  • 线圈(Coil):单个位的读/写(如继电器状态)。
  • 离散输入(Discrete Input):单个位的只读(如开关状态)。
  • 保持寄存器(Holding Register):16位的读/写(如模拟量输出)。
  • 输入寄存器(Input Register):16位的只读(如模拟量输入)。

数据帧结构:

  • Modbus RTU:数据帧由地址字段、功能码、数据字段和校验码(CRC)组成。
  • Modbus ASCII:数据帧由起始字符、地址字段、功能码、数据字段、校验码(LRC)和结束字符组成。
  • Modbus TCP/IP:数据帧封装在TCP报文中,包含Modbus协议数据单元(PDU)。

Modbus广泛应用于工业自动化领域,如监控和数据采集系统(SCADA)、可编程逻辑控制器(PLC)、远程终端单元(RTU)、变频器、传感器和执行器等。总之,Modbus是一种简单、开放且广泛应用的工业通信协议,支持多种传输介质和数据格式,适合各种工业自动化应用场景。

四、如果让你设计一款 12路车载超声波雷达,采用 stm32F103+HC-SR04超声波模块,对外提供RS485和Modbus协议,你的设计方案是什么?

1. 硬件设计
1.1 主控芯片
STM32F103C8T6:

  • 内置72 MHz的ARM Cortex-M3内核。
  • 64 KB Flash,20 KB SRAM。
  • 丰富的外设接口,支持UART、GPIO等。

1.2 超声波模块
HC-SR04:

  • 测量范围:2cm至400cm。
  • 测距精度:约3mm。
  • 工作电压:5V。

1.3 通信接口
RS485:

  • MAX485:用于将STM32的UART信号转换为RS485信号。
    支持长距离通信,抗干扰能力强。

1.4 电源管理
DC-DC转换器:

  • 输入:12V/24V(车载电源)。
  • 输出:5V(供给HC-SR04和MAX485)。
  • 输出:3.3V(供给STM32)。

1.5 其他组件

  • 晶振:8MHz外部晶振。
  • 电容、电阻:用于电源滤波和信号调节。
  • 连接器:用于连接超声波模块和外部接口。

2. 电路设计
2.1 电源电路
使用DC-DC转换器(如LM2596)将12V/24V转换为5V和3.3V。
2.2 微控制器接口

  • STM32F103的GPIO引脚用于连接HC-SR04的Trigger和Echo引脚。
  • 一个UART接口通过MAX485转换器连接到RS485总线。

2.3 超声波模块接口

  • 每个HC-SR04的Trigger引脚连接到STM32的一个GPIO输出引脚。
  • 每个HC-SR04的Echo引脚连接到STM32的一个GPIO输入引脚。

2.4 RS485接口

  • 使用MAX485芯片,将STM32的UART信号转换为RS485信号。
  • 连接器用于外部RS485总线接口。

3. 软件设计
3.1 STM32固件开发

  • 开发环境:Keil MDK 或 STM32CubeIDE。
  • 库:使用STM32 HAL库或标准库。

3.2 超声波测距

  • 初始化12个HC-SR04模块。
  • 周期性触发每个HC-SR04模块,并测量回波时间。
  • 计算距离值,并保存到相应的寄存器。
void trigger_ultrasonic(uint8_t sensor_id) {
    HAL_GPIO_WritePin(trigger_pins[sensor_id], GPIO_PIN_SET);
    HAL_Delay(10); // 10微秒脉冲
    HAL_GPIO_WritePin(trigger_pins[sensor_id], GPIO_PIN_RESET);
}

void read_distance(uint8_t sensor_id) {
    uint32_t start_time = 0;
    uint32_t end_time = 0;

    while (HAL_GPIO_ReadPin(echo_pins[sensor_id]) == GPIO_PIN_RESET);
    start_time = __HAL_TIM_GET_COUNTER(&htim1);

    while (HAL_GPIO_ReadPin(echo_pins[sensor_id]) == GPIO_PIN_SET);
    end_time = __HAL_TIM_GET_COUNTER(&htim1);

    uint32_t time_elapsed = end_time - start_time;
    distances[sensor_id] = (time_elapsed * 0.034 / 2); // 转换为厘米
}

3.3 RS485通信

  • 配置STM32的UART接口为RS485模式。
  • 实现Modbus RTU协议栈,处理Modbus请求和响应。
void USART2_IRQHandler(void) {
    if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE)) {
        uint8_t data = (uint8_t)(huart2.Instance->DR & 0x00FF);
        modbus_receive(data);
    }
}

3.4 Modbus寄存器

  • 定义Modbus寄存器映射,包含12个超声波模块的测量值。
  • 实现读/写寄存器的Modbus功能码。
uint16_t modbus_registers[24]; // 12个距离值(每个2字节)

void modbus_process(uint8_t *request, uint8_t length) {
    uint8_t function_code = request[1];
    uint16_t start_address = (request[2] << 8) | request[3];
    uint16_t register_count = (request[4] << 8) | request[5];

    if (function_code == 0x03) { // 读保持寄存器
        modbus_read_holding_registers(start_address, register_count);
    }
    // 处理其他Modbus功能码
}

3.5 调试与测试

  • 使用示波器和逻辑分析仪检查超声波测距信号。
  • 使用Modbus调试工具验证RS485和Modbus通信。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值