超声模块HC_SR04基本原理与FPGA、STM32应用
我的学记|刘航宇的博客

超声模块HC_SR04基本原理与FPGA、STM32应用

刘航宇
3年前发布 /正在检测是否收录...
电子信息类专业“一站式”考研-简历-就业宝藏分享 电子信息类专业“一站式”考研-简历-就业宝藏分享

HC-SR04

硬件概述

HC-SR04超声波距离传感器的核心是两个超声波传感器。一个用作发射器,将电信号转换为40 KHz超声波脉冲。接收器监听发射的脉冲。如果接收到它们,它将产生一个输出脉冲,其宽度可用于确定脉冲传播的距离。就是如此简单!

该传感器体积小,易于在任何机器人项目中使用,并提供2厘米至600厘米(约1英寸至13英尺)之间出色的非接触范围检测,精度为3mm。
图片[1] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客

接口定义:

图片[2] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客

模式选择:

图片[3] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客

测量操作:

一:GPIO模式
图片[4] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客
外部MCU给模块Trig脚一个大于10uS的高电平脉冲;模块会给出一个与距离等比的高电平脉冲信号,可根据脉宽时间“T”
算出:
距离=T*C/2 (C为声速)

声速温度公式:c=(331.45+0.61t/℃)m•s-1 (其中330.45是在0℃)

 0℃声速:   330.45M/S
 20℃声速:  342.62M/S
 40℃声速:  354.85M/S

0℃-40℃声速误差7%左右。实际应用,如果需要精确距离值,必需要考虑温度影响,做温度补偿。
二:UART模式

UART 模式波特率设置: 9600 N 1
图片[5] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客
连接串口。外部MCU或PC发命令0XA0,模块完成测距后发3个返回距离
数据,BYTE_H,BYTE_M与BYTE_L。
距离计算方式如下(单位mm):

距离=((BYTE_H<<16)+(BYTE_M<<8)+ BYTE_L)/1000
三:IIC模式

IIC地址: 0X57
IIC传输格式:
写数据:
图片[6] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客
读数据:
图片[7] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客
命令格式:
图片[8] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客
向模块写入0X01,模块开始测距;等待200mS(模块最大测距时间)
以上。直接读出3个距离数据。BYTE_H,BYTE_M与BYTE_L。
距离计算方式如下(单位mm):

距离=((BYTE_H<<16)+(BYTE_M<<8)+ BYTE_L)/1000

FPGA实现超声测距

本次测距教程一律按基本原理实现,至于UART、ICC测距原理可以网上查询
FPGA 产生周期性的 TRIG 脉冲信号,使得超声波模块周期性发出测距脉冲,当这些脉冲发出后遇到障碍物返回,超声波模块将返回的脉冲处理整形后返回给 FPGA,即 ECHO 信号。我们通过对 ECHO 信号的高脉冲保持时间就可以推算出超声波脉冲和障碍物之间的距离。

本实例的功能如图三所示,FPGA 产生 10us 脉冲 TRIG 给超声波测距模块,然后以 10us 为单位计算超声波测距模块返回的回响信号 ECHO 的高电平保持时间。ECHO 的高电平保持时间通过一定的换算后可以得到障碍物和超声波测距模块之间的距离(由距离公式计算&进制换算模块实现),我们将最终获得的以 mm 为单位的距离信息显示在 4 位数码管上。
图片[9] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客

模块代码

1、vlg_en模块

 /*
 * @Author: Hangyu Liu 
 * @Date: 2023-11-20 15:24:01 
 * @Email: hyliu@ee.ac.cn 
 * @Descripttion: 板子时钟转化1us
 * @Last Modified time: 2023-11-20 15:24:01 
 */
//1us/50ns=20
module vlg_1us#(parameter P_CLK_PERIORD = 50) //i_clk的时钟周期50ns,20MHZ
(
    input   i_clk,
    input   i_rst_n,
    output  reg o_clk //时钟周期1us
);
    parameter NUM_DIV = 20;//  (1MHZ = 1us,20MHZ/20 = 1MHZ)
    reg [3:0] cnt;

always @(posedge i_clk or negedge i_rst_n) begin
    if(!i_rst_n) begin
        cnt <= 4'd0;
        o_clk <= 1'b0;
    end
    else if(cnt == NUM_DIV/2 - 1) begin
        cnt <= 4'b0;
        o_clk <= ~o_clk;
    end
    else
        cnt <= cnt + 1'b1;
end   

endmodule

2、vlg_trig模块

 /*
 * @Author: Hangyu Liu 
 * @Date: 2023-11-20 16:50:44 
 * @Email: hyliu@ee.ac.cn 
 * @Descripttion: 产生10us的触发超声信号
 * @Last Modified time: 2023-11-20 16:50:44 
 */

module vlg_trig (
    input i_rst_n,
    input clk_1us, //1us
    output reg o_trig
);
reg[17:0] r_tricnt;
//200ms的周期计数  1us一个单位
always @(posedge clk_1us or negedge i_rst_n)begin
  if(!i_rst_n) r_tricnt <= 18'd0;
  else if((r_tricnt == 18'd199999))
        r_tricnt <= 18'd0;
  else r_tricnt <= r_tricnt + 1'b1;
end
//产生保持10us的高脉冲o_tring信号
always @(posedge clk_1us or negedge i_rst_n) begin
    if(!i_rst_n) o_trig<=1'b0;
    else if((r_tricnt > 18'd0) && (r_tricnt <= 18'd10))
        o_trig <= 1'b1; //不从0开始0~9,防止出现不到10us的波干扰
    else o_trig <= 1'b0;
end
    
endmodule

3、vlg_echo模块

module vlg_echo (
    input i_clk,  //1us
    input i_rst_n,
    input i_clk_1us,
    input i_echo,
    output reg[15:0] o_t_us
);
reg[1:0] r_echo;
wire pos_echo,neg_echo;
reg r_cnt_en;
reg[15:0] r_echo_cnt;
//对i_echo信号同步处理,获取边沿检测信号,产生计数使能信号r_cnt_en
always @(posedge i_clk or negedge i_rst_n) begin
    if(!i_rst_n) r_echo <= 2'd0;
    else r_echo <= {r_echo[0],i_echo}; //设置两个寄存器进行打拍寄存
end

assign pos_echo = r_echo[0] & ~r_echo[1]; //现状态是1上状态是0,就是上升沿
assign neg_echo = ~r_echo[0] & r_echo[1];

always @(posedge i_clk or negedge i_rst_n) begin
    if(!i_rst_n) r_cnt_en <= 1'b0;
    else if(pos_echo) r_cnt_en <= 1'b1;
    else if(neg_echo) r_cnt_en <= 1'b0;
    else r_cnt_en <= r_cnt_en;
end

//对i_echo信号的高脉冲计时,以us为单位
always @(posedge i_clk_1us or negedge i_rst_n) begin
    if(!i_rst_n) r_echo_cnt <= 1'b0;
    else if(r_cnt_en) r_echo_cnt <= r_echo_cnt + 1'b1;
    else r_echo_cnt <= 1'b0;
end
//在下降沿对计数最大值进行保存
always @(negedge i_clk or negedge i_rst_n) begin
    if(!i_rst_n) o_t_us <= 16'd0;
    else if(neg_echo) o_t_us <= r_echo_cnt;
    else o_t_us <= o_t_us;
end

endmodule

4、顶层模块例化

 /*
 * @Author: Hangyu Liu 
 * @Date: 2023-11-23 17:16:40 
 * @Email: hyliu@ee.ac.cn 
 * @Descripttion:HR04驱动模块 
 * @Last Modified time: 2023-11-23 17:16:40 
 */

module vlg_design (
    input i_clk, //200MHZ
    input i_rst_n,
    input i_echo, //这是超声模块给的输入
    output o_trig,
    output wire[15:0] w_t_us
);

wire clk_20MHZ;
clk_div_20MHZ UU(
    .i_clk(i_clk),
    .i_rst_n(i_rst_n),
    .clk_div(clk_20MHZ)
 );

localparam P_CLK_PERIORD = 50;
wire clk_1us;
//使能时钟产生模块
vlg_1us #(
    .P_CLK_PERIORD(P_CLK_PERIORD) //i_clk的时钟周期50ns,20MHZ
)U1(
    .i_clk(clk_20MHZ),
    .i_rst_n(i_rst_n),
    .o_clk(clk_1us)
);
//产生超声波测距模块的触发信号o_trig
vlg_trig U2(
    .i_rst_n(i_rst_n),
    .clk_1us(clk_1us),
    .o_trig(o_trig)
);
//超声波测距模块的回响信号i_echo的高电平时间采集
vlg_echo U3(
    .i_clk(clk_20MHZ),
    .i_rst_n(i_rst_n),
    .i_clk_1us(clk_1us),
    .i_echo(i_echo),
    .o_t_us(w_t_us)
);

endmodule

STM32(Cubemax)实现超声波测距

CubeMX配置STM32

1 时钟配置
这里我用的是STM32F103C8T6的核心板,时钟配置如下图,我用了8MHz的HSE,HCLK调到了最大值72MHz
图片[10] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客
2 设置输入捕获的定时器
设置定时器TIM2每1us向上计数一次,通道4为上升沿捕获并连接到超声波模块的ECHO引脚,记得开启定时器中断(涉及到捕获中断+定时器溢出中断)。
图片[11] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客
3 触发引脚
PB10接到了HC-SR04的TIRG触发引脚,默认输出低电平
图片[12] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客
4 串口配置
还要开启一个串口,以便通过串口查看测距结果
图片[13] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客

编写代码

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 >= 450)
  {
    Hcsr04Info.distance = 450;
  }
  return Hcsr04Info.distance;
}

main.c
1、引用对应的头文件

/* USER CODE BEGIN Includes */
#include "hc-sr04.h"
#include "printf.h"
/* USER CODE END Includes */

2、200ms测距一次

/**
  * @brief  The application entry point.
  * @retval int
  */
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_TIM2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  DelayInit(72);
  Hcsr04Init(&htim2, TIM_CHANNEL_4);  //  超声波模块初始化
  Hcsr04Start();  //  开启超声波模块测距
  printf("hc-sr04 start!\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    // 打印测距结果
    printf("distance:%.1f cm\r\n", Hcsr04Read());
    Hcsr04Start();
    DelayMs(200); //  测距周期200ms
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

3、重定义定时器的中断服务函数

/* 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 */

4、串口打印结果
图片[14] - 超声模块HC_SR04基本原理与FPGA、STM32应用 - 我的学记|刘航宇的博客

© 版权声明
THE END
喜欢就支持一下吧
点赞 2 分享 赞赏
评论 抢沙发
取消