超声波测距器的设计
基于MSP430单片机超声波测距器的设计
一、总体设计
1.1超声波测距原理
超声波发射器向某一方向发射超声波 ,在发射时刻的同时开始计时 ,超声波在空气中传播 ,途中碰到障碍物就立即返回来 ,超声波接收器收到反射波就立即停止计时 ,超声波在空气中传播的速度为,根据计时时间 ,就可以计算出发射点距障碍物的距离 ,即
Vt (1)
L=1
2
公式中,V为声波在空气中传播的速度;t为超声波在空气中传播的时间这就是时间差测距法,本系统就是利用单片机控制超声波发射器发射超声波脉冲,同时利用单片机中的计数器开始计时。超声波达到后面的障碍物就会反射回来,接收装置接收到回波信号后由外部比较电路产生高电平使单片机产生外部中断。单片机运行中断服务子程序(ISR)计算出距离,并传送给LCD显示给司机,同时程序内还有比较模块,若车距小于3米,则显示所测量的距离同时单片机输出一个高电平使蜂鸣器报警,若车距大于3米,则显示安全蜂鸣器不报警,这样以声光两种方式可靠地向司机反馈信息,来保证倒车或行车的安全。
1.2 超声波时序图原理
HC-SR04超声波测距模块可提供2cm-400cm 的非接触式距离感应功能,测距精度高达3mm ,模块包括超声波发射器、接收器与控制电路。 时序图原理:
1、采用IO 口TRIG 触发测距,给至少10us 的高电平信号;
2、模块自动发送8个40khz 的方波,自动检测是否有信号返回;
3、有信号返回,通过TO 口ECHO 输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340m/s ))/2;
1.3 总体设计方法
本设计采用MSP430F1611单片机作为主控芯片,由超声波模块、电源电路模块、液晶显示模块、蜂鸣器等组成。利用定时器中断的方式捕获时间差,将采集的时间通过公式计算得到障碍物与测试点之间的距离,最后将测量的数据通过LCD12864液晶实时的显示出来。
二、硬件设计
2.1 基本应用系统
本设计采用MSP430F1611为主控芯片,由一个8M 的晶振和32.768KHz 的晶振外加一个复位电路,构成了基本应用系统,如图2所示:
图2 基本应用系统
图1 总体设计框图
与常用的51系列单片机相比,MSP430系列单片机功能强大、功耗低、集成度高,但其价格略高。为得到合适的性价比 ,选择了TI 公司早期生产的MSP430F1611,这种型号的单片机价格较低 ,功能虽然并不强大 ,但足以满足本系统要求
2.2 人机接口电路
本设计使用的液晶是12864字符型液晶,并且带字符库的,不需要查找代码。液晶电路使用时如果发现液晶不亮可以调节连接液晶的点位器,改变液晶的亮度。通过超声波模块获取的数据通过液晶实时显示出来。人机接口电路如图2所示;
2.3 超声波模块电路
超声波模块电路图如图4所示:
图3 人机接口电路
图4 超声波模块电路图
超声波的发射和接收采用 HC-SR04 模块, 模块包括超声波发射器、接收器和控制电路。 采用 IO 口 TRIG 触发测距,给至少 10μs 的高电平信号,模块自动发送 8 个 40kHz 的方波,并自动检测是否有信号返回,一旦检测到有回波信号则输出回响信号,回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号的时间间隔可以计算出距离。测量周期应为 60ms 以上,以防止发射信号对回响信号产生影响。 模块中 ECHO 是信号接收引脚,有信号返回时,通过IO 口 ECHO 输出一个高电平, 高电平持续时间就是超声波从发射到返回的时间。
2.4 电源电路
电源电路如图5所示,本设计中分别采用了3.3V 、5V 电压为超声波测距器供电。此电源电路首先将220V 交流电通过降压变压器降到10V 交流电,然后通过整流电路、滤波电路、稳压电路得到12V 直流电,再通过SPX29300稳压芯片得到5V 和3.3V 电压。
三、软件设计
3.1 程序流程图
超声波测距系统的软件由主程序、 超声波发射及接收中断程序、显示子程序构成。 由于 C 语言有利于实现较为复杂的算法,而汇编程序有较高的效率并容易精确计算程序运行的时间,因此控制程序可以采用 C 语言程序与汇编语言程序的混合编程。
图5 电源电路图
超声波测距主程序先对系统进行初始化,设定定时器T0 工作在方式1 ,置位总中断允许位EA 并对显示端口P2 清零,然后调用模块HC-SR04 发送超声波脉冲,延时2ms 后打开外部中断0 接收返回的超声波信号。主程序检测到接收成功后,将计数器T0 中的数(即超声波从发射到返回的时间t ),按照公式 d= (v*t )/2 计算测距器与被测物体间的距离,其中v 为声速。超声波测距系统主程序流程如图 6 所示。
图 6 主程序流程图
在环境温度变化不大的情况下,可以认为声速是基本不变的。设计时取20℃时的声速为344m/s ,则有d= (172*T0/10000)cm ,其中,T0为计数器T0 的计数值。表 1 列出了在不同温度下超声波的声速。
3.2 接口定义
本设计采用的MSP430F1611单片机是一款功能强大、低功耗工业型微处理器。单片机与超声波模块、液晶显示模块、蜂鸣器模块的接口定义如表2所示:
四、超声波测距器测量结果及分析
系统设计并调试后,测试了8组数据,并对测距结果进行了分析,用以验证测距器的准确性与稳定性,测距结果如表3所示:
对测距结果进行分析可知,超声波测距器HC-SR04 测距模块的测距范围为2cm-400cm。在程序设计的过程中,要考虑到发射信号对回响信号的影响,测量周期至少在60ms以上。测距时被测物体的面积应不少于0.5m2,并保证被测面尽量平整,以免影响测量结果。在测距精度要求很高的情况下,要考虑到温度对于超声波声速的影响,通过温度补偿的方法进行校正。
五、结论
超声波指向性强,能量消耗慢,在介质中传播的距离较远,可以经常用于距离的测量。利用单片机控制器和HC-SR04 超声波模块设计的超声波测距系统,设计较为方便,计算处理也比较简单,在测量精度方面能达到日常应用的要求,测量速度快,结果显示直观,在汽车倒车控制、建筑施工工地及其他工业现场的位置监控方面有较好的应用前景。
附录二源代码
/******P1.2为捕获源输入端口,P1.3与传感器的trig端口相连******/
#include
#include"12864.h"
#define uint unsigned int
#define uchar unsigned char
#define CPU (1000000)
#define delay_us(x) (__delay_cycles((double)x*CPU/1000000.0)) //"__"
#define delay_ms(x) (__delay_cycles((double)x*CPU/1000.0))
uchar line1[]="距离为:";
unsigned long line2[10]; //用于储存高电平期间计数的数值
uint overflow=0,n=0;
float r=0;
/**************(不精确)延时函数****************/
void delay_nms(uint x)
{
uint i,j;
for(i=0;i for(j=0;j<110;j--); } /*******************时钟初始化********************/ void init_clk() { uchar i; BCSCTL1 &= ~XT2OFF; // XT2= HF XTAL do { IFG1 &= ~OFIFG; // Clear OSCFault flag for (i = 0xFF; i > 0; i--); // Time for flag to set } while ((IFG1 & OFIFG)); // OSCFault flag still set? BCSCTL2 |=SELM_2+SELS+DIVM_3; // MCLK=1M,SMCLK= 8M //MCLK频率测试 //P5SEL|=BIT4; //P5DIR|=BIT4; } /******************TA的初始化设置********************/ void CAP_Init() { P1DIR &=~ BIT2; //设置P1.2为输入 P1SEL |= BIT2; //设置P1.2为模块功能,即捕获源 TACCTL1|=CAP+CCIS_0+CM_3+CCIE+SCS; //上升、下降沿触发捕捉,用于测脉宽,同步模式、时能中断CCI1A TACTL |=TASSEL1 + ID_3+ MC_2+TACLR; //选择8M-SMCLK时钟,8分频,连续计数模式 } /*********************处理函数**********************/ void chuli() { unsigned long sum=0; P1SEL &=~BIT2; //设置P1.2为普通IO口 P1DIR |= BIT2; //设置P1.2为输出 P1OUT &=~BIT2; //先把echo端口拉低 P1OUT |= BIT3; delay_us(15); P1OUT &=~BIT3; delay_us(200); P1SEL |= BIT2; //设置P1.2为模块功能,即捕获源 P1DIR &=~BIT2; //设置P1.2为输入 if(n==10) { uint k; n=0; TACTL|=MC_0; //关定时器 for(k=0;k<10;k++) { sum=sum+line2[k]; } sum=sum/10; r=sum/1000000.0; //把us化成s r=(r*340)/2.0; displayfloat(1,4,r); delay_ms(200); } sum=0; r=0; } /*********************主函数**********************/ void main() { WDTCTL = WDTPW + WDTHOLD; //关看门狗 P1SEL &=~BIT3; //(trig)设置为普通IO口 P1DIR |= BIT3; //配置为输出 P1OUT &=~BIT3; /*P1SEL &=~BIT4; //(echo)设置为普通IO口 P1DIR |= BIT4; //配置为输出 P1OUT &=~BIT4;*/ init_lcd(); //液晶初始化 init_clk(); CAP_Init(); _EINT(); while(1) { displaystr(1,0,line1); delay_nms(20); chuli(); delay_ms(20); } } /******************TA的中断服务程序********************/ #pragma vector=TIMERA1_VECTOR __interrupt void Timer_A (void) { switch (TAIV) //向量查询 { case 2: //捕获中断 if (TACCTL1&CCI) //上升沿触发 { TACTL |=TACLR; //清除TAR overflow=0; } else //下降沿触发 { line2[n++]=TACCR1+overflow; TACTL |=TACLR; //清除TAR overflow=0; } break; //TACCR1 CCIFG中断向量(P1.2) case 4: break; case 10: //定时器溢出中断 overflow++; //溢出次数 break; default: break; } } /******************************************************************/ /*12864的模板程序,LCD12864的4、5、6脚分别接P4、P5、P6 */ /**********/********************************************************/ ********************************************************/ #include #include "12864.h" #define BIT(x) (1<<(x)) //1左移X位 #define uint unsigned int #define uchar unsigned char #define cyCS 4 //P2.4,命令片选信号 #define cySID 5 //P2.5,读写选择串行数据 #define cySCLK 6 //P2.6,同步时钟 #define cyPSB 7 //P2.7,串并行选择 /****************************************************************** ** 函数名: 12864定位函数 ** 函数功能:根据X,Y找到12864的地址 ** 输入参数: X坐标,Y坐标 ******************************************************************/ void lcd_pos(uchar x,uchar y) //x代表lcd行,y代表lcd列 { int pos; switch(x) { case 1:pos = 0x80;break; case 2:pos = 0x90;break; case 3:pos = 0x88;break; case 4:pos = 0x98;break; default:pos=0x80; } pos += y; send(0,pos); } /**************写指令、数据函数********************/ /****************************************************************** ** 函数名: 12864写函数 ** 函数功能:根据输入的数据或指令,写入12864 ** 输入参数: type为0时写指令,transdata为数据 ******************************************************************/ void send(uchar type,uchar transdata) { uchar firstbyte = 0xf8; //第一个字节写指令 uchar temp; uchar i,j; if(type) firstbyte |= 0x02; //写数据 P2OUT |= BIT(cyCS); //片选线拉高 P2OUT &= ~BIT(cySCLK);//CLK拉低 for(j=3;j>0;j--) { switch(j) { case 3:temp=firstbyte;break; //第一个字节 case 2:temp=transdata&0xf0;break; //第二个字节 case 1:temp=(transdata<<4)&0xf0;break; //第三个字节 } for(i=8;i>0;i--) { if(temp&0x80) //提取最高位 P2OUT |= BIT(cySID); //最高位为1时输出1 else P2OUT &= ~BIT(cySID); //最高位为0时输出0 P2OUT |= BIT(cySCLK); //时钟拉高,写数据 temp<<= 1; P2OUT &= ~BIT(cySCLK); //时钟线拉低 } delay_nms(1); //延时 } P2OUT &= ~BIT(cySID); //输出为0 P2OUT &= ~BIT(cyCS); //片选线拉低,为下一次做准备 } /****************************************************************** ** 函数名: 12864显示数组 ** 函数功能:根据输入的数组,显示到12864指定的位置 ** 输入参数: X,Y坐标,数组指针 ******************************************************************/ void displaystr(uchar x,uchar y,uchar *p) { lcd_pos(x,y); uint i=0; while(*(p+i)!='\0') { send(1,*(p+i)); i++; } } /****************************************************************** ** 函数名: 12864显示数字** 函数功能:根据输入的数字,显示到12864指定的位置 ** 输入参数: X,Y坐标,要显示的整形数组NUM ** 返回值:无 ** 函数创建者: ** 函数创建日期:2014年6月21日 ******************************************************************/ void displayint(uchar x,uchar y,uint NUM) { /*uchar a_SHOW[5]; a_SHOW[0]=NUM/1000+'0'; //qian a_SHOW[1]=NUM%1000/100+'0'; //bai a_SHOW[2]=NUM%1000%100/10+'0'; //shi a_SHOW[3]=NUM%10+'0';//ge a_SHOW[4]='\0';*/ uchar a_SHOW[6]; a_SHOW[0]=NUM/10000+'0'; //wan a_SHOW[1]=NUM%10000/1000+'0'; //qian a_SHOW[2]=NUM%10000%1000/100+'0'; //bai a_SHOW[3]=NUM%10000%1000%100/10+'0'; //shi a_SHOW[4]=NUM%10+'0';//ge a_SHOW[5]='\0'; displaystr(x,y,a_SHOW); } /****************************************************************** ** 函数名: 12864显示当个字符 ** 函数功能:根据输入的字符,显示到12864指定的位置 ** 输入参数: X,Y坐标,要显示的字符a ******************************************************************/ void displaychar(uchar x,uchar y,char a) { lcd_pos(x,y); send(1,a); } /****************************************************************** ** 函数名:显示浮点型数据 ** 函数功能:根据输入的浮点型数字,显示到12864指定的位置 ** 输入参数: X,Y坐标,要显示的浮点数num ** 返回值:无 ** 函数创建者: ** 函数创建日期:2014年6月21日 ******************************************************************/ void displayfloat(uchar x,uchar y,float num) { uchar a_SHOW[7]; long int t; t=(long int)(num*100); //乘以1000取整,保留二位小数a_SHOW[0]=(t/1000)%10+'0'; //shi a_SHOW[1]=(t/100)%10+'0' ; //ge a_SHOW[2]='.'; //小数点 a_SHOW[3]=(t/10)%10+'0'; //shi fen a_SHOW[4]=t%10+'0'; //bai fen a_SHOW[5]='m'; //a_SHOW[5]=t%10+'0'; //qian fea_SHOW[6]='\0'; displaystr(x,y,a_SHOW); } /******************************************* 函数名称:Clear_GDRAM 功能:清除液晶GDRAM内部的随机数据 参数:无 返回值:无 ********************************************/ void Clear_GDRAM(void) { uchar i,j,k; send(0,0x34); //打开扩展指令集 i = 0x80; for(j = 0;j < 32;j++) { send(0,i++); //对应行 send(0,0x80); //对应列 for(k = 0;k < 16;k++) { send(1,0x00); } } i = 0x80; for(j = 0;j < 32;j++) { send(0,i++); send(0,0x88); for(k = 0;k < 16;k++) { send(1,0x00); } } send(0,0x30); //回到基本指令集 } /******************************************* 函数名称:Draw_PM 功能:在整个屏幕上画一个图片 参数:ptr--指向保存图片位置的指针 返回值:无 ********************************************/ void Draw_PM(const uchar *ptr) { uchar i,j,k; send(0,0x34); //打开扩展指令集 i = 0x80; for(j = 0;j < 32;j++) { send(0,i++); send(0,0x80); for(k = 0;k < 16;k++) { send(1,*ptr++); } } i = 0x80; for(j = 0;j < 32;j++) { send(0,i++); send(0,0x88); for(k = 0;k < 16;k++) { send(1,*ptr++); } } send(0,0x36); //打开绘图显示 send(0,0x30); //回到基本指令集 } /******************************************** 函数名称:Qiandisplay 功能:将16进制ADC转换数据变换成十进制 表示形式 参数:x1--16进制数据 返回值:无(有疑问。。。。。。。。。) ********************************************/ void Qiandisplay(uchar x,uchar y,uint x1) { uchar a_SHOW[5]; a_SHOW[0]=x1/1000+'0'; a_SHOW[1]=x1%1000/100+'0'; a_SHOW[2]=x1%100/10+'0'; a_SHOW[3]=x1%10+'0'; a_SHOW[4]='\0'; displaystr(x,y,a_SHOW); } /****************************************************************** ** 函数名: ** 函数功能:根据输入的数字,显示到12864指定的位置 ** 输入参数: X,Y坐标,要显示的字符num ******************************************************************/ //void displaynum(uchar shuzi) void displaynum(uchar x,uchar y,uint num) { lcd_pos(x,y); send(1,num); } /****************************************************************** ** 函数名: 12864初始化函数 ** 函数功能:初始化12864 ** 输入参数:无 ******************************************************************/ void init_lcd() { P2DIR |= BIT(cyCS) + BIT(cySID) + BIT(cySCLK) +BIT(cyPSB); //设置12864的接口为输出。 P2OUT &= ~BIT7; delay_nms(6); //延时等待液晶完成复位 send(0,0x30); //功能设置:一次送8位数据,基本指令集 delay_nms(10); send(0,0x02); //DDRAM地址归位 delay_nms(5); send(0,0x0c); //显示设定:开显示,不显示光标,不做当前显示位反白闪动 delay_nms(5); send(0,0x06); delay_nms(10); //功能设置,点设定:显示字符/光标从左到右移位,DDRAM地址加1 send(0,0x01); //清零 delay_nms(10); } #define _12864_h #define uint unsigned int #define uchar unsigned char void send(uchar type,uchar transdata); //写指令、数据函数 void delay_1ms(); //延时毫秒 void delay_nms(uint n); //延时毫秒 void displaynum(uchar x,uchar y,uint num); /*************显示0~9数字函数 ***************/ void displaystr(uchar x,uchar y,uchar *ptr); /*************显示数组函数***************/ void displaychar(uchar x,uchar y,char); /*************显示字符函数***************/ void displayint(uchar x,uchar y,uint); /*************显示整数函数***************/ void displayfloat(uchar x,uchar y,float num); void init_lcd(); //液晶初始化 void Qiandisplay(uchar x,uchar y,uint x1); void lcd_pos(uchar x, uchar y); //液晶定位 void Clear_GDRAM(void); //清屏 #endif