zoukankan      html  css  js  c++  java
  • stm32与HC-SR04超声波传感器测距

    首先,先来看一下这个模块的基本功能和原理。

    HC-SR04超声波测距模块可提供2cm-400cm的非接触式距离感测功能,测距精度可达高到3mm;模块包括超声波发射器、接收器与控制电路。像智能小车的测距以及转向,或是一些项目中,常常会用到。智能小车测距可以及时发现前方的障碍物,使智能小车可以及时转向,避开障碍物。

    注意是5v输入,但是我用stm32 的3.3v输入也是没有问题的。

    二.工作原理

          1.给超声波模块接入电源和地。
          2.给脉冲触发引脚(trig)输入一个长为20us的高电平方波

          3.输入方波后,模块会自动发射8个40KHz的声波,与此同时回波引脚(echo)端的电平会由0变为1;(此时应该启动定时器计时)
          4.当超声波返回被模块接收到时,回波引 脚端的电平会由1变为0;(此时应该停止定时器计数),定时器记下的这个时间即为超声波由发射到返回的总时长。
          5.根据声音在空气中的速度为344米/秒,即可计算出所测的距离。

          要学习和应用传感器,学会看懂传感器的时序图是很关键的,所以我们来看一下HC-SR04的时序触发图。

        

     

        我们来分析一下这个时序图,先由触发信号启动HC-RS04测距模块,也就是说,主机要先发送至少10us的高电平,触发HC-RS04,模块内部发出信号是传感器自动回应的,我们不用去管它。输出回响信号是我们需要关注的。信号输出的高电平就是超声波发出到重新返回接收所用的时间。用定时器,可以把这段时间记录下来,算出距离,别忘了结果要除于2,因为总时间是发送和接收的时间总和。

    下面是亲测可用的驱动程序。

    芯片型号为stm32f103zet6,超声波测距后通过串口打印到电脑上面。

    驱动和测距;

    //超声波测距
    
    #include "hcsr04.h"
     
    #define HCSR04_PORT     GPIOB
    #define HCSR04_CLK      RCC_APB2Periph_GPIOB
    #define HCSR04_TRIG     GPIO_Pin_5
    #define HCSR04_ECHO     GPIO_Pin_6
    
    #define TRIG_Send  PBout(5) 
    #define ECHO_Reci  PBin(6)
    
    u16 msHcCount = 0;//ms计数
    
    void Hcsr04Init()
    {  
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;     //生成用于定时器设置的结构体
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB2PeriphClockCmd(HCSR04_CLK, ENABLE);
         
            //IO初始化
        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);    
         
                //定时器初始化 使用基本定时器TIM6
            RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);   //使能对应RCC时钟
            //配置定时器基础结构体
            TIM_DeInit(TIM2);
            TIM_TimeBaseStructure.TIM_Period = (1000-1); //设置在下一个更新事件装入活动的自动重装载寄存器周期的值         计数到1000为1ms
            TIM_TimeBaseStructure.TIM_Prescaler =(72-1); //设置用来作为TIMx时钟频率除数的预分频值  1M的计数频率 1US计数
            TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;//不分频
            TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
            TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位         
            
            TIM_ClearFlag(TIM6, TIM_FLAG_Update);   //清除更新中断,免得一打开中断立即产生中断
            TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);    //打开定时器更新中断
            hcsr04_NVIC();
        TIM_Cmd(TIM6,DISABLE);     
    }
    
    
    //tips:static函数的作用域仅限于定义它的源文件内,所以不需要在头文件里声明
    static void OpenTimerForHc()        //打开定时器
    {
            TIM_SetCounter(TIM6,0);//清除计数
            msHcCount = 0;
            TIM_Cmd(TIM6, ENABLE);  //使能TIMx外设
    }
     
    static void CloseTimerForHc()        //关闭定时器
    {
            TIM_Cmd(TIM6, DISABLE);  //使能TIMx外设
    }
     
     
     //NVIC配置
    void hcsr04_NVIC()
    {
                NVIC_InitTypeDef NVIC_InitStructure;
                NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        
                NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn;             //选择串口1中断
                NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //抢占式中断优先级设置为1
                NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;         //响应式中断优先级设置为1
                NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //使能中断
                NVIC_Init(&NVIC_InitStructure);
    }
    
    
    //定时器6中断服务程序
    void TIM6_IRQHandler(void)   //TIM3中断
    {
            if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
            {
                    TIM_ClearITPendingBit(TIM6, TIM_IT_Update  );  //清除TIMx更新中断标志 
                    msHcCount++;
            }
    }
     
    
    //获取定时器时间
    u32 GetEchoTimer(void)
    {
            u32 t = 0;
            t = msHcCount*1000;//得到MS
            t += TIM_GetCounter(TIM6);//得到US
              TIM6->CNT = 0;  //将TIM2计数寄存器的计数值清零
                    Delay_Ms(50);
            return t;
    }
     
    
    //一次获取超声波测距数据 两次测距之间需要相隔一段时间,隔断回响信号
    //为了消除余震的影响,取五次数据的平均值进行加权滤波。
    float Hcsr04GetLength(void )
    {
            u32 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();        //获取时间,分辨率为1US
                lengthTemp = ((float)t/58.0);//cm
                sum = lengthTemp + sum ;
            
        }
            lengthTemp = sum/5.0;
            return lengthTemp;
    }
    
    
    /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    ** 函数名称: Delay_Ms_Ms
    ** 功能描述: 延时1MS (可通过仿真来判断他的准确度)            
    ** 参数描述:time (ms) 注意time<65535
    :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
    void Delay_Ms(uint16_t time)  //延时函数
    { 
        uint16_t i,j;
        for(i=0;i<time;i++)
              for(j=0;j<10260;j++);
    }
    /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    ** 函数名称: Delay_Ms_Us
    ** 功能描述: 延时1us (可通过仿真来判断他的准确度)
    ** 参数描述:time (us) 注意time<65535                 
    :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
    void Delay_Us(uint16_t time)  //延时函数
    { 
        uint16_t i,j;
        for(i=0;i<time;i++)
              for(j=0;j<9;j++);
    }

    但是关于USART的函数我就不往上写了,这个简单的串口打印大家应该都会写。下面简单贴一下我的主函数吧。

    /*
    教训:实验前一定要检查引脚连接是否正确,万不可搞错,不然又要烧坏芯片!!!!
    
    */
    
    #include "hcsr04.h"
    #include "chao_usart.h"
    
    int main()
    {
        
            float length;
            
            GPIO_cfg();
          NVIC_cfg();
            USART_cfg();    
            printf("串口初始化成功!
    ");
        
            Hcsr04Init();    
            printf("超声波初始化成功!
    ");//测试程序是否卡在下面两句上面
    
            length = Hcsr04GetLength();
            printf("距离为:%.3f
    ",length);
        
        
    }

    实验结果:

    好了,其实这个模块很简单,但是要是把他用的很好的话还是比较困难的,比如用超声波做一个四轴定高的程序,还是有一定的挑战性的。

    写这篇博客的目的不仅仅是介绍这个模块的使用,其实这种使用介绍网上一搜一大把,我只是想纪录一下,我在做这个模块的时候遇到的一些其他的问题。

    其中有一个小插曲,就是当吧写好的程序烧进去之后,运行时总是出现每次返回一个同样的比正常值小的多的数据,比如说0.034cm,这明显是一个错误的数据,但是刚开始的时候,不知道为什么

    总是这样,多次复位从新上电总是这一个数据。让我很是苦恼。但是幸运的是,在这样的情况中间,他又会有时出现一两个正常的的数据,让你有点摸不着头脑。

    上网查了一下才慢慢明白,这种现象叫做“余震”,网上关于余震的解释大致有三种:

      1、探头的余震。即使是分体式的,发射头工作完后还会继续震一会,这是物理效应,也就是余震。这个余震信号也会向外传播。如果你的设计是发射完毕后立刻切换为接收状态(无盲区),那么这个余震波会通过壳体和周围的空气,直接到达接收头、干扰了检测(注:通常的测距设计里,发射头和接收头的距离很近,在这么短的距离里超声波的检测角度是很大的,可达180度)。
      2、壳体的余震。就像敲钟一样,能量仍来自发射头。发射结束后,壳体的余震会直接传导到接收头,当然这个时间很短,但已形成了干扰。另外,在不同的环境温度下,壳体的硬度和外形会有所变化,其余震有时长、有时短、有时干扰大、有时干扰小,这是设计工业级产品时必须要考虑的问题。
      3、电路串扰。超声波发射时的瞬间电流很大,例如某种工业级连续测距产品瞬间电流会有15A,通常的产品也能达到1A,瞬间这么大的电流会对电源有一定影响,并干扰接收电路。通过改善电源设计可以缓解这种情况,但在低成本设计中很难根除。所以每次发射完毕,接收电路还需要一段时间稳定工作状态。在此期间,其输出的信号很难使用。

    消除上述现象的方法之一就是在检测的时候多次循环检测,取平均值,也就是加权平均滤波,一个简单的滤波处理。就是下面这一段:

            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();        //获取时间,分辨率为1US
                lengthTemp = ((float)t/58.0);//cm
                sum = lengthTemp + sum ;
            
        }
            lengthTemp = sum/5.0;
            return lengthTemp;

    加了这个之后,基本上就没有出现余震现象了。

    还有一点就是测试程序前一定要检查引脚有没有接错,不管多有把握,也要看一遍,不然很容易出大事的,一个芯片也许就因为你的大意给GG了。切记,这个应该也算我们这个行业的基本素养吧。

  • 相关阅读:
    android 中文 api (43) —— Chronometer
    SVN客户端清除密码
    Android 中文 API (35) —— ImageSwitcher
    Android 中文API (46) —— SimpleAdapter
    Android 中文 API (28) —— CheckedTextView
    Android 中文 API (36) —— Toast
    Android 中文 API (29) —— CompoundButton
    android 中文 API (41) —— RatingBar.OnRatingBarChangeListener
    Android 中文 API (30) —— CompoundButton.OnCheckedChangeListener
    Android 中文 API (24) —— MultiAutoCompleteTextView.CommaTokenizer
  • 原文地址:https://www.cnblogs.com/qsyll0916/p/6964638.html
Copyright © 2011-2022 走看看