M5Stackで始めるセンサ・インターフェーシング (3) 気圧センサDPS310を利用
台風が近づく季節になると、「気圧」が気になります。現在の単位はhPa(ヘクト・パスカル)です。Paの1/100になり、1000hPa付近が日常的で、台風が近づくと900hPa台に下がります。台風の気圧を直接測ることは頻繁にはできないので、気象衛星の撮影した雲の広がりの写真データから類推しています(ドボラック法)。
市販品の外側が真鍮でできた気圧計は置物としても貫禄があります。
気圧は、高度によっても変化します。高度(標高)は、東京湾の平均海面を基準にします。市販の針が動く気圧計では1hPa単位まで読み取れます。「高度が上がると気圧が減る」ので、同じ場所で高い建物や山に登ると、気圧が変化します。1hPaは約10mに相当するようです。絶対的な高度はGPSで得る(誤差数mから10mぐらい)のが一番簡単ですが、相対的な高度は気圧計でも得られます。
Infineonの気圧センサのDPS310は、300~1200hPaが測れます。実験に便利なボードに実装されたMikroElektonikaのPressure 3 clickボードを入手しました。電源電圧は3.3Vです。
分解能は高く±0.005hPa、±5cmです。階段の段差も記録できそうです。
●DPS310のおもなスペック
- 動作電圧 1.2~3.6V
- 動作範囲 300~1200hPa
- 消費電流 3uA
- 分解能 ±0.005hPa、±5cm
- 確度 ±1hPa、±8m
- 変換時間 3.6ms
- 動作時の温度 -40~85℃
- 温度の確度 ±0.5℃
- 温度の分解能 0.01℃
- インターフェース I2C(最大400kHz)とSPI
●接続
I2Cで接続します。I2CScannerを動かすと0x76に見つけました。
Pressure 3 click |
M5Stack |
---|---|
GND | GND |
+3.3V | 3.3V |
SCL | SCL |
SDA | SDA |
●ライブラリを利用
ライブラリ管理から、検索欄にDPS310を入れると、製造メーカInfineonのライブラリが見つかります。同様の分解能をもつオムロンの形2SMPB-02E MEMS絶対圧センサもM5StackのArduino IDE用サンプルも用意されています。
スケッチ例->DPS310->i2c_commandを読み込みます。デフォルトのスレーブ・アドレスが0x77なので、
Dps310PressureSensor.begin(Wire, 0x76);
と変更し、コンパイル、実行します。
気圧は、温度に依存するので、このセンサには温度センサが内蔵されています。Wireライブラリを使って、DPS310のレジスタを直接読み出して気圧と温度を得る事例は、
SpresenseでLチカから始める (27) Wireライブラリ 温度気圧DPS310
を参照ください。
●LCDディスプレイに表示
ライブラリを利用すると、有効桁数相当の数値しか戻ってきません。平均を取ったり、cm級の高度を得たいので、二桁細かい数値を得られるようにライブラリを変更します(戻り値に100を掛けただけ)。
ドキュメント->Arduino->libraries->DPS310->srcのDps310.cppの中の2か所を変更します。
1339行 return (int32_t)temp; -> return (int32_t)(temp * 100.0); 1361行 return (int32_t)prs; -> return (int32_t)(prs * 100.0); |
スケッチi2c_command.inoのほうでは100.0で除算します。温度(℃)は整数から小数点第2位まで、気圧(hPa)は小数点第2位から小数点第4位までが得られました。この桁数は確度をはるかに超えているので、いつも正しい値が読み出せているかは不明です。
気圧計によく似た外観をもつ時計のサンプルを読み込みます。
スケッチ例->M5Stack->Advanced->Display->TFT_Clock
このスケッチの秒針だけを残して、i2c_command.inoにコピーします。時刻の表示を気圧の数値に変更しながら、プロットします。秒針は0~360度を動くので、map関数を使って気圧の範囲に変換します。
#include <Dps310.h>
Dps310 Dps310PressureSensor = Dps310();
#include <M5Stack.h>
#define TFT_GREY 0x5AEB
float sx = 0, sy = 1;
float sdeg=0;
uint16_t osx=120, osy=120;
uint16_t x0=0, x1=0, yy0=0, yy1=0;
float Press;
void setup()
{
Serial.begin(9600);
while (!Serial);
//The parameter 0x76 is the bus address. The default address is 0x77 and does not need to be given.
Dps310PressureSensor.begin(Wire, 0x76);
Serial.println("Init complete!");
M5.begin();
M5.Lcd.fillScreen(TFT_GREY);
M5.Lcd.setTextColor(TFT_WHITE, TFT_GREY); // Adding a background colour erases previous text automatically
// Draw clock face
M5.Lcd.fillCircle(120, 120, 118, TFT_GREEN);
M5.Lcd.fillCircle(120, 120, 110, TFT_BLACK);
// Draw 12 lines
for(int i = 0; i<360; i+= 30) {
sx = cos((i-90)*0.0174532925);
sy = sin((i-90)*0.0174532925);
x0 = sx*114+120;
yy0 = sy*114+120;
x1 = sx*100+120;
yy1 = sy*100+120;
M5.Lcd.drawLine(x0, yy0, x1, yy1, TFT_BLUE);
}
// Draw 60 dots
for(int i = 0; i<360; i+= 6) {
sx = cos((i-90)*0.0174532925);
sy = sin((i-90)*0.0174532925);
x0 = sx*102+120;
yy0 = sy*102+120;
// Draw minute markers
M5.Lcd.drawPixel(x0, yy0, TFT_WHITE);
// Draw main quadrant dots
if (i==0) M5.Lcd.drawCentreString("1010", x0, yy0, 2);
if (i==30) M5.Lcd.drawCentreString("1020", x0, yy0, 2);
if (i==60) M5.Lcd.drawCentreString("1030", x0-7, yy0, 2);
if (i==90) M5.Lcd.drawCentreString("1040", x0-14, yy0-7, 2);
if (i==120) M5.Lcd.drawCentreString("1050", x0-9, yy0-9, 2);
if (i==240) M5.Lcd.drawCentreString("970", x0+10, yy0-9, 2);
if (i==270) M5.Lcd.drawCentreString("980", x0+7, yy0-7, 2);
if (i==300) M5.Lcd.drawCentreString("990", x0+7, yy0-5, 2);
if (i==330) M5.Lcd.drawCentreString("1000", x0+2, yy0, 2);
}
}
void loop()
{
int32_t temperature;
int32_t pressure;
int16_t oversampling = 7;
int16_t ret;
Serial.println();
ret = Dps310PressureSensor.measureTempOnce(temperature, oversampling);
if (ret != 0)
{
//Something went wrong.
//Look at the library code for more information about return codes
Serial.print("FAIL! ret = ");
Serial.println(ret);
}
else
{
Serial.print("Temperature: ");
Serial.println((float)temperature/100.0);
M5.Lcd.drawCentreString(" "+String((float)temperature/100.0)+" 'C ",120,260,4);
}
//Pressure measurement behaves like temperature measurement
//ret = Dps310PressureSensor.measurePressureOnce(pressure);
ret = Dps310PressureSensor.measurePressureOnce(pressure, oversampling);
if (ret != 0)
{
//Something went wrong.
//Look at the library code for more information about return codes
Serial.print("FAIL! ret = ");
Serial.println(ret);
}
else
{
Serial.print("Pressure: ");
Press = pressure/100.0/100.0;
Serial.print(Press,4);
Serial.println(" hPa");
M5.Lcd.drawCentreString(String(Press,4)+" hPa",120,189,4);
}
// sdeg = 30;
//Press = 1026;
if (Press>1010) {
sdeg = map(Press,1010,1060,0,150);
}
if (Press<1010) {
sdeg = map(Press,970,1010,240,360);
}
// M5.Lcd.drawCentreString(String(sdeg),120,130,2);
sx = cos((sdeg-90)*0.0174532925);
sy = sin((sdeg-90)*0.0174532925);
// Redraw new hand positions, hour and minute hands not erased here to avoid flicker
M5.Lcd.drawLine(osx, osy, 120, 121, TFT_BLACK);
osx = sx*90+121;
osy = sy*90+121;
M5.Lcd.drawLine(osx, osy, 120, 121, TFT_RED);
M5.Lcd.drawLine(osx, osy, 120, 121, TFT_RED);
M5.Lcd.fillCircle(120, 121, 8, TFT_RED);
delay(2000);
}
実行している様子です。この日は雲が出ていました。
高度を計算する式を、delay(2000);の前に追加しました。
height = (( pow ( (1013.25 / Press), ( 1.0 / 5.257 )) - 1.0 ) * ( Temp + 273.15) ) / 0.0065;
M5.Lcd.drawCentreString(String(height) + "m",120,130,2);
実行している様子です。この日は晴天でした。
マイナスの値が出ました。海面の数値1013.25が地域や気圧によって異なるので、絶対的な標高を求めるには何らかの方法で補正しないといけないです。
いくつかの修正をしたスケッチです。
#include <Dps310.h>
Dps310 Dps310PressureSensor = Dps310();
#include <M5Stack.h>
#define TFT_GREY 0x5AEB
float sx = 0, sy = 1;
float sdeg = 0;
uint16_t osx = 120, osy = 120;
uint16_t x0 = 0, x1 = 0, yy0 = 0, yy1 = 0;
float Press, Temp;
float height;
void setup(){
Serial.begin(9600);
while (!Serial);
//The parameter 0x76 is the bus address. The default address is 0x77 and does not need to be given.
Dps310PressureSensor.begin(Wire, 0x76);
Serial.println("Init complete!");
M5.begin();
M5.Lcd.fillScreen(TFT_GREY);
M5.Lcd.setTextColor(TFT_WHITE, TFT_GREY); // Adding a background colour erases previous text automatically
// Draw clock face
M5.Lcd.fillCircle(120, 120, 118, TFT_GREEN);
M5.Lcd.fillCircle(120, 120, 110, TFT_BLACK);
// Draw 12 lines
for (int i = 0; i<360; i+= 30) {
sx = cos((i-90) * 0.0174532925);
sy = sin((i-90) * 0.0174532925);
x0 = sx * 114 + 120;
yy0 = sy * 114 + 120;
x1 = sx * 100 + 120;
yy1 = sy * 100 + 120;
M5.Lcd.drawLine(x0, yy0, x1, yy1, TFT_BLUE);
}
// Draw 60 dots
for (int i = 0; i<360; i+= 6) {
sx = cos((i-90) * 0.0174532925);
sy = sin((i-90) * 0.0174532925);
x0 = sx * 102 + 120;
yy0 = sy * 102 + 120;
// Draw minute markers
M5.Lcd.drawPixel(x0, yy0, TFT_WHITE);
// Draw main quadrant dots
if (i == 0) M5.Lcd.drawCentreString("1010", x0, yy0, 2);
if (i == 30) M5.Lcd.drawCentreString("1020", x0, yy0, 2);
if (i == 60) M5.Lcd.drawCentreString("1030", x0-7, yy0, 2);
if (i == 90) M5.Lcd.drawCentreString("1040", x0-14, yy0-7, 2);
if (i == 120) M5.Lcd.drawCentreString("1050", x0-9, yy0-9, 2);
if (i == 240) M5.Lcd.drawCentreString("970", x0+10, yy0-9, 2);
if (i == 270) M5.Lcd.drawCentreString("980", x0+7, yy0-7, 2);
if (i == 300) M5.Lcd.drawCentreString("990", x0+7, yy0-5, 2);
if (i == 330) M5.Lcd.drawCentreString("1000", x0+2, yy0, 2);
}
}
void loop(){
int32_t temperature;
int32_t pressure;
int16_t oversampling = 7;
int16_t ret;
Serial.println();
ret = Dps310PressureSensor.measureTempOnce(temperature, oversampling);
if (ret != 0){
Serial.print("FAIL! ret = ");
Serial.println(ret);
}else{
Serial.print("Temperature: ");
Temp = (float)temperature / 100.0;
Serial.println(Temp);
M5.Lcd.drawCentreString(" "+String(Temp)+" 'C ", 120, 260, 4);
}
ret = Dps310PressureSensor.measurePressureOnce(pressure, oversampling);
if (ret != 0){
Serial.print("FAIL! ret = ");
Serial.println(ret);
}else{
Serial.print("Pressure: ");
Press = pressure / 100.0 / 100.0;
Serial.print(Press, 4);
Serial.println(" hPa");
M5.Lcd.drawCentreString(String(Press,4)+" hPa", 120, 189, 4);
if (Press >= 1010) {
sdeg = map(Press, 1010, 1060, 0, 150);
}
if (Press < 1010) {
sdeg = map(Press, 970, 1010, 240, 360);
}
sx = cos((sdeg-90) * 0.0174532925);
sy = sin((sdeg-90) * 0.0174532925);
// Redraw new hand positions, hour and minute hands not erased here to avoid flicker
M5.Lcd.drawLine(osx, osy, 120, 121, TFT_BLACK);
osx = sx * 90 + 121;
osy = sy * 90 + 121;
M5.Lcd.drawLine(osx, osy, 120, 121, TFT_RED);
M5.Lcd.drawLine(osx, osy, 120, 121, TFT_RED);
M5.Lcd.fillCircle(120, 121, 8, TFT_RED);
height = (( pow ( (1013.25 / Press), ( 1.0 / 5.257 )) - 1.0 ) * ( Temp + 273.15) ) / 0.0065;
M5.Lcd.drawCentreString(String(height) + "m", 120, 130, 2);
}
delay(2000);
}
●使ってみると
このままUSBケーブルからM5Stackを抜き、廊下に出ました。階段の2階から1段ずつ下がりその数値を記録しました。
1段は約21cmです。グラフの縦のメモリは10cm間隔です。
赤色は近似直線です。測定値はばらつくので、平均を取るようにすれば、測定開始時点でゼロ・リセットしてから高度を測ると、高い精度(cmもしくは10cm)で高さの偏移を測れるように思えます。
(※)現在の開発環境では、LCDディスプレイへ文字を出すと、シリアルモニタの文字が文字化けしています。
●コラム LCDディスプレイのライブラリ
文書化されていないライブラリのコマンドが、Adavanceのサンプルに見つかります。今回利用した時計の描画もそうです。少し整理します。
●任意の色の定義
#define TFT_GREY 0x5AEB
M5.Lcd.fillScreen(TFT_GREY);色コードは、Redが5ビット+Greenが6ビット+Blueが5ビットの16ビットで構成されているようです。
サンプル;
0xBDF7 Light Gray
0x7BEF Dark Gray
0xFFE0 Yellow
0xFBE0 Orange
0x79E0 Brown
0x7FF Cyan
0xF81F Pink●円
M5.Lcd.fillCircle(中心X座標, 中心Y座標, 半径, 色); ●文字印字
M5.Lcd.drawCentreString(文字列, X座標, Y座標, 大きさ); 大きさは2、4、6などの偶数のようです。文字の背景はfillScreen()した色です。