目录
1、模块说明
VCC引脚:5V电源
TRIG引脚:触发信号引脚,单片机给超声波模块一个信号,超声波模块就会工作。
ECHO引脚:回响信号引脚,当超声波模块已经测量距离成功后,通过该引脚告诉单片机当前超声波传输的时间。
GND:信号地。
2、时序图
看时序图的技巧,从上到下,从左到右,因为一般的时序图是隐含了时间轴。如何区分t1、t2、t3、t4,就是要找出它们的变化点。
超声波模块实物硬件连接如下:
- 参考角度1
- 参考角度2
3、注意事项
1)不同的温度,声音的传播速度是不一样的
2)超声波的测量误差为3mm,可根据3mm的测距求出测量时间,求算如下:
3)可编写简单代码如下
int32_t sr04_get_distance(void)
{
uint32_t t=0;
uint32_t d=0;
//1.PB6引脚输出至少10us以上的高电平
PBout(6)=1;
delay_us(20);
PBout(6)=0;
//2.等待超声波模块测距完毕,标志性的动作就是回响信号引脚从输出低电平变为输出高电平
//在等待过程当中,最好添加超时处理
t=0;
while(PEin(6)==0)
{
delay_us(1);
t++;
//超时处理
if(t >= 1000000)
return -1;
}
//3.测量PE6引脚的高电平时间,该高电平的持续时间就是超声波模块的传输时间
t=0;
while(PEin(6))
{
delay_us(9); //9us == 3mm
t++;
//超时处理
if(t >= 1000000)
return -2;
}
//4.将时间转换为距离
d = t*3/2;
return d;
}
加超时处理(属于容错处理中的一部分)有以下原因:
1)硬件问题:引脚连接有问题、超声波模块的损坏
2)软件问题:延时不准确
4、完整代码
沿用串口的代码模板:
#include "stm32f4xx.h"
#include <stdio.h>
static GPIO_InitTypeDef GPIO_InitStruct;//不加*
static USART_InitTypeDef USART_InitStructure;
static NVIC_InitTypeDef NVIC_InitStructure;
#define PEout(x) *(volatile uint32_t *)(0x42000000+(GPIOE_BASE+0x14-0x40000000)*32+(x)*4)
#define PFout(x) *(volatile uint32_t *)(0x42000000+(GPIOF_BASE+0x14-0x40000000)*32+(x)*4)
#define PAin(x) *(volatile uint32_t *)(0x42000000+(GPIOA_BASE+0x10-0x40000000)*32+(x)*4)
#define PEin(x) *(volatile uint32_t *)(0x42000000+(GPIOE_BASE+0x10-0x40000000)*32+(x)*4)
#define PBout(x) *(volatile uint32_t *)(0x42000000+(GPIOB_BASE+0x14-0x40000000)*32+(x)*4)
#define PBin(x) *(volatile uint32_t *)(0x42000000+(GPIOB_BASE+0x10-0x40000000)*32+(x)*4)
struct __FILE { int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;
int fputc(int ch, FILE *f) {
USART_SendData(USART1,ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return ch;
}
void delay_us(uint32_t n)
{
//使用系统始终的8分频作为系统定时器的时钟源
SysTick->CTRL = 0; // Disable SysTick
SysTick->LOAD = n*21-1; // Count from 255 to 0 (256 cycles)
SysTick->VAL = 0; // Clear current value as well as count flag
SysTick->CTRL = 1; // Enable SysTick timer with processor clock
while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set
SysTick->CTRL = 0; // Disable SysTick
}
void delay_ms(uint32_t n)
{
while(n--)
{
SysTick->CTRL = 0; // Disable SysTick,关闭系统定时器后才能取配置
SysTick->LOAD = 21000-1; // 填写计数值,就是我们的延时时间
SysTick->VAL = 0; // 清空标志位
SysTick->CTRL = 1; // 选中21MHz的时钟源,并开始让系统定时器工作
while ((SysTick->CTRL & 0x10000)==0);//等待计数完毕
}
SysTick->CTRL = 0; // 不再使用就关闭系统定时器
}
void usart1_init(uint32_t baud)
{
//打开PA硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//打开串口1硬件时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//PA9,PA10配置为复用功能模式
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed=GPIO_High_Speed;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//将PA9和PA10引脚连接到串口1
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
//配置串口1相关参数:波特率、无校验位、8位数据位、1个停止位
USART_InitStructure.USART_BaudRate = baud; //波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1个停止位
/* When using Parity the word length must be configured to 9 bits */
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//允许收发数据
USART_Init(USART1, &USART_InitStructure);
//配置串口1的中断触发方法:接收一个字节触发中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//配置串口1的中断优先级
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//使能串口1工作
USART_Cmd(USART1, ENABLE);
}
void sr04_init(void)
{
//端口B硬件时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
//端口E硬件时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
//配置PB6为输出模式
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_Speed=GPIO_High_Speed;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//配置PE6为输入模式
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Speed=GPIO_High_Speed;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_Init(GPIOE,&GPIO_InitStruct);
//PB6初始状态为低电平
PBout(6)=0;
}
int32_t sr04_get_distance(void)
{
uint32_t t=0;
int32_t d=0;
//PB6高电平
PBout(6)=1;
//持续10us以上
delay_us(20);
//PB6低电平
PBout(6)=0;
//等待回响信号(等待PE6出现高电平)
while(PEin(6)==0)
{
t++;
delay_us(1);
if(t>=1000000)
return -1;
}
//测量高电平的时间
t=0;
while(PEin(6))
{
t++;
delay_us(9); //9us=3mm
if(t>=1000000)
return -2;
}
//得到距离
t=t/2; //时间是超声波从发射到返回的时间
d=3*t;
return d;
}
int main()
{
int32_t d=0;
//打开端口F的硬件时钟,就是为端口F供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
//配置引脚
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_Speed=GPIO_High_Speed;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_Init(GPIOF,&GPIO_InitStruct);//为何传地址
usart1_init(115200);//串口1波特率115200bps
PFout(9)=1;
sr04_init();
while(1)
{
d=sr04_get_distance();
if(d>0)
{
if(d>=20 && d<=4000)
{
printf("distance=%dmm\r\n",d);
}
}
delay_ms(1000);
}
//为何用此循环
}
void USART1_IRQHandler(void)
{
uint32_t d;
//检测标志位
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
{
//接收数据
d=USART_ReceiveData(USART1);
//将接收到的数据返发给PC
USART_SendData(USART1,d);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
if(d=='1') PFout(9)=1;
if(d=='0') PFout(9)=0;
//清空标志位
USART_ClearITPendingBit(USART1,USART_FLAG_RXNE);
}
}