2014年5月11日 星期日

Arduino範例20:利用BMP085擷取大氣壓力 (換算成海拔高度)


今天要練習使用的東西是BMP085的大氣壓力量測晶片。

1. 首先介紹Sensor
BMP085是一種超低能耗的壓力感測器,採用的8-pin 陶瓷無引線晶片承載(LCC)超薄封裝,可以通過I2C匯流排直接與各種微處理器相連,因此我們接線時,除了供應電源&GND,再接上SDA和SCL兩個Pin腳,就可以把其數位的訊號帶進Arduino的板子裡了。其規格如下:

壓力範圍:30 ~ 110 kPa (海拔 9000 至-500公尺
電源電壓:1.8V ~ 3.6V
解析度為:6 Pa (0.5米)
含溫度輸出,同時也已做溫度補償
I²C介面

2. 特別說明一下I²C
I²C (Inter-Integrated Circuit)是一種串列通訊匯流排,使用多主從架構,由飛利浦公司在1980年代為了讓主機板、嵌入式系統用以連接低速週邊裝置而發展。I²C使用兩條雙向開放集極 (Open Drain),一條為資料線(SDA),另一條為時脈線(SCL)。I²C允許多主多僕系統,所以電氣上,SCL 和 SDA二線要透過高接電阻接於正電源。

I²C具有以下優點:
(1) 不需額外的解碼電路。
(2) 資料傳送的協定可以用軟體規劃,具有高度的彈性。
(3) 具有 I²C 匯流排無論從系統中移去或加入,都不會影響其他裝置的功能。
(4) 匯流排是兩線式的匯流排,除錯、維修變得很容易。

但是它速度不快,只適合當 IC 間溝通的橋樑。通常用在一些簡單的系統或者晶片組 IC 間溝通(命令傳遞,不是資料傳遞)的場合看到她的影子。

3. 實際接線
所以說明一下SDA與SCL之後,我們就是把這兩條線接到BMP085對應的SDA與SCL上,記得不同種類的Arduino板子,其SDA與SCL的Pin腳位置是不一樣的。接法如下圖:

4. Code
這個Code有點複雜,我後來還是直接找別人寫好的過來看 Base from bildr.blog (下面有參考資料的來源),我把我看懂之後的註解寫上去,也增加自己的理解。


-------------------------------------------------------------------------------
/*Based from bildr.blog
利用BMP085取得溫度&大氣壓力,並換算成高度
鮑率用9600,並用Serial.print顯示出來
 */
#include <Wire.h>
#define BMP085_ADDRESS 0x77     // I2C address of BMP085
const unsigned char OSS = 0;           // Oversampling Setting

// 定義校準值
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;
long b5; 

// bmp085GetTemperature(...)這個指令會先計算出b5這個變數
//然後b5又會被拿去用在bmp085GetPressure(...)校正壓力、高度
//所以Temperature(...)必須在Pressure(...)之前先執行

void setup(){
  Serial.begin(9600);
  Wire.begin();
  bmp085_Calibration();
}

void loop() {
  float temperature = bmp085GetTemperature(bmp085ReadUT());
  float pressure = bmp085GetPressure(bmp085ReadUP());
  float atm = pressure / 101325;
  float altitude = calcAltitude(pressure);
//Uncompensated caculation - in Meters 
//先叫出溫度的變數,再叫出氣壓變數,1atm = 101325 Pa
//最後再叫出高度的變數

  Serial.print("Temperature: ");
  Serial.print(temperature, 2);     //顯示小數位下兩位
  Serial.println("deg C");

  Serial.print("Pressure: ");
  Serial.print(pressure, 0);                  //0代表顯示整數
  Serial.println (" Pa");

  Serial.print("Standard Atmosphere: ");
  Serial.println(atm, 4);              //顯示小數位下四位

  Serial.print("Altitude: ");
  Serial.print(altitude, 2);            //顯示小數位下兩位
  Serial.println (" M");
  Serial.println ();                        //空白行
  delay(1000);                              //1000毫秒跑一次
}

//前面一開始就有呼叫一個子程式bmp085_Calibration()
//這是要用來校準溫度與壓力的程式

void bmp085_Calibration() {
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}

// 計算溫度
float bmp085GetTemperature(unsigned int ut){
  long x1, x2;
  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;
  float temp = ((b5 + 8)>>4);
  temp = temp /10;
  return temp;
}

// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up){
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;

  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;

  // Calculate B4
  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>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;

  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;

  long temp = p;
  return temp;
}

// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address) {
  unsigned char data;
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available());
  return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address) {
  unsigned char msb, lsb;
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2);
  msb = Wire.read();
  lsb = Wire.read();
  return (int) msb<<8 | lsb;
}

// Read the uncompensated temperature value
  unsigned int bmp085ReadUT() {
  unsigned int ut;

// Write 0x2E into Register 0xF4
// This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();

// Wait at least 4.5ms
  delay(5);

// Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Read the uncompensated pressure value
 unsigned long bmp085ReadUP(){
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;

// Write 0x34+(OSS<<6) into register 0xF4
// Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();

// Wait for conversion, delay time dependent on OSS
  delay(2 + (3<<OSS));

// Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  msb = bmp085Read(0xF6);
  lsb = bmp085Read(0xF7);
  xlsb = bmp085Read(0xF8);

  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
  return up;
}

void writeRegister(int deviceAddress, byte address, byte val) {
  Wire.beginTransmission(deviceAddress); // start transmission to device 
  Wire.write(address);               // send register address
  Wire.write(val);                    // send value to write
  Wire.endTransmission();         // end transmission
}

int readRegister(int deviceAddress, byte address){

  int v;
  Wire.beginTransmission(deviceAddress);
  Wire.write(address); // register to read
  Wire.endTransmission();
  Wire.requestFrom(deviceAddress, 1); // read a byte

  while(!Wire.available()) {
// waiting
  }

  v = Wire.read();
  return v;
}

float calcAltitude(float pressure){
  float A = pressure/101325;
  float B = 1/5.25588;
  float C = pow(A,B);
  C = 1 - C;
  C = C /0.0000225577;
  return C;
}
--------------------------------------------------------------------------------

5. 結果
最後跑出來的結果如下圖,總共出現4行,包括
(1) 溫度
(2) 大氣壓力Pa
(3) 大氣壓力atm (=Pa/101325)
(4) 海拔高度



參考資料

12 則留言:

  1. 請問要怎麼把測得的數據顯示在螢幕面板上?

    回覆刪除
    回覆
    1. 可以參考 http://ming-shian.blogspot.tw/2013/09/arduino-16x2-lcd.html
      改一下要顯示的data。

      刪除
  2. 請問可以麻煩你告訴我SDA PIN20 和SCL PIN21 在擴展板上面的位置嗎?我不知道該差在哪裡?感謝

    回覆刪除
    回覆
    1. 不好意思,我不知道您的shield是那一種,因為shield太多了,你可以直接google你的shield的接線圖,看那些pin是對應 SDA跟SCL的腳。
      可以查看 arduino 網站: http://www.arduino.cc/en/Main/Products

      刪除
  3. 我的板子是UNO R3的,請問要怎麼接?

    回覆刪除
  4. 我的板子是UNO R3的,請問在擴充版上該如何接那四條線?感恩

    回覆刪除
    回覆
    1. 先找出你的SDA跟SCL,就可以如圖上的接法了
      你可以上arduino的網站查,你的UNO中,SDA跟SCL到底是那隻腳。

      刪除
  5. 謝謝你的回覆,我已經利用擴充板接BMP085,並測量出結果,請問要怎樣修改CODE才能使結果顯示於LCD上?

    回覆刪除
  6. 請問,如果量測的氣壓超過規格書上寫的。修改library有可能達成嗎?

    回覆刪除
    回覆
    1. 超出規格量出來的應該都不是準的,以Sensor一般性質來說

      刪除
  7. 請問,可以提供您的line嗎? 想跟您私聊一下

    回覆刪除