YOGYUI

DFRobot BMP180 Barometer Sensor 본문

Hardware/Sensor

DFRobot BMP180 Barometer Sensor

요겨 2021. 2. 2. 17:50
반응형

DFRobot BMP180 Barometer 모듈

1. Hardware

독일 Bosch사(오...대기업...)에서 제작한 BMP180 기압계(barometer)가 장착된 모듈

DFRobot 공식 소개 페이지: SKU:TOY0058(단종되었다...)

Specification, 출처: DFRobot

I2C 시리얼 인터페이스로 통신하며, 0.12hPa/m 고정밀 기압 측정 및 온도와 고도(altitude) 측정도 가능하다

중급 IoT 학습에서 배우는 IMU 센서에 왠만하면 모두 들어있는 기능들이라 요즘은 barometer만 따로 사용하는 경우는 거의 없는 것 같다 (드론 제어 시에 꼭 필요한 기능들이다보니...)

 

BMP180 데이터시트를 보면 기압 및 온도 측정 알고리즘과 고도 환산 공식이 잘 기재되어 있다

기압/온도 측정 알고리즘, 출처: BMP180 데이터시트
고도 환산 공식, 출처: BMP180 데이터시트

IoT 입문자들뿐만 아니라 산업현장에서도 널리 쓰이고 있는 센서

2. Prototyping

알고리즘을 번거롭게 구현할 필요없이 Adafruit에서 개발한 Adafruit BMP085 Library를 활용하자

Adafruit BMP085 Library

소스코드를 보면 복잡한 수식을 잘 구현해두었다

 

프로토타이핑에는 Arduino Micro를 사용

전원은 +5V, GND에 연결하고 I2C의 SDA은 GPIO2, SCL은 GPIO3에 연결

배선 Schematic

#include <Adafruit_BMP085.h>

Adafruit_BMP085 sensor;
bool sensor_init = false;

void setup() {
  Serial.begin(115200);
}

void loop() {
  if (!sensor_init) {
    Serial.println("Sensor is not initialized");
    sensor_init = sensor.begin();
  } else {
    float temperature = sensor.readTemperature();
    Serial.print("Temperature: ");
    Serial.println(temperature);
    
    int32_t pressure = sensor.readPressure();
    Serial.print("Pressure: ");
    Serial.println(pressure);
    
    int32_t sealevel_pressure = sensor.readSealevelPressure();
    Serial.print("Pressure at Sealevel: ");
    Serial.println(sealevel_pressure);
    
    float altitude = sensor.readAltitude();
    Serial.print("Altitude: ");
    Serial.println(altitude);
    Serial.print("\n\n");
  }
  delay(1000);
}

시리얼 모니터를 열어서 통신 결과 확인

Temperature: 22.10
Pressure: 100723
Pressure at Sealevel: 100710
Altitude: 51.41


Temperature: 22.10
Pressure: 100712
Pressure at Sealevel: 100712
Altitude: 51.41


Temperature: 22.10
Pressure: 100711
Pressure at Sealevel: 100713
Altitude: 50.83

현재 온도와 기압 측정값 및 이를 토대로 환산한 해면 기압(Sea-level Pressure) 및 고도(Altitude) 계산값이 디스플레이된다

※ 압력은 단위는 Pa(파스칼), 고도의 단위는 meter

 

공식에 따르면 해면 기압과 고도는 상관관계가 있으며, 다음과 같이 서로의 값으로 치환될 수 있다

int32_t Adafruit_BMP085::readSealevelPressure(float altitude_meters /*=0*/) {
  float pressure = readPressure();
  return (int32_t)(pressure / pow(1.0 - altitude_meters / 44330, 5.255));
}

float Adafruit_BMP085::readAltitude(float sealevelPressure /*=101325*/) {
  float altitude;
  float pressure = readPressure();
  altitude = 44330 * (1.0 - pow(pressure / sealevelPressure, 0.1903));
  return altitude;
}
  • 해면기압 계산 시 고도를 입력받게 되어 있으며, default값은 0m이다

  • 고도 계산 시 해면 기압을 입력받게 되어 있으며, default값은 1013.25hPa (평소에 말하는 '1기압' = 760mmHg)이다

즉, 측정 위치에서의 고도 계산을 위해서는 해면 기압이 필요하며 관련 공식은 다음과 같다

출처: 위키피디아

해면 기압은 기상 상태에 따라 변화하는데, 이는 다음과 같이 기상청 날씨누리에서 확인할 수 있다

(국내에서 활용시...)

기상청 날씨누리 지상관측자료

측정 지점의 위치에 따라 해면 기압이 다르므로 이를 활용하면 된다

 

드론 등의 이동 디바이스에서 고도 측정 시, 해면 기압은 환경의 영향을 많이 받으므로 정확도에 영향을 미칠 수 있으므로 지자기 센서(지구 자기장 측정, 보통 3축)를 사용해서 고도를 측정하는게 일반적이다

3. Advanced

ESP8266 모듈을 활용해 MQTT를 통해 센서 측정지점의 해면 기압을 업데이트해 고도를 측정하는 코드를 만들어보자

 

웹서버는 측정된 온도, 기압 및 고도값을 받으며, ESP8266이 일정 주기로 해면 기압 정보를 요청하면 기상청으로부터 해면 기압을 가져와 (웹크롤링) ESP8266으로 넘겨주도록 한다 (웹크롤링 관련 링크)

 

웹 서버 코드 (Python, Flask)

import json
import requests
import pandas as pd
from bs4 import BeautifulSoup
import paho.mqtt.client as mqtt
from flask import Flask, render_template, jsonify

class SensorData:
    temperature: float = 0
    pressure: float = 0
    altitude: float = 0
    sealevel_pressure: float = 0

def getWeatherData() -> pd.DataFrame:
    url = "https://www.weather.go.kr/weather/observation/currentweather.jsp"
    html = requests.get(url).text
    soup = BeautifulSoup(html, "html.parser")
    table = soup("table", "table_develop3")[0]

    table_rows = table.find_all("tr")
    table_data = table_rows[2:]
    table_data_elements = [x.find_all("td") for x in table_data]
    data_lst = []
    for elem in table_data_elements:
        if len(elem) > 0:
            data_lst.append([x.text for x in elem])
    df = pd.DataFrame(data_lst)
    df.columns = ['지점', '일기', '시정km', '운량', '중하운량', '현재기온', '이슬점온도', '체감온도',
                  '일강수', '적설', '습도', '풍향', '풍속', '해면기압']
    return df

def publish_weather(location: str):
    df = getWeatherData()
    obj = {
        "sealevel_pressure": df[df['지점'] == location]['해면기압'].values[0]
    }
    mqtt_client.publish("esp8266_test/weather", json.dumps(obj), 1)

data = SensorData()
weather_df: pd.DataFrame
app = Flask(__name__)
mqtt_client = mqtt.Client()
mqtt_client.username_pw_set(
    username="MQTT Broker ID",
    password="MQTT Broker Password"
)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/update', methods=['POST'])
def update():
    return jsonify({
        'temperature': data.temperature,
        'pressure': data.pressure,
        'altitude': data.altitude,
        'sealevel_pressure': data.sealevel_pressure,
    })

def on_mqtt_connect(client, userdata, flags, rc):
    mqtt_client.subscribe('esp8266_test/sensor')
    mqtt_client.subscribe('esp8266_test/weather/get')

def on_mqtt_message(client, userdata, message):
    print(f'Message: {message.topic}, {message.payload}')
    if message.topic == 'esp8266_test/sensor':
        msg_dict = json.loads(message.payload.decode("utf-8"))
        data.temperature = msg_dict['temperature']
        data.pressure = msg_dict['pressure']
        data.altitude = msg_dict['altitude']
        data.sealevel_pressure = msg_dict['sealevel_pressure']
    elif message.topic == 'esp8266_test/weather/get':
        publish_weather('수원')

def on_mqtt_publish(client, userdata, mid):
    print(f'Publish: {userdata}')

mqtt_client.on_message = on_mqtt_message
mqtt_client.on_publish = on_mqtt_publish
mqtt_client.on_connect = on_mqtt_connect

if __name__ == '__main__':
    mqtt_client.connect('MQTT Broker 주소', 1883)
    mqtt_client.loop_start()
    app.run(host='127.0.0.1', port=9999, debug=True)
    mqtt_client.loop_stop()
    mqtt_client.disconnect()
<!-- index.html -->
<!DOCTYPE html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
    <a>Temperature</a>
    <h1 id="temperature"></h1>
    <a>Pressure (Pa)</a>
    <h1 id="pressure"></h1>
    <a>Altitude (m)</a>
    <h1 id="altitude"></h1>
    <a>Sea-Level Pressure (Pa)</a>
    <h1 id="sealevel_pressure"></h1>
    <script> setInterval(function(){$.ajax({
        url: '/update',
        type: 'POST',
        success: function(response) {
            console.log(response);
            $("#temperature").html(response["temperature"]);
            $("#pressure").html(response["pressure"]);
            $("#altitude").html(response["altitude"]);
            $("#sealevel_pressure").html(response["sealevel_pressure"]);
        },
        error: function(error) {
            console.log(error);
        }
    })}, 2000);
    </script>
</body>

ESP8266 구현 코드

MQTT Client 구현을 위해 PubSubClient 라이브러리 사용

JSON 핸들링을 위해 ArduinoJson 라이브러리 사용

#include <Adafruit_BMP085.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

Adafruit_BMP085 sensor;
bool sensor_init = false;
float sealevel_pressure = 101325;

char publish_msg[256];
StaticJsonDocument<256> json_doc;
long last_acq_time = 0;
long last_acq_time2 = 0;

const char* WIFI_SSID = "당신의 WiFi SSID";
const char* WIFI_PW = "당신의 WiFi Password";
const char* MQTT_BROKER_ADDR = "당신의 MQTT Broker 주소";
const int   MQTT_BROKER_PORT = 1883;
const char* MQTT_ID = "당신의 MQTT BrokerID"; // optional
const char* MQTT_PW = "당신의 MQTT Broker Password";  // optional

WiFiClient wifi_client;
PubSubClient mqtt_client(wifi_client);

void mqtt_callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  if (!strcmp(topic, "esp8266_test/weather")) {
    DynamicJsonDocument doc_temp(256);
    deserializeJson(doc_temp, payload);

    sealevel_pressure = float(doc_temp["sealevel_pressure"]) * 100;
  }
}

void setup() {
  Serial.begin(115200);

  WiFi.begin(WIFI_SSID, WIFI_PW);
  Serial.print("WiFi Connecting");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());
  Serial.printf("MAC address = %s\n", WiFi.softAPmacAddress().c_str());

  // setup MQTT Client
  mqtt_client.setServer(MQTT_BROKER_ADDR, MQTT_BROKER_PORT);
  mqtt_client.setCallback(mqtt_callback);
}

void establish_mqtt_connection() {
  if (mqtt_client.connected())
    return;
  while (!mqtt_client.connected()) {
    Serial.println("Try to connect MQTT Broker");
    if (mqtt_client.connect("ESP8266_Client", MQTT_ID, MQTT_PW)) {
      Serial.println("Connected");
      mqtt_client.subscribe("esp8266_test/weather");
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqtt_client.state());
      delay(2000);
    }
  }
}

void loop() {
  establish_mqtt_connection();
  mqtt_client.loop();
  
  if (!sensor_init) {
    Serial.println("Sensor is not initialized");
    sensor_init = sensor.begin();
  } else {
    long current = millis();
    if (current - last_acq_time > 2000) {
      last_acq_time = current;
      json_doc["temperature"] = sensor.readTemperature();;
      json_doc["pressure"] = sensor.readPressure();
      json_doc["altitude"] = sensor.readAltitude(sealevel_pressure);
      json_doc["sealevel_pressure"] = sealevel_pressure;
      size_t n = serializeJson(json_doc, publish_msg);
      mqtt_client.publish("esp8266_test/sensor", publish_msg, n);
      Serial.print("Published (esp8266_test/sensor): ");
      Serial.println(publish_msg);
    }
    
    if (current - last_acq_time2 > 10000) {
      last_acq_time2 = current;
      mqtt_client.publish("esp8266_test/weather/get", "", 0);
      Serial.println("Published (esp8266_test/weather/get): ");
    }
  }
}

 

<주요 기능 요약>

  • ESP8266이 2초에 한번 측정 데이터를 전송(토픽: esp8266_test/sensor)

  • ESP8266이 10초에 한번 기후 데이터 전송 요청(토픽: esp8266_test/weather/get)

  • 웹서버는 전송 요청에 대한 응답 전송(토픽: esp8266_test/weather)

웹페이지 접속 결과

아두이노 시리얼 로그를 보면 최초 해면기압은 1기압(1013.25hPa)이었다가 웹서버에 요청 후 변수값이 바뀐 것을 확인할 수 있다

WiFi Connecting.......
Connected, IP address: XXX.XXX.X.XX
MAC address = 62:01:94:02:A2:D9
Try to connect MQTT Broker
Connected
Sensor is not initialized
Published (esp8266_test/sensor): {"temperature":24.7,"pressure":100964,"altitude":29.84867,"sealevel_pressure":101325}
Published (esp8266_test/sensor): {"temperature":24.7,"pressure":100963,"altitude":29.93237,"sealevel_pressure":101325}
Published (esp8266_test/sensor): {"temperature":24.7,"pressure":100964,"altitude":29.34794,"sealevel_pressure":101325}
Published (esp8266_test/weather/get)
Message arrived [esp8266_test/weather] {"sealevel_pressure": "1023.9"}
Published (esp8266_test/sensor): {"temperature":24.8,"pressure":100964,"altitude":118.4907,"sealevel_pressure":102390}
Published (esp8266_test/sensor): {"temperature":24.8,"pressure":100966,"altitude":118.0742,"sealevel_pressure":102390}

 

테스트코드에서는 웹서버가 현재 위치를 '수원'으로 고정해서 기상청으로부터 데이터를 얻는데, 추후에 ESP8266에 GPS 등을 추가해서 현재 위도와 경도를 도시 이름으로 치환하여 웹서버에 요청 시 함께 보내도록 수정할 수 있다

 

나중에 시간내서 GPS, IMU 센서랑 Fusion해보도록 하자

끝~

반응형
Comments