超声波的工作原理(基于wemos小项目拆分的讲解)
1.超声波模块介绍:
超声波传感器模块上面通常有两个超声波元器件,一个用于发射,一个用于接收。
电路板上有4个引脚:
VCC (正极)
Trig (触发)
Echo (回应)
GND (接地)
工作电压与电流: 5V,15mA
感测距离: 2—400cm
感测角度: 不答应15°
检测物体的面积不要小于 50cm²并且尽量平整
具备温度补偿电路
在超声波模块触发脚位输入10微秒以上的高电位,即可发生超声波,发射超声波之后,与接收到传回的超声波之前,“响应”脚位呈现高点位。
因此,程序可以从 “响应” 脚位的高电位脉冲持续时间,换算出被测物体的距离。
我们可以看到它一共有四个引脚:
Vcc:接5V电源(接1号引脚)
Trig:输出端口(接16号引脚)
Echo:输入端口(接18号端口)
Gnd:接地端(接6号端口)
硬件连接:
我们重点来介绍这次试验用到的两种引脚:
①.电源引脚:分为3.3V / 5V / 0V三种,其中0V代表接地
②.GPIO输入输出接口:这是非常重要的一类引脚,上图中标明了GPIO的就是这类接口,树莓派GPIO接口只能输入输出数字信号(0&1等)
实现代码:
#include <wiringPi.h>
#include <stdio.h>
#include <sys/time.h>
#define Trig 4
#define Echo 5
void ultraInit(void)
{
pinMode(Echo, INPUT); //设置端口为输入
pinMode(Trig, OUTPUT)/; //设置端口为输出
}
float disMeasure(void)
{
struct timeval tv1; //timeval是time.h中的预定义结构体 其中包含两个一个是秒,一个是微秒
/*
struct timeval
{
time_t tv_sec; //Seconds.
suseconds_t tv_usec; //Microseconds.
};
*/
struct timeval tv2;
long start, stop;
float dis;
digitalWrite(Trig, LOW);
delayMicroseconds(2);
digitalWrite(Trig, HIGH); //拉高
delayMicroseconds(10); //发出超声波脉冲
digitalWrite(Trig, LOW); //拉低
while(!(digitalRead(Echo) == 1)); //如果不是1,则继续等待
gettimeofday(&tv1, NULL); //获取当前时间 开始接收到返回信号的时候
while(!(digitalRead(Echo) == 0)); //如果不是0,则继续等待
gettimeofday(&tv2, NULL); //获取当前时间 最后接收到返回信号的时候
start = tv1.tv_sec * 1000000 + tv1.tv_usec; //微秒级的时间
stop = tv2.tv_sec * 1000000 + tv2.tv_usec;
dis = (float)(stop - start) / 1000000 * 34000 / 2; //计算时间差求出距离
return dis;
}
int main(void)
{
float dis;
if(wiringPiSetup() == -1){ //如果初始化失败,就输出错误信息 程序初始化时务必进行
printf("setup wiringPi failed !");
return 1;
}
ultraInit();
while(1){
dis = disMeasure();
printf("distance = %0.2f cm\n",dis);
delay(1000);
}
return 0;
}
编译运行:
gcc demo1.c -lwiringPi
./a.out
distance = 170.74 cm
distance = 190.74 cm
distance = 150.04 cm
distance = 180.00 cm
.......
代码分析:
1.wiringPiSetup(void):
在使用wiringPi.h库时,在执行任何操作前都必须初始化树莓派,否则程序便无法正常运行。
当初始化操作未完成时,函数返回值为-1
其他的树莓派初始化函数还有wiringPiSetupGpio(void),此函数使用方法与wiringPiSetup(void)类似,当函数无法正常运行时返回值也是-1.不同的地方在于,wiringPiSetup(void)初始化树莓派引脚时使用的是wiringPi 引脚编号表引脚的编号为 0~16;
wiringPiSetupGpio(void)初始化树莓派引脚时使用的是BCM GPIO 引脚编号表。
其他两种函数还有wiringPiSetupPhys(void)和wiringPiSetupSys (void) ,因为不常用,所以在此处不做介绍。
2.void pinMode(uint8 pin, WiringPinMode mode)
这个函数是用来确定引脚的功能的,如果在使用某个引脚之前没有确定这个引脚的功能或者引脚设置模式不正确,就会出现一些不可捉摸的错误。
这个函数有两个参数,第一个参数pin是一个正整数,用来指定引脚的编号(0-16),第二个参数是用来指定引脚的IO模式,可用的参数有INPUT , OUTPUT , OUTPUT_OPEN_DRAIN , INPUT_ANALOG , INPUT_PULLUP , INPUT_PULLDOWN , INPUT_FLOATING , PWMPWM_OPEN_DRAIN
3.digitalWrite(uint8 pin, uint8 value)
这又是一个在wiringPi.h中已经定义好的函数,它的作用是对一个已近配置为输出模式(OUTPUT或者OUTPUT_OPEN_DRAIN)
的 引脚 输出指定的电平信号,其中pin是一个正整数,用来指定一个已经初始化过的引脚,value可以是数字或者参数,数字表示下:1代表高电平,0代表低电平;参数表示下:LOW代表低电平,HIGH代表高电平。
4.delayMicroseconds (unsigned int howLong)
将线程暂停指定的微秒数(1000微妙=1毫秒=0.001s),因为Linux是多线程的,所以实际暂停的秒数可能比设置的更多一些
5.digitalRead (int pin)
读取一个引脚的电平值(LOW / HIGH),并且返回。
其中pin是引脚的编号,该引脚的初始化类型必须为INPUT等输入类型。返回值也可以是1 / 0(当输入信号电压在0~1.16 V时该函数返回0,当输入信号在1.83~3.3 V时返回1。如果输入电压在1.16~1.83 V之间不确定会返回0还是1。)
6.gettimeofday(struct timeval *, struct timezone *);
则个函数返回的是1970年0:00:00到现在经过的秒数,函数的正常传入时需要用到两个参数。
第一个已经介绍过了,第二个因为在这里没有用处,所以暂且不表,传入参数时用NULL即可,这里关于这个问题还有一个小故事:timeval中的tv_sec是time_t类型的,即long的类型。在32位下为4个字节,能够表示的最大正整数是2147483647,而这个表示的时间最大能到2038-01-19 03:14:07,超过了之后就变为-2147483648,这就是linux2038年的问题。而64位系统下的time_t类型即long类型长度为8个字节,可以用到几千亿年,这么长的时间完全不用担心溢出的问题。
7.根据返回的秒数计算出微秒数
start = tv1.tv_sec * 1000000 + tv1.tv_usec ;
stop = tv2.tv_sec * 1000000 + tv2.tv_usec;
我们知道 timeval结构体中含有两个变量,
tv_sec表示的是秒数,1秒=1000000微妙,第二个参数tv_usec表示的就是微秒数,所以通过这两个式子我们就求出了开始和结束时的微秒数,然后做差即可得到超声波传递所使用的时间
8.根据时间计算距离
(stop - start) / 1000000 * 34000 / 2
因为stop和start原本表示的微妙,所以做差之后处1000000换算回是多少秒。
因为声音在物质中的传播受到物质材质的影响,这里我们暂且不考虑介质的种类,默认为声音是在空气中传播,所以取声音的速度为340m/s=34000cm/s,因为超声波测距的误差较小的范围200-300cm,所以我们这里计算速度时用cm表示。
参考:
树莓派控制超声波