在测量海拔高度时,传统的做法是通过测量某一高度的大气压力,再经过换算才能得到高度数据。为了测量大气压力,就得用上气压传感器,下面就来讨论一下气压传感器的应用。 气压传感器是压力传感器中的一种,它专用于测量气体的绝对压强。目前市场上能见到的气压传感器有很多种,下面就以市场上常见的Bosch公司推出的BMP180来进行讨论。BMP180不仅可以实时的测量大气压力,还能测量实时温度。同时它还具有IIC总线的接口,便于单片机进行访问。另外它的使用也很方便,不需要太多的操作就可读取到气压及测量数据。 BMP180采用强大的8脚陶瓷无引线芯片承载(LCC)超薄封装,它性能卓越,内置有校准补偿,绝对精度最低可以达到0.03hPa(0.25米),并且耗电极低,只有3μA。气压测量范围从300hPa到1100hPa,换算成高度为海拔9000米到500米。下图是其封装外形和引脚排列。 引脚各功能如下:1脚(GND)接电源地,2脚(EOC)为完成转换输出,3脚(VDDA)为正电源,4脚(VDDD)为数字正电源,5脚为空,6脚(SCL)为IIC的时钟端,7脚(SDA)为IIC的数据端,8脚(XCLR)为主清除信号输入端,低电平有效,用来复位BMP180和初始化寄存器和控制器,在不用的情况下可以空置。 BMP180的工作电压为1.8V~3.6V,典型工作电压为2.5V,其与单片机相连的典型电路如下图所示。 从上图中可以看到,BMP180内包含有电阻式压力传感器、AD转换器和控制单元,其中控制单元包括了EEPROM和IIC接口。读取BMP180时会直接传送没有经过补偿的温度值和压力值。而在EEPROM中则储存了176位单独的校准数据,这些数据将对读取的温度压力值进行补偿。176位的EEPROM被划分为11个字,每个字16位,这样就包含有11个校准系数。每个器件模块都有自己单独的校准系数,在第一次计算温度压力数据之前,单片机就应该先读出读出EEPROM中的这些校准数据,然后再开始采集数据温度和压力数据。 和所有的IIC总线器件一样,BMP180也有一个器件的固定地址,根据其数据手册,出厂时默认BMP180的从机地址为0xEE(写入方向),或0xEF(读出方向)。温度数据UT和压力数据UP都存储在寄存器的第0到15位之中,压力数据UP的精度还可扩展至16~19位。
上图中左边是Bosch公司技术手册上提供的读取顺序的流程图,右边是EEPROM中的校准数据。 从流程图中可以看出,单片机发送开始信号启动温度和压力测量,经过一定的转换时间(4.5ms)后,从IIC接口读出结果。为了将温度的单位换算成℃和将压力的单位换算成hPa,需要用到EEPROM中的校准数据来进行补偿计算,这些数据也可以从IIC接口读出。事实上,EEPROM中的这些校准数据应该在程序初始化的时候就读出,以方便后面的计算。 在同一个采样周期中BMP180可以采128次压力值和1次温度值,并且这些值在读取后会被及时更新掉。若不想等待到最大转化时间之后才读取数据,可以有效利用BMP180的输出管脚EOC来检查转化是否完毕。若为1表示转换完成,为0表示转换正在进行中。 要得到温度或气压的值,必须要访问地址为0xF4的控制寄存器。它根据写入数据的不同,回应的值也不一样,具体如下表所示。 从图中可以看出,要获得温度数据,必须先向控制寄存器(地址0xF4)写0x2E,然后等待至少4.5ms,才可以从地址0xF6和0xF7读取十六位的温度数据。同样,要获得气压数据,必须先向控制寄存器(地址0xF4)写0x34,然后等待至少4.5ms,才可以从地址0xF6和0xF7读取16位的气压数据,若要扩展分辨率,还可继续读取0xF8(XLSB)扩展16位数据到19位。获取到的数据还要根据EEPROM中的校准数据来进行补偿后才能用,EEPROM的数据读取可根据上图中的地址来进行,地址从0xAA~0xBF,具体的补偿算法可参看官方的数据手册,这里就不赘述了。 下面以一个例子来看一下BMP180的具体应用。 例子:利用单片机读取来自BMP180的温度和气压数据,并把它们通过LCD1602显示出来。 BMP180的SDA、SCL端分别接到ATMega16的TWI端(PC1、PC0),EOC和XCLR端悬空,LCD1602的接法与前面的一致。参考代码如下。 - #include <iom16.h>
- //=========================定义从器件地址和读写方式=============================
- #define rd_device_add 0xef //即11101111,1110111是BMP180器件的固定地址,最后的1表示对从器件进行读操作
- #define wr_device_add 0xee //即11101110,1110111是BMP180器件的固定地址,最后的0表示对从器件时行写操作
- //===============================TWI状态定义==================================
- #define START 0x08
- #define RE_START 0x10
- #define MT_SLA_ACK 0x18
- #define MT_SLA_NOACK 0x20
- #define MT_DATA_ACK 0x28
- #define MT_DATA_NOACK 0x30
- #define MR_SLA_ACK 0x40
- #define MR_SLA_NOACK 0x48
- #define MR_DATA_ACK 0x50
- #define MR_DATA_NOACK 0x58
- //=============================常用TWI操作定义================================
- #define Start() (TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN))
- #define Stop() (TWCR=(1<<TWINT)|(1<<TWSTO)|(1<<TWEN))
- #define Wait() {while(!(TWCR&(1<<TWINT)));}
- #define TestAck() (TWSR&0xf8)
- #define SetAck() (TWCR|=(1<<TWEA))
- #define SetNoAck() (TWCR&=~(1<<TWEA))
- #define Twi() (TWCR=(1<<TWINT)|(1<<TWEN))
- #define Write8Bit(x) {TWDR=(x);TWCR=(1<<TWINT)|(1<<TWEN);}
- //============引脚电平的宏定义===============
- #define LCM_RS_1 PORTB_Bit0=1 //RS脚输出高电平
- #define LCM_RS_0 PORTB_Bit0=0 //RS脚输出低电平
- #define LCM_RW_1 PORTB_Bit1=1 //RW脚输出高电平
- #define LCM_RW_0 PORTB_Bit1=0 //RW脚输出低电平
- #define LCM_EN_1 PORTB_Bit2=1 //EN脚输出高电平
- #define LCM_EN_0 PORTB_Bit2=0 //EN脚输出低电平
- #define DataPort PORTA //PORTA为数据端口
- #define Busy 0x80 //忙信号
- //==============定义全局变量================
- unsigned char ge,shi,bai,qian,wan,shiwan; //显示变量
- unsigned char ReadTemp[2]; //接收到的温度数据缓冲区
- unsigned char ReadPressure[2]; //接收到的气压数据缓冲区
- int ac1;
- int ac2;
- int ac3;
- unsigned int ac4;
- unsigned int ac5;
- unsigned int ac6;
- int b1;
- int b2;
- int mb;
- int mc;
- int md;
- //==============定义显示字符串================
- const unsigned char str0[]={" T: . C "}; //显示温度
- const unsigned char str1[]={" P: . Kpa "}; //显示气压
- //===============1mS延时===================
- void delay_1ms(void)
- {
- unsigned int i;
- for(i=1;i<(unsigned int)(8*143-2);i++)
- ;
- }
- //=============n*1mS延时===============
- void delay_nms(unsigned int n)
- {
- unsigned int i=0;
- while(i<n)
- {delay_1ms();
- i++;
- }
- }
- //===============IIC总线写n个字节(成功返回0,失败返回1)=====================
- unsigned char I2C_Write(unsigned char RomAddress,unsigned char *buf,unsigned char len)
- {
- unsigned char i;
- Start(); //启动I2C总线
- Wait(); //等待回应
- if(TestAck()!=START)
- return 1; //若回应的不是启动信号,则失败返回1
- Write8Bit(wr_device_add); //写I2C从器件地址、写方向
- Wait(); //等待回应
- if(TestAck()!=MT_SLA_ACK)
- return 1; //若回应的不是ACK信号,则失败返回值1
- Write8Bit(RomAddress); //写BMP180的ROM地址
- Wait(); //等待回应
- if(TestAck()!=MT_DATA_ACK)
- return 1; //若回应的不是ACK信号则失败返回值1
- for(i=0;i<len;i++)
- {
- Write8Bit(buf[i]); //写数据到BMP180的ROM中
- Wait(); //等待回应
- if(TestAck()!=MT_DATA_ACK)
- {return 1;} //若回应的不是ACK信号则失败返回值1
- delay_nms(10);
- }
- Stop(); //停止I2C总线
- delay_nms(10); //延时等待BMP180写完
- return 0; //写入成功,返回值0
- }
- //====================IIC总线读n个字节(成功返回0,失败返回1)=========================
- unsigned char I2C_Read(unsigned char RomAddress,unsigned char *buf,unsigned char len)
- {
- unsigned char i;
- Start(); //启动I2C总线
- Wait(); //等待回应
- if(TestAck()!=START)
- return 1; //若回应的不是启动信号,则失败返回1
- Write8Bit(wr_device_add); //写I2C从器件地址、写方向
- Wait(); //等待回应
- if(TestAck()!=MT_SLA_ACK)
- return 1; //若回应的不是ACK信号,则失败返回值1
- Write8Bit(RomAddress); //写BMP180的ROM地址
- Wait(); //等待回应
- if(TestAck()!=MT_DATA_ACK)
- return 1; //若回应的不是ACK信号,则失败返回值1
- Start(); //重新启动I2C总线
- Wait(); //等待回应
- if(TestAck()!=RE_START)
- return 1; //若回应的不是重复启动信号,则失败返回1
- Write8Bit(rd_device_add); //写I2C从器件地址、读方向
- Wait(); //等待回应
- if(TestAck()!=MR_SLA_ACK)
- return 1; //若回应的不是ACK信号,则失败返回值1
- for(i=0;i<len;i++)
- {
- Twi(); //启动I2C读方式
- SetAck(); //设置接收自动应答
- delay_nms(10);
- Wait(); //等待回应
- delay_nms(10);
- *(buf+i)=TWDR; //把连续读取的len个字节数据依次存入对应的地址单元(数组)中
- }
- SetNoAck(); //读数据的最后一位后紧跟的是无应答
- delay_nms(10);
- Stop(); //停止I2C总线
- return 0; //成功返回值0
- }
- //================检测LCD忙信号子函数================
- void WaitForEnable(void)
- {
- unsigned char val;
- DataPort=0xff; //数据线电平拉高
- LCM_RS_0; //选择指令寄存器
- LCM_RW_1; //选择写方式
- __asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
- LCM_EN_1; //使能端拉高电平
- __asm("NOP");
- __asm("NOP"); //调用汇编指令延时两个空指令周期,等待稳定
- DDRA=0x00; //改变数据线方向成输入
- val=PINA; //读取数据
- while(val&Busy)
- val=PINA; //当DB7位为1时表示忙,循环检测
- LCM_EN_0; //忙信号结束,拉低使能端电平
- DDRA=0xff; //改变数据线方向成输出
- }
- //================写数据到LCD子函数=================
- void LcdWriteData(unsigned char dataW) //写数据dataW到LCD中
- {
- WaitForEnable(); //检测忙信号
- LCM_RS_1; //选择数据寄存器
- LCM_RW_0; //选择读方式
- __asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
- DataPort=dataW; //把显示数据送到数据线上
- __asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
- LCM_EN_1; //使能端拉高电平
- __asm("NOP");
- __asm("NOP"); //调用汇编指令延时两个空指令周期,等待稳定
- LCM_EN_0; //拉低使能端,执行写入动作
- }
- //================写命令到LCD子函数================
- void LcdWriteCommand(unsigned char CMD,unsigned char Attribc) //写命令CMD到LCD中,Arribc为1时检测忙信号,否则不检测
- {
- if(Attribc)
- WaitForEnable(); //检测忙信号
- LCM_RS_0; //选择指令寄存器
- LCM_RW_0; //选择写方式
- __asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
- DataPort=CMD; //把命令数据送到数据线上
- __asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
- LCM_EN_1; //使能端拉高电平
- __asm("NOP");
- __asm("NOP"); //调用汇编指令延时两个空指令周期,等待稳定
- LCM_EN_0; //拉低使能端,执行写入动作
- }
- //================显示光标定位子函数================
- void LocateXY(char posx,char posy) //定位位置到地址x列y行
- {
- unsigned char temp;
- temp=posx&0x0f; //屏蔽高4位,限定x坐标的范围为0~15
- posy&=0x01; //屏蔽高7位,限定y坐标的范围为0~1
- if(posy)
- temp|=0x40; //若要显示的是第二行,则地址码+0x40,因为第二行起始地址为0x40
- temp|=0x80; //指令码为地址码+0x80,因为写DDRAM时DB7恒为1(即0x80)
- LcdWriteCommand(temp,1); //把temp写入LCD中,检测忙信号
- }
- //===========显示指定座标的一个字符子函数============
- void DisplayOneChar(unsigned char x,unsigned char y,unsigned char Wdata) //在x列y行处显示变量Wdata中的一个字符
- {
- LocateXY(x,y); //定位要显示的位置
- LcdWriteData(Wdata); //将要显示的数据Wdata写入LCD
- }
- //==========显示指定座标的一串字符子函数===========
- void ePutstr(unsigned char x,unsigned char y,unsigned char const *ptr) //在x列y行处显示ptr指向的字符串
- {
- unsigned char i,j=0;
- while(ptr[j]>31)
- j++; //ptr[j]>31时为ASCII码,j累加,计算出字符串长度
- for(i=0;i<j;i++)
- {
- DisplayOneChar(x++,y,ptr[i]); //显示单个字符,同时x坐标递增
- if(x==16)
- {
- x=0;
- y^=1; //当每行显示超过16个字符时换行继续显示
- }
- }
- }
- //==================LCD初始化子函数==================
- void InitLcd(void)
- {
- LcdWriteCommand(0x38,0); //8位数据方式,双行显示,5X7字形,不检测忙信号
- delay_nms(5); //延时5ms
- LcdWriteCommand(0x38,0);
- delay_nms(5);
- LcdWriteCommand(0x38,0);
- delay_nms(5); //重复三次
- LcdWriteCommand(0x38,1); //8位数据方式,双行显示,5X7字形,检测忙信号
- LcdWriteCommand(0x08,1); //关闭显示,检测忙信号
- LcdWriteCommand(0x01,1); //清屏,检测忙信号
- LcdWriteCommand(0x06,1); //显示光标右移设置,检测忙信号
- LcdWriteCommand(0x0C,1); //打开显示,光标不显示,不闪烁,检测忙信号
- }
- //==================转换子函数====================
- void conversion(long temp_data)
- {
- shiwan=temp_data/100000+0x30 ;
- temp_data=temp_data%100000; //取余运算
- wan=temp_data/10000+0x30 ;
- temp_data=temp_data%10000; //取余运算
- qian=temp_data/1000+0x30 ;
- temp_data=temp_data%1000; //取余运算
- bai=temp_data/100+0x30 ;
- temp_data=temp_data%100; //取余运算
- shi=temp_data/10+0x30 ;
- temp_data=temp_data%10; //取余运算
- ge=temp_data+0x30;
- }
- //===================BMP180读温度=====================
- void BMP180ReadTemp(void)
- {
- unsigned char t=0x2e;
- I2C_Write(0xf4,&t,1); //向地址0xf4写0x2e,进行温度转换
- delay_nms(5); //延时大于4.5ms
- I2C_Read(0xf6,ReadTemp,2); //从地址0xf6开始读出温度数据并存到数组ReadTemp中,共2个字节
- }
- //===================BMP180读气压=====================
- void BMP180ReadPressure(void)
- {
- unsigned char t=0x34;
- I2C_Write(0xf4,&t,1); //向地址0xf4写0x34,进行第一次气压转换
- delay_nms(5); //延时大于4.5ms
- I2C_Read(0xf6,ReadPressure,2); //从地址0xf6开始读出气压数据并存到数组ReadPressure中,共2个字节
- }
- //==================初始化BMP180====================
- void Init_BMP180(void)
- {
- unsigned char temp[2];
- I2C_Read(0xaa,temp,2);
- ac1 = (temp[0]<<8)|temp[1];
- I2C_Read(0xac,temp,2);
- ac2 = (temp[0]<<8)|temp[1];
- I2C_Read(0xae,temp,2);
- ac3 = (temp[0]<<8)|temp[1];
- I2C_Read(0xb0,temp,2);
- ac4 = (temp[0]<<8)|temp[1];
- I2C_Read(0xb2,temp,2);
- ac5 = (temp[0]<<8)|temp[1];
- I2C_Read(0xb4,temp,2);
- ac6 = (temp[0]<<8)|temp[1];
- I2C_Read(0xb6,temp,2);
- b1 = (temp[0]<<8)|temp[1];
- I2C_Read(0xb8,temp,2);
- b2 = (temp[0]<<8)|temp[1];
- I2C_Read(0xba,temp,2);
- mb = (temp[0]<<8)|temp[1];
- I2C_Read(0xbc,temp,2);
- mc = (temp[0]<<8)|temp[1];
- I2C_Read(0xbe,temp,2);
- md = (temp[0]<<8)|temp[1]; //连续读取EEPROM中的校准数据,并存放到相应的变量中,以供后面补偿使用
- }
- //===================转换子函数=====================
- void BMP180Convert()
- {
- long ut,up,temperature,pressure; //定义长整型变量
- long x1, x2, b5, b6, x3, b3, p;
- unsigned long b4, b7; //定义无符号长整型变量
- BMP180ReadTemp(); //读取温度
- ut=ReadTemp[0]<<8|ReadTemp[1]; //合成温度数据
- x1=((long)ut-ac6)*ac5>>15; //以下根据EEPROM中的值对获取的温度数据的进行补偿换算
- x2=((long)mc<<11)/(x1+md);
- b5=x1+x2;
- temperature=(b5+8)>>4;
- conversion(temperature); //调用温度显示转换函数
- DisplayOneChar(5,0,bai); //显示温度十位
- DisplayOneChar(6,0,shi); //显示温度个位
- DisplayOneChar(8,0,ge); //显示温度小数后一位
- BMP180ReadPressure(); //读取气压
- up=ReadPressure[0]<<8|ReadPressure[1]; //合成气压数据
- up&=0x0000FFFF;
- b6=b5-4000; //以下根据EEPROM中的值对获取的气压数据的进行补偿换算
- x1=(b2*(b6*b6>>12))>>11;
- x2=ac2*b6>>11;
- x3=x1+x2;
- b3=(((long)ac1*4+x3)+2)/4;
- x1=ac3*b6>>13;
- x2=(b1*(b6*b6>>12))>>16;
- x3=((x1+x2)+2)>>2;
- b4=(ac4*(unsigned long)(x3+32768))>>15;
- b7=((unsigned long)up-b3)*(50000>>0);
- if(b7<0x80000000)
- p=(b7*2)/b4;
- else
- p=(b7/b4)*2;
- x1=(p>>8)*(p>>8);
- x1=(x1*3038)>>16;
- x2=(-7357*p)>>16;
- pressure=p+((x1+x2+3791)>>4);
- conversion(pressure); //调用气压显示转换函数
- DisplayOneChar(4,1,shiwan); //显示气压的百位
- DisplayOneChar(5,1,wan); //显示气压的十位
- DisplayOneChar(6,1,qian); //显示气压的个位
- DisplayOneChar(8,1,bai); //显示气压小数后一位
- DisplayOneChar(9,1,shi); //显示气压小数后二位
- }
- //==================主函数=====================
- void main(void)
- {
- delay_nms(400); //延时400ms等待电源稳定
- DDRA=0xff;PORTA=0x00;
- DDRB=0xff;PORTB=0x00;
- DDRC=0xff;PORTC=0xff;
- DDRD=0xff;PORTD=0xff; //初始化I/O口
- InitLcd(); //LCD初始化
- Init_BMP180(); //BMP180初始化
- ePutstr(0,0,str0); //显示温度
- delay_nms(10);
- DisplayOneChar(10,0,0xdf); //显示特殊符号
- delay_nms(10);
- ePutstr(0,1,str1); //显示气压
- while(1)
- {
- BMP180Convert(); //调用转换
- delay_nms(1000);
- }
- }
复制代码 上述程序中,是运用延时来等待数据转换完成的,并没使用EOC脚来检查转化是否完毕。同时,程序利用EEPROM中的值对获取的数据进行补偿换算的方法是直接来自BMP180的数据手册,若对换算代码有问题可参看其手册。气压数据采用了常规的16位数据,并没有扩展到19位。另外,由于在程序中大量使用了长整型数据格式,所以在IAR开发环境中编译时仍然要把编译优化选项改成Low或Medium(具体参见第一章),系统才能正常运行。若选择不优化(None)时,可能在液晶屏得不到任何显示。究其原因可能与IAR的编译环境有关,这一点要非常注意!!
把程序下载到单片机中,按要求接好连线,给系统上电,就可以在液晶屏上看到实时的温度和气压数据了
以上的Word格式文档51黑下载地址:
BMP180中文资料.doc
(166.5 KB, 下载次数: 59)
|