超声波模块

工作原理

1、采用IO触发测距,需要至少10us的高电平信号。所以需要单片机给模块输入10us高电平信号

2、模块自动发送8个40KHz方波,自动检测是否有信号返回

3、若有信号返回,通过IO输出高电平信号,高电平持续时间就是超声波从发射到返回的时间。单片机接收模块输出的高电平信号。

4、HC-SR04提供2-400cm测距功能,误差在3mm

硬件连接

VCC接5V供电,GND接地,TRIG为外部触发信号输入(RX),输入一个10us的高电频信号即可触发测距功能,ECHO为回响信号输出(TX),测距结束会输出一个低电平,电平宽度反映超声波往返时间之和

STM32控制超声波测距程序步骤

1、初始化超声波模块引脚和时钟(HC-SR04的GPIO和APBx)

2、初始化引脚模式和定时器中断(TIMx和NVIC)

3、编写定时器中断函数(IRQNHandler)

流程框架

1、单片机通过PB11输出10us高电平到SR04 Trig引脚

2、单片机检测SR04 Echo引脚低电平信号,定时器计算输出给Trig高电平时间和接收Echo低电平时间

3、函数计算时间换算成距离

4、串口打印距离和时间

程序

基本步骤与之前一致:User文件夹新建超声模块文件夹,存放.c和.h文件、打开keil工程文件添加.c文件;魔术棒添加超声模块路径。

打开.c文件,添加头文件和函数:

#include "stm32f10x.h"
#include "HCSR04.h"

void HCSR04_Init(void)

{

}

打开.h文件,添加函数声明:

#ifndef HCSR04_H_

#define HCSR04_H_

void HCSR04_Init(void);

#endif

HCSR04_Init函数内部添加GPIO结构体、定时器2结构体和定时器中断结构体:

void HCSR04_Init(void)
{
    GPIO_InitTypeDef HCSR04_GPIO_Struct;
    TIM_TimeBaseInitTypeDef HCSR04_TIME_Struct;
    NVIC_InitTypeDef HCSR04_NVIC_Struct;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    //Trig PB11 TX
    HCSR04_GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_PP;
    HCSR04_GPIO_Struct.GPIO_Pin = GPIO_Pin_11;
    HCSR04_GPIO_Struct.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOB,&HCSR04_GPIO_Struct);
    
    //Echo PB10 RX
    HCSR04_GPIO_Struct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    HCSR04_GPIO_Struct.GPIO_Pin = GPIO_Pin_10;
    HCSR04_GPIO_Struct.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOB,&HCSR04_GPIO_Struct);

    //TIM2
    HCSR04_TIME_Struct.TIM_ClockDivision = TIM_CKD_DIV1;
    HCSR04_TIME_Struct.TIM_CounterMode = TIM_CounterMode_Up;
    HCSR04_TIME_Struct.TIM_Period = 1000-1;
    HCSR04_TIME_Struct.TIM_Prescaler = 72-1;
    HCSR04_TIME_Struct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2,&HCSR04_TIME_Struct);
    
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
    TIM_Cmd(TIM2,DISABLE);
    
    //NVIC
    HCSR04_NVIC_Struct.NVIC_IRQChannel = TIM2_IRQn;
    HCSR04_NVIC_Struct.NVIC_IRQChannelCmd = ENABLE;
    HCSR04_NVIC_Struct.NVIC_IRQChannelPreemptionPriority = 0;
    HCSR04_NVIC_Struct.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&HCSR04_NVIC_Struct);
    

}

main函数添加HCSR04头文件、引用HCSR04_Init函数。结果需要将超声模块测得距离显示到电脑屏幕,需要通过串口进行显示,所以还需要添加串口函数:

int main()
{

    HCSR04_Init();
    Usart_IT_Init();

 

    while(1)
    {
         length = ...;
         printf("%lf\r\n",length);
    }
    
}

做到这一步已基本完成整体程序代码撰写,剩下的就是根据功能逐步实现。

新建函数Get_Length()用于计算距离,内部包括:

1、控制单片机输出高电平给HCSR04。因为超声模块需要至少10us的高电平信号,因此通过控制GPIO和延时函数控制高电平输出。所以这一部分程序与前面LED闪烁类似。

延时函数:

//us数量级延时函数

void delay_us (uint32_t us )
{
    
    us *= 8;
    while(us--);
}

//ms数量级延时函数

void delay_ms (uint32_t ms)
{
    while(ms--)
    {
        delay_us(1000);
    }
}

            GPIO_SetBits(GPIOB,GPIO_Pin_11);
            delay_us(20);
            GPIO_ResetBits(GPIOB,GPIO_Pin_11);

2、获取Echo高电平持续时间。因为Trig上电会持续发出方波,Echo用于回收反射回来的方波,自接收方波开始就会给单片机输入高电平信号,高电平信号持续时间可以换算成距离,所以需要得到Echo高电平持续时间。

一开始Echo为低电平,此时不需要打开定时器,但当Echo输出高电平,立即打开定时器;当Echo输出低电平时,马上关闭定时器。用两个函数用于控制定时器开关,再用一个函数用于计算Echo高电平持续时间,最后用time表示Echo高电平持续时间用于换算距离。

            

            while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 0);
            Open_TIM2();
        
            while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 1);
            Close_TIM2();

            time = Get_Echo_Time();

3、通过高电平时间换算成距离。length=time/58,最后返回距离即可。

    length = time/58.0;
    return length;

整体架构代码如下

//开启定时器

void Open_TIM2()
{

}

//关闭定时器

void Close_TIM2()
{
    

}

//计算完整定时器中断时间
void TIM2_IRQHandler()
{

}
//计算Echo高电平时间
float Get_Echo_Time()
{

}

float Get_Length(void)
{
    
    uint16_t time = 0;
    float length = 0;
    
            GPIO_SetBits(GPIOB,GPIO_Pin_11);
            delay_us(20);
            GPIO_ResetBits(GPIOB,GPIO_Pin_11);
            
            while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 0);
            Open_TIM2();
            while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 1);
            Close_TIM2();
            time = Get_Echo_Time();
            
            length = (float)time/58.0;
    return length;
}

函数内容声明

开启和关闭定时器只需要用到定时器使能函数即可

//开启定时器

void Open_TIM2()
{
        TIM_Cmd(TIM2,ENABLE);
}

//关闭定时器

void Close_TIM2()
{

        TIM_Cmd(TIM2,DISABLE);

}

计算Echo高电平持续时间分为两部分:完整中断时间和不足一次中断的时间

先计算完整中断时间。由定时器结构体可以发现:

    HCSR04_TIME_Struct.TIM_Period = 1000-1;
    HCSR04_TIME_Struct.TIM_Prescaler = 72-1;

说明每次中断时间=72*1000/72M=10^(-3)s=1ms。所以只需要计算中断次数就可以知道完整中断的时间。所以需要新设一个变量mscount用于计算次数。代码与之前一致,获取标志位函数,然后触发一次,mscount+1,标志位清零。

uint16_t mscount = 0;

void TIM2_IRQHandler()
{
    if (TIM_GetITStatus(TIM2,TIM_IT_Update) == 1)
    {
        
        mscount++;
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
        
    }
}

对于不到一次中断的时间需要通过内部CNT计数器获取。

但是要注意单位,完整中断时间的单位是ms,但是CNT内部时间则是us,所以在计算总时间需要换算单位。时间获取函数新设一个t用作表示总时间。

float Get_Echo_Time()
{
    
    uint16_t t=0;
    t = mscount * 1000; //us
    t = t + TIM_GetCounter(TIM2);
    
    delay_ms(50);//预留时间给寄存器写入时间


    return t;
    
}

到这里基本的超声测距功能可以实现,但是部分细节需要优化:

1、每次计算中断次数mscount和CNT内部时间都需要及时清零,因此需要在开始定时器函数中及时清零中断次数和内部时间。

2、为保险起见,还需要在总时间函数中完成总时间计算之后再清零一次,确保CNT内部及时清零。

3、每次计算距离都只有一次,可能会造成较大误差,所以通过计算平均距离减少误差估计。在获取距离函数中新设两个变量:i和sum;i用于循环,sum用于计算平均距离。

最终代码如下

HCSR04

#include "stm32f10x.h"
#include "HCSR04.h"


uint16_t mscount=0;//全局变量

void delay_us (uint32_t us )
{
    
    us *= 8;
    while(us--);
}

void delay_ms (uint32_t ms)
{
    while(ms--)
    {
        delay_us(1000);
    }
}


void HCSR04_Init(void)
{
    GPIO_InitTypeDef HCSR04_GPIO_Struct;
    TIM_TimeBaseInitTypeDef HCSR04_TIME_Struct;
    NVIC_InitTypeDef HCSR04_NVIC_Struct;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    //Trig PB11 TX
    HCSR04_GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_PP;
    HCSR04_GPIO_Struct.GPIO_Pin = GPIO_Pin_11;
    HCSR04_GPIO_Struct.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOB,&HCSR04_GPIO_Struct);
    
    //Echo PB10 RX
    HCSR04_GPIO_Struct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    HCSR04_GPIO_Struct.GPIO_Pin = GPIO_Pin_10;
    HCSR04_GPIO_Struct.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOB,&HCSR04_GPIO_Struct);

    //TIM2
    HCSR04_TIME_Struct.TIM_ClockDivision = TIM_CKD_DIV1;
    HCSR04_TIME_Struct.TIM_CounterMode = TIM_CounterMode_Up;
    HCSR04_TIME_Struct.TIM_Period = 1000-1;
    HCSR04_TIME_Struct.TIM_Prescaler = 72-1;
    HCSR04_TIME_Struct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2,&HCSR04_TIME_Struct);
    
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
    TIM_Cmd(TIM2,DISABLE);//关闭定时器中断
    
    //NVIC
    HCSR04_NVIC_Struct.NVIC_IRQChannel = TIM2_IRQn;
    HCSR04_NVIC_Struct.NVIC_IRQChannelCmd = ENABLE;
    HCSR04_NVIC_Struct.NVIC_IRQChannelPreemptionPriority = 0;
    HCSR04_NVIC_Struct.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&HCSR04_NVIC_Struct);
    

}


void Open_TIM2()
{
    
    TIM_SetCounter(TIM2,0);
    mscount = 0;
    TIM_Cmd(TIM2,ENABLE);
}

void Close_TIM2()
{
    
    TIM_Cmd(TIM2,DISABLE);

}


void TIM2_IRQHandler()
{
    if (TIM_GetITStatus(TIM2,TIM_IT_Update) == 1)
    {
        
        mscount++;
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
        
    }
    
}

float Get_Echo_Time()
{
    
    uint16_t t=0;
    t = mscount * 1000; //µs
    t = t + TIM_GetCounter(TIM2);
    
    TIM_SetCounter(TIM2,0);
    
    delay_ms(50);
    
    return t;
    
}


//GET LENGTH
float Get_Length(void)
{
    
    uint16_t time = 0;
    float length = 0;
    uint16_t i=0;
    float sum = 0;

    while(i != 5)
    {
            GPIO_SetBits(GPIOB,GPIO_Pin_11);
            delay_us(20);
            GPIO_ResetBits(GPIOB,GPIO_Pin_11);
            
            while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 0);
            Open_TIM2();
        
            i++;
        
            while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 1);
            Close_TIM2();
            time = Get_Echo_Time();

            length = (float)time/58.0;
            sum = sum + length;
    }
    
    length = sum/5.0;
    
    return length;
}

main

#include"stm32f10x.h"
#include"main.h"
#include"Usart_IT.h"
#include"HCSR04.h"


int fputc (int ch, FILE * p    )
{
    
    USART_SendData(USART1, ch);
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    
    return ch;
    
}

int main()
{
    float length =0;
    
    HCSR04_Init();
    Usart_IT_Init();

 

    while(1)
    {
         length = Get_Length();
         printf("%lf\r\n",length);
    }
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐