STM32进阶教程:使用HC-SR04超声波模块进行精准测距

前言

本实验旨在学习和理解HC-SR04超声波测距模块的工作原理,并利用stm32F103单片机完成一个超声波测距方案。HC-SR04超声波测距模块是一种常用的距离测量传感器,通过发送超声波脉冲并接收其回波来测量物体与传感器之间的距离。

超声波测距原理

利用HC-SR04超声波测距模块可以实现比较精确的直线测距,其测距原理图如下:

HC-SR04的一端发出超声波,接触到反射物后反射,被另一个端口接收到,所以只要知道发射和接收的时间差,就可以根据声波传播的速率算出HC-SR04和反射物直接的距离。
所以实现超声波测距就需要俩个条件:

  • 发射和接收的时间差
  • 超声波传输的速率
  • HC-SR04工作原理

    HC-SR04模块的电气参数如示:

  • HC-SR04模块的实物图如示:

  • HC-SR04模块的实物图如示:

  • 有四个引脚:

    Vcc:+5V电源供电
    Trig:输入触发信号(可以触发测距)
    Echo:传出信号回响(可以传回时间差)
    Gnd:接地
    用Trig和Echo引脚实现测距的流程:
    1.通过Trig输出一段至少10us的高电平(脉冲),触发一次测距,超声波在传输的过程中Echo一直输出高电平。
    2.在Trig脉冲输出后,立即检测Echo引脚的电平,测出Echo高电平持续的时间t,t就是超声波在所测距离一个来回所需时间。
    测距时序图如示:

  • OLED屏幕
    OLED(Organic Light Emitting Diode,有机发光二极管)是一种能够自发光的显示技术,广泛应用于单片机开发中的显示模块。它具有高对比度、快速响应、低功耗等优点,适用于小尺寸显示和低功耗应用。
    同时,可以将程序中的某些重要参数直接输出到OLED屏幕当中,这样就无需使用串口助手,方便了程序的调试

  • 工作原理

    OLED显示屏由许多微小的有机发光二极管组成,每个像素点都可以自发光。当施加电压时,有机材料中的电子和空穴结合,产生光,从而形成图像。OLED不需要背光源,因此可以实现更薄、更轻、更柔性的显示器。

  • 实验示例

    使用标准库

    HC-SR04模块代码

    HC.c#include "HC.h"
     

    #include "Delay.h"
    #include "stm32f10x.h" 
    #include "sys.h"
     
    #define HCSR04_PORT     GPIOB
    #define HCSR04_CLK      RCC_APB2Periph_GPIOB
    #define HCSR04_TRIG     GPIO_Pin_11
    #define HCSR04_ECHO     GPIO_Pin_10
     
    u16 msHcCount = 0; 
     
    void HC_Init(void)
    {  
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;   
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB2PeriphClockCmd(HCSR04_CLK, ENABLE);
     
        GPIO_InitStructure.GPIO_Pin =HCSR04_TRIG;      
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);
        GPIO_ResetBits(HCSR04_PORT,HCSR04_TRIG);
     
        GPIO_InitStructure.GPIO_Pin = HCSR04_ECHO;     
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);  
        GPIO_ResetBits(HCSR04_PORT,HCSR04_ECHO);    
     
     
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);   
     
        TIM_DeInit(TIM2);
        TIM_TimeBaseStructure.TIM_Period = (1000-1); 
        TIM_TimeBaseStructure.TIM_Prescaler =(72-1); 
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
        TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);          
     
        TIM_ClearFlag(TIM4, TIM_FLAG_Update);  
        TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);    
     
        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
     
        NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;             
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;         
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;       
        NVIC_Init(&NVIC_InitStructure);
     
        TIM_Cmd(TIM4,DISABLE);     
    }
     

     
     
    static void OpenTimerForHc()  
    {
        TIM_SetCounter(TIM4,0);
        msHcCount = 0;
        TIM_Cmd(TIM4, ENABLE); 
    }
     
     
    static void CloseTimerForHc()    
    {
        TIM_Cmd(TIM4, DISABLE); 
    }
     
     
    void TIM4_IRQHandler(void)  
    {
        if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)  
       {
           TIM_ClearITPendingBit(TIM4, TIM_IT_Update  ); 
           msHcCount++;
       }
    }
     
     
     
      u32 GetEchoTimer(void)
    {
        u32 t = 0;
        t = msHcCount*1000;
        t += TIM_GetCounter(TIM4);
        TIM4->CNT = 0;  
        Delay_ms(50);
        return t;
    }
     
      float HC_Get(void )
    {
        int t = 0;
        int i = 0;
        float lengthTemp = 0;
        float sum = 0;
        while(i!=5)
       {
           TRIG_Send = 1;      
           Delay_us(20);
           TRIG_Send = 0;
           while(ECHO_Reci == 0);      
           OpenTimerForHc();        
           i = i + 1;
           while(ECHO_Reci == 1);
           CloseTimerForHc();        
           t = GetEchoTimer();        
           lengthTemp = ((float)t/58.0);//cm
           sum = lengthTemp + sum ;
     
       }
        lengthTemp = sum/5.0;
        return lengthTemp;

    }

    HC.h

    #ifndef __HC_H
    #define __HC_H    
    #include "sys.h"
     
    void HC_Init(void);
    static void OpenTimerForHc(void);
    static void CloseTimerForHc(void);
    void TIM4_IRQHandler(void);
    u32 GetEchoTimer(void);
    float HC_Get(void );
     
    #define TRIG_Send  PBout(11)
    #define ECHO_Reci  PBin(10)
    #endif

     

    OLED模块代码

    OLED.c

    #include "stm32f10x.h"
    #include "OLED_Font.h"

    /*引脚配置*/
    #defin OLED_W_SCL(x)        GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
    #define OLED_W_SDA(x)        GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

    /*引脚初始化*/
    void OLED_I2C_Init(void)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
        
        GPIO_InitTypeDef GPIO_InitStructure;
         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
         GPIO_Init(GPIOB, &GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
         GPIO_Init(GPIOB, &GPIO_InitStructure);
        
        OLED_W_SCL(1);
        OLED_W_SDA(1);
    }

    /**
      * @brief  I2C开始
      * @param  无
      * @retval 无
      */
    void OLED_I2C_Start(void)
    {
        OLED_W_SDA(1);
        OLED_W_SCL(1);
        OLED_W_SDA(0);
        OLED_W_SCL(0);
    }

    /**
      * @brief  I2C停止
      * @param  无
      * @retval 无
      */
    void OLED_I2C_Stop(void)
    {
        OLED_W_SDA(0);
        OLED_W_SCL(1);
        OLED_W_SDA(1);
    }

    /**
      * @brief  I2C发送一个字节
      * @param  Byte 要发送的一个字节
      * @retval 无
      */
    void OLED_I2C_SendByte(uint8_t Byte)
    {
        uint8_t i;
        for (i = 0; i < 8; i++)
        {
            OLED_W_SDA(Byte & (0x80 >> i));
            OLED_W_SCL(1);
            OLED_W_SCL(0);
        }
        OLED_W_SCL(1);    //额外的一个时钟,不处理应答信号
        OLED_W_SCL(0);
    }

    /**
      * @brief  OLED写命令
      * @param  Command 要写入的命令
      * @retval 无
      */
    void OLED_WriteCommand(uint8_t Command)
    {
        OLED_I2C_Start();
        OLED_I2C_SendByte(0x78);        //从机地址
        OLED_I2C_SendByte(0x00);        //写命令
        OLED_I2C_SendByte(Command); 
        OLED_I2C_Stop();
    }

    /**
      * @brief  OLED写数据
      * @param  Data 要写入的数据
      * @retval 无
      */
    void OLED_WriteData(uint8_t Data)
    {
        OLED_I2C_Start();
        OLED_I2C_SendByte(0x78);        //从机地址
        OLED_I2C_SendByte(0x40);        //写数据
        OLED_I2C_SendByte(Data);
        OLED_I2C_Stop();
    }

    /**
      * @brief  OLED设置光标位置
      * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
      * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
      * @retval 无
      */
    void OLED_SetCursor(uint8_t Y, uint8_t X)
    {
        OLED_WriteCommand(0xB0 | Y);                    //设置Y位置
        OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));    //设置X位置高4位
        OLED_WriteCommand(0x00 | (X & 0x0F));            //设置X位置低4位
    }

    /**
      * @brief  OLED清屏
      * @param  无
      * @retval 无
      */
    void OLED_Clear(void)
    {  
        uint8_t i, j;
        for (j = 0; j < 8; j++)
        {
            OLED_SetCursor(j, 0);
            for(i = 0; i < 128; i++)
            {
                OLED_WriteData(0x00);
            }
        }
    }

    /**
      * @brief  OLED显示一个字符
      * @param  Line 行位置,范围:1~4
      * @param  Column 列位置,范围:1~16
      * @param  Char 要显示的一个字符,范围:ASCII可见字符
      * @retval 无
      */
    void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
    {          
        uint8_t i;
        OLED_SetCursor((Line – 1) * 2, (Column – 1) * 8);        //设置光标位置在上半部分
        for (i = 0; i < 8; i++)
        {
            OLED_WriteData(OLED_F8x16[Char – ' '][i]);            //显示上半部分内容
        }
        OLED_SetCursor((Line – 1) * 2 + 1, (Column – 1) * 8);    //设置光标位置在下半部分
        for (i = 0; i < 8; i++)
        {
            OLED_WriteData(OLED_F8x16[Char – ' '][i + 8]);        //显示下半部分内容
        }
    }

    /**
      * @brief  OLED显示字符串
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  String 要显示的字符串,范围:ASCII可见字符
      * @retval 无
      */
    void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
    {
        uint8_t i;
        for (i = 0; String[i] != '\0'; i++)
        {
            OLED_ShowChar(Line, Column + i, String[i]);
        }
    }

    /**
      * @brief  OLED次方函数
      * @retval 返回值等于X的Y次方
      */
    uint32_t OLED_Pow(uint32_t X, uint32_t Y)
    {
        uint32_t Result = 1;
        while (Y–)
        {
            Result *= X;
        }
        return Result;
    }

    /**
      * @brief  OLED显示数字(十进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~4294967295
      * @param  Length 要显示数字的长度,范围:1~10
      * @retval 无
      */
    void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
        uint8_t i;
        for (i = 0; i < Length; i++)                            
        {
            OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length – i – 1) % 10 + '0');
        }
    }

    /**
      * @brief  OLED显示数字(十进制,带符号数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:-2147483648~2147483647
      * @param  Length 要显示数字的长度,范围:1~10
      * @retval 无
      */
    void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
    {
        uint8_t i;
        uint32_t Number1;
        if (Number >= 0)
        {
            OLED_ShowChar(Line, Column, '+');
            Number1 = Number;
        }
        else
        {
            OLED_ShowChar(Line, Column, '-');
            Number1 = -Number;
        }
        for (i = 0; i < Length; i++)                            
        {
            OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length – i – 1) % 10 + '0');
        }
    }

    /**
      * @brief  OLED显示数字(十六进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
      * @param  Length 要显示数字的长度,范围:1~8
      * @retval 无
      */
    void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
        uint8_t i, SingleNumber;
        for (i = 0; i < Length; i++)                            
        {
            SingleNumber = Number / OLED_Pow(16, Length – i – 1) % 16;
            if (SingleNumber < 10)
            {
                OLED_ShowChar(Line, Column + i, SingleNumber + '0');
            }
            else
            {
                OLED_ShowChar(Line, Column + i, SingleNumber – 10 + 'A');
            }
        }
    }

    /**
      * @brief  OLED显示数字(二进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
      * @param  Length 要显示数字的长度,范围:1~16
      * @retval 无
      */
    void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
        uint8_t i;
        for (i = 0; i < Length; i++)                            
        {
            OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length – i – 1) % 2 + '0');
        }
    }

    /**
      * @brief  OLED初始化
      * @param  无
      * @retval 无
      */
    void OLED_Init(void)
    {
        uint32_t i, j;
        
        for (i = 0; i < 1000; i++)            //上电延时
        {
            for (j = 0; j < 1000; j++);
        }
        
        OLED_I2C_Init();            //端口初始化
        
        OLED_WriteCommand(0xAE);    //关闭显示
        
        OLED_WriteCommand(0xD5);    //设置显示时钟分频比/振荡器频率
        OLED_WriteCommand(0x80);
        
        OLED_WriteCommand(0xA8);    //设置多路复用率
        OLED_WriteCommand(0x3F);
        
        OLED_WriteCommand(0xD3);    //设置显示偏移
        OLED_WriteCommand(0x00);
        
        OLED_WriteCommand(0x40);    //设置显示开始行
        
        OLED_WriteCommand(0xA1);    //设置左右方向,0xA1正常 0xA0左右反置
        
        OLED_WriteCommand(0xC8);    //设置上下方向,0xC8正常 0xC0上下反置

        OLED_WriteCommand(0xDA);    //设置COM引脚硬件配置
        OLED_WriteCommand(0x12);
        
        OLED_WriteCommand(0x81);    //设置对比度控制
        OLED_WriteCommand(0xCF);

        OLED_WriteCommand(0xD9);    //设置预充电周期
        OLED_WriteCommand(0xF1);

        OLED_WriteCommand(0xDB);    //设置VCOMH取消选择级别
        OLED_WriteCommand(0x30);

        OLED_WriteCommand(0xA4);    //设置整个显示打开/关闭

        OLED_WriteCommand(0xA6);    //设置正常/倒转显示

        OLED_WriteCommand(0x8D);    //设置充电泵
        OLED_WriteCommand(0x14);

        OLED_WriteCommand(0xAF);    //开启显示
            
        OLED_Clear();                //OLED清屏
    }

     

    OLED.h

    #ifndef __OLED_H
    #define __OLED_H

    void OLED_Init(void);
    void OLED_Clear(void);
    void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
    void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
    void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
    void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
    void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
    void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);

    #endif

    此外,还需要OLED_Fnot.h的文件,用于定义显示字符:

    主函数

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "HC.h"
     
    uint32_t Length;
    float length;
     
     
    int main(void)
    {
        
      OLED_Init();
      OLED_ShowString(1,1,"Length:");
      OLED_ShowString(2,10,"cm");
      HC_Init();
     
    while(1)
        {
            length =HC_Get(); 
            Length = length*100; //eg:54.25->5425
            OLED_ShowNum(2,4,Length/1000,1);  // ?
            OLED_ShowNum(2,5,Length%1000/100,1); //?
            OLED_ShowString(2,6,".");
            OLED_ShowNum(2,7,Length%100/10,1); //?
            OLED_ShowNum(2,8,Length%10/1,1);  //?
            
      }
    }
    实验效果

    总结

    1. 使用STM32控制HC-SR04超声波传感器进行测距实验,可以有效测量目标物体与传感器之间的距离。
    2. 在实验过程中,需要注意使用适当的引脚进行连接,确保传输信号的稳定性和准确性。
    3. 在编写程序时,需要使用定时器来控制超声波模块的工作时间,以及捕获定时器的计数值以计算出距离。
    4. 需要了解定时器的工作原理和使用方法,确保能够正确地设置定时器的时钟周期和计数模式。
    5. 在数据处理过程中,需要使用速度和距离的关系公式来计算实际的距离值。
    6. 还需要注意超声波传感器的特性和测距范围,避免超出传感器的测量能力。
    7. 在实验过程中,可以通过多次测量取平均值的方式来提高测距的准确性。
    8. 需要注意信号和噪声的干扰,可以使用滤波器来处理不符合要求的信号。
    9. 需要注意超声波传感器的工作环境,避免受到其他物体的干扰,导致测量结果不准确。
    10. 可以根据实际应用需求,对测距实验进行进一步的优化和改进,例如增加数据显示和记录功能,实现自动控制等。

    作者:nrt123

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32进阶教程:使用HC-SR04超声波模块进行精准测距

    发表评论