基于stm32和HC_SR04超声波模块的测距和报警
目录
一.工作原理
(1)采用 IO 口 TRIG 触发测距,给最少 10us 的高电平信号;
(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;
(3)有信号返回, 通过 IO 口 ECHO 输出一个高电平, 高电平持续的时间就是超声
波从发射到返回的时间。 测试距离 = (高电平时间*声速(340M/S))/2。
实物图:
引脚连接:
时序:
二.功能实现
实验源码上传在在gitee上,
https://gitee.com/ashenone-129/stm32
HC_SR04初始化和读取距离
hc_sr04.c
c
#include "hcsr04.h"
#include "delay.h"
TIM_ICInitTypeDef tim_ic_init;
//g_cap_state:捕获状态标志(bit7:完成捕获; bit6:捕获高电平;bit5-bit0:捕获高电平溢出次数)
u16 g_cap_state, g_cap_val;
u32 g_cap_distance; //超声波测量距离
//---------------------------------------------------------------------------------------------------------------------------------------------
// 函 数 名: timer4_cap_init
// 功能说明: TIM4初始化
// 形 参: timer_arr:自动重装值; timer_psc:时钟分频系数
// 返 回 值: 无
// 日 期:
// 作 者:
// 备 注:
//---------------------------------------------------------------------------------------------------------------------------------------------
void timer4_cap_init(u16 timer_arr, u16 timer_psc)
{
GPIO_InitTypeDef gp_init;
TIM_TimeBaseInitTypeDef tim_base_init;
NVIC_InitTypeDef nvic_init_config;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟
gp_init.GPIO_Pin = GPIO_Pin_8;
gp_init.GPIO_Mode = GPIO_Mode_IPD; //PB8 输入 (ECHO)
GPIO_Init(GPIOB, &gp_init);
gp_init.GPIO_Pin = GPIO_Pin_9;
gp_init.GPIO_Mode = GPIO_Mode_Out_PP; //PB9 输出 (TRIG)
gp_init.GPIO_Speed = GPIO_Speed_2MHz; //2M
GPIO_Init(GPIOB, &gp_init);
//初始化定时器4 TIM4
tim_base_init.TIM_Period = timer_arr; //设定计数器自动重装值
tim_base_init.TIM_Prescaler = timer_psc; //预分频器
tim_base_init.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
tim_base_init.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &tim_base_init); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM4输入捕获参数
tim_ic_init.TIM_Channel = TIM_Channel_3; //CC1S=03 选择输入端 IC3映射到TI1上
tim_ic_init.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
tim_ic_init.TIM_ICSelection = TIM_ICSelection_DirectTI;
tim_ic_init.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
tim_ic_init.TIM_ICFilter = 0x00;//配置输入滤波器 不滤波
TIM_ICInit(TIM4, &tim_ic_init);
//中断分组初始化
nvic_init_config.NVIC_IRQChannel = TIM4_IRQn; //TIM4中断
nvic_init_config.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级2级
nvic_init_config.NVIC_IRQChannelSubPriority = 3; //从优先级0级
nvic_init_config.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&nvic_init_config); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig(TIM4, TIM_IT_Update|TIM_IT_CC3, ENABLE);//允许更新中断 ,允许CC3IE捕获中断
TIM_Cmd(TIM4, ENABLE); //使能定时器3
}
//---------------------------------------------------------------------------------------------------------------------------------------------
// 函 数 名: hcsr04_read_distance
// 功能说明: 超声波测量距离
// 形 参: 无
// 返 回 值: 无
// 日 期:
// 作 者:
// 备 注:
//---------------------------------------------------------------------------------------------------------------------------------------------
void hcsr04_read_distance(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_9); //启动超声波测量
delay_us(15);
GPIO_ResetBits(GPIOB, GPIO_Pin_9);
if (g_cap_state & 0X80) //捕获一次高电平
{
g_cap_distance = g_cap_state & 0X3f;
g_cap_distance *= 65535;
g_cap_distance += g_cap_val;
g_cap_distance = g_cap_distance * 170 / 1000; //计算距离(mm)
g_cap_state = 0X00;
}
}
void TIM4_IRQHandler(void)
{
u16 sta_val;
sta_val = TIM4->SR;
if ((g_cap_state & 0X80) == 0X00) //未捕获
{
if (sta_val & 0X01) //溢出
{
if (g_cap_state & 0X40) //捕获到高电平
{
if ((g_cap_state & 0X3f) == 0X3f) //高电平时间过长
{
g_cap_state |= 0X80; //记录一次捕获
g_cap_val = 0Xffff; //溢出时间
}
else
{
g_cap_state++; //继续捕获
}
}
}
if (sta_val & 0X08) //捕获3(通道3)发生捕获事件
{
if (g_cap_state & 0X40) //捕获下降沿
{
g_cap_state |= 0X80; //标记成功捕获一次高电平
g_cap_val = TIM4->CCR3; //获取当前捕获值
TIM4->CCER &= ~(1<<9); //CC1P=0 设置为上升沿捕获
}
else
{
g_cap_state = 0;
g_cap_val = 0;
g_cap_state |= 0X40; //标记捕获到上升沿
TIM4->CNT = 0; //清空计数器
TIM4->CCER |= (1<<9); //CC1P=1 设置为下降沿捕获
}
}
}
TIM4->SR = 0; //清除中断标志位
}
hc_sr04.h
c
#ifndef __HCSR04_H__
#define __HCSR04_H__
#include "sys.h"
extern u32 g_cap_distance;
extern void timer4_cap_init(u16 timer_arr, u16 timer_psc);
extern void hcsr04_read_distance(void);
#endif
使用呼吸灯表示距离远近
PWM.c
c
#include "stm32f10x.h"
#include "PWM.h"
void PWM_Init()
{
TIM_TimeBaseInitTypeDef TIM1_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM1_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//初始化定时器参数
TIM1_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟划分
TIM1_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up; //计数器模式向上计数
TIM1_TimeBaseInitStructure.TIM_Period = 100 -1; //自动重载
TIM1_TimeBaseInitStructure.TIM_Prescaler = 720 -1;
TIM1_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1,&TIM1_TimeBaseInitStructure);
//TIM1_OCStructInit(&TIM1_OCInitStructure);
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM1_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM1, &TIM1_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
//TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1预装载使能
//TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
TIM_Cmd(TIM1,ENABLE);
}
void PWM_SetCompare1(uint16_t compare)
{
TIM_SetCompare1(TIM1,compare);
}
PWM.h
c
#ifndef __PWM_H
#define __PWM_H
#include "stm32f10x.h"
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
void PWM_Init();
//void TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
#endif
主函数编写
c
#include "typedef.h"
#include "declares.h"
#include "variable.h"
#include "drvexflash.h"
#include "drvnrf2401.h"
#include "drvadc.h"
#include "drvuart.h"
#include "drvsdcard.h"
#include "hardware_spi.h"
#include "delay.h"
#include "drvled.h"
#include "app_adc.h"
#include "app_flash.h"
#include "app_htu21d.h"
#include "app_at24cxx.h"
#include "drvoled.h"
#include "drvtimer.h"
#include "drvexti.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "logic_grammer.h"
#include "c_language_regular.h"
#include "hcsr04.h"
#include "drvrtc.h"
#include "PWM.h"
void PWM_SetCompare1(uint8_t value);
int main_rtc(void)
{
u8 i = 0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
uart_init(1, 115200 * 8); //串口初始化为961200
Bsp_LedInit(); //初始化与LED连接的硬件接口
PWM_Init();
timer4_cap_init(0Xffff, 72 - 1);
RTC_Init_LSI(); //内部低速时钟
while (1)
{
hcsr04_read_distance();
delay_ms(500);
/*GPIO_SetBits(GPIOA, GPIO_Pin_8); //启动超声波测量
delay_ms(500);
GPIO_ResetBits(GPIOA, GPIO_Pin_8);*/
if(g_cap_distance<=100){
PWM_SetCompare1(0);
delay_ms(10);
}
else if(g_cap_distance>100&&g_cap_distance<=200){
PWM_SetCompare1(40);
delay_ms(10);
}
else if(g_cap_distance>200&&g_cap_distance<=300){
PWM_SetCompare1(70);
delay_ms(10);
}
else if(g_cap_distance>300){
PWM_SetCompare1(100);
delay_ms(10);
}
printf("g_cap_distance = %d \r\n", g_cap_distance); //用串口1打印输出
/*get_time();
i++;
delay_ms(10);
if (g_time_flag)
{
g_time_flag = 0;
printf("RTC Time:20%d-%d-%d %d:%d:%d\r\n",w_year_x, w_month_x, w_date_x, hour_x, min_x, sec_x);//输出时间
}*/
}
}
int main(void)
{
main_rtc();
//main_hcr();
}
其余的例如串口初始化或GPIO初始化可详见gitee.
三.效果
四.关于modbus和串口RS485
1)RS485与RS232(UART)有什么不同?
电气特性:
RS485:使用差分信号传输,具有更强的抗干扰能力,可以在较长的距离上传输数据,一般可达1200米。
RS232(UART):使用单端信号传输,抗干扰能力较弱,传输距离较短,通常在15米以内。
连接方式:
RS485:支持多点通信,即一个总线上可以连接多个设备。
RS232:通常是点对点通信,即一对一的连接方式。
数据速率:
RS485:数据传输速率(波特率)通常在300bps到10Mbps之间。
RS232:数据传输速率较低,一般在300bps到115.2kbps之间。
应用场景:
RS485:常用于工业环境中,因为其抗干扰能力强,适合长距离通信。
RS232:常用于计算机与打印机、调制解调器等设备之间的短距离通信。
电气接口:
RS485:使用9针D-sub连接器,通常使用A和B两个引脚传输数据。
RS232:使用9针或25针D-sub连接器,数据传输主要通过2、3引脚(发送和接收)。
协议复杂性:
RS485:协议相对简单,主要关注物理层和链路层。
RS232:协议较为复杂,包括物理层、数据链路层、网络层等多个层面。
功耗:
RS485:由于使用差分信号,功耗相对较低。
RS232:功耗相对较高,尤其是在长距离传输时。
成本:
RS485:由于其多点通信和长距离传输的特性,可能在某些应用中成本较高。
RS232:由于其简单性和普及性,成本通常较低。
2)Modbus协议是什么?
Modbus协议是一种串行通信协议,最初由Modicon(现在是施耐德电气的一部分)在1979年开发,用于其可编程逻辑控制器(PLC)。Modbus协议广泛应用于工业自动化领域,特别是在监控和控制设备之间进行通信时.
特点:
开放性:Modbus是一个开放的协议,这意味着它不受专利限制,任何制造商都可以免费使用它来开发兼容的设备。
应用层协议:Modbus主要定义了应用层的通信规则,它不指定物理层或数据链路层的具体实现。因此,Modbus可以运行在多种物理介质上,如RS232、RS485、以太网等。
功能码:Modbus使用功能码来定义通信中执行的操作,例如读取或写入寄存器、诊断等。每个功能码对应一个特定的操作。
数据格式:Modbus协议定义了一种简单的数据格式,用于在设备之间传输信息。数据通常以二进制形式传输,并且包含地址、功能码、数据和校验等部分。
多点通信:Modbus支持多点通信,即一个主设备可以与多个从设备通信。在RS485网络上,Modbus使用广播方式来实现这一点。
校验机制:Modbus提供了多种校验机制,包括无校验、奇偶校验和CRC校验,以确保数据传输的可靠性。
简单性:Modbus协议相对简单,易于实现,这使得它在工业自动化领域非常流行。
版本:Modbus有几个版本,包括Modbus ASCII、Modbus RTU(二进制模式)和Modbus TCP/IP。Modbus RTU是最常用的版本,它使用二进制数据格式来减少传输时间。
应用领域:Modbus广泛应用于各种工业控制和自动化系统中,包括制造、电力、石油和天然气、水处理等行业。
集成性:由于其开放性和简单性,Modbus协议可以很容易地与其他控制系统和协议集成,如BACnet、OPC等
3)如果让你设计一款 12路车载超声波雷达,采用 stm32F103+HC-SR04超声波模块,对外提供RS485和Modbus协议,你的设计方案是什么?
- 系统架构
主控制器:STM32F103负责协调整个系统,处理数据,以及与外部设备通信。
超声波传感器:HC-SR04模块用于检测距离,每个模块可以测量一个方向的距离。
RS485通信接口:用于与其他车载系统或中央控制单元通信。
Modbus协议:实现标准的Modbus通信,便于集成到现有的工业自动化系统中。 - 硬件设计
STM32F103:选择具有足够GPIO引脚和串行通信接口的型号。
HC-SR04模块:每个模块包含一个超声波发射器和一个接收器,需要12个模块来覆盖车辆周围。
电源管理:设计电源电路为STM32和超声波模块提供稳定的电源。
RS485通信电路:包括驱动器和接收器,以及终端电阻等。
接口电路:可能需要设计一些接口电路来适配Modbus协议。 - 软件设计
驱动程序:编写HC-SR04超声波模块的驱动程序,实现距离测量功能。
Modbus协议栈:实现Modbus RTU协议栈,处理Modbus命令和响应。
RS485通信:实现RS485通信协议,确保数据的正确发送和接收。
数据处理:编写算法处理超声波数据,如滤波、平均等,以提高测量精度。
用户接口:可能包括一些基础的用户接口,如LED指示灯或LCD显示屏,显示状态信息。 - 系统整合
布线:合理布线,确保超声波传感器的信号线和电源线不会相互干扰。
测试:进行单元测试、集成测试和系统测试,确保系统稳定可靠。
外壳设计:设计适合车载环境的外壳,保护内部组件。 - 功能实现
距离测量:每个HC-SR04模块测量一个方向的距离,STM32F103收集所有数据。
数据同步:设计同步机制,确保所有传感器数据的一致性。
Modbus功能码:定义Modbus功能码来读取距离数据、设置参数等。
错误处理:实现错误检测和处理机制,如传感器故障、通信错误等。
五.总结
这次作业实现了HC_SR04模块的超声波测距,加深了我对modbus,RS485的认识.