M5Stamp pico でADC(温度センサ)とI2C(高精度気圧センサDSP310)をつかう

前回、サンプルコードを書き込み、Lチカをトライしました。

M5Stamp PICO を買ってみた

今回は、Groveセンサでよく使われるADCとI2C入力の方法を調べてみます。開発環境はArduino IDEです。上の記事で紹介したライブラリがインストールされている前提ではなしをすすめます。

今回使用するセンサ二つ。左がアナログ、右がI2C。

M5Stamp PICO のスペック

項 目内 容
ControllerESP-PICO-D4
Flash4MB
入力電圧5V@500mA
IOG0,G1,G3,G26,G36,G18,G19,G21,G22,G25,G32,G33
消費電流5V@0.35-84mA

Pinout図

pinoutは下図です。ADCの入力 G32, G33, G36にあります。I2Cのデフォルトは、G21とG22です。

Datasheet (ESP32-PICO-D4)

esp32-pico-d4_datasheet_en.pdf (espressif.com)

ADCの使い方

温度計はサーミスタなので、抵抗値の温度変化特性によって、温度を推定します。下のサンプルコードはその為の変換式がかかれていますので、複雑になってしまっていますが、基本的には、pinMode(ピン番号, input) として、analogRead(ピン番号)で信号を読むだけ。

超簡単です。

ちょうどハイライト部分です。ESP32のADCの分解能は、12bitなので、analogReadの結果は0~4095のdigit値です。

#include <math.h>

const int B = 4275;            // B value of the thermistor
const int R0 = 100000;         // R0 = 100k

void setup() {
  Serial.begin(115200);
  pinMode(32,INPUT);
}
void loop() {
  int value;
  value = analogRead(33);
  float R = 4095.0/value-1.0;
  R = R0*R;
  float temperature = 1.0/(log(R/R0)/B+1/298.15)-273.15; 

  Serial.print("temperature = ");
  Serial.println(temperature);

  delay(1000);
}

下のようにSerial monitor上にセンサ値を表示すると、それっぽい値がでていることが確認できます。

これだけ。

次はI2C

使った気圧センサはInfineonのDPS310です。高精度な気圧計として、有名ですね。センサ精度が±0.002hPaで、高度にすると±0.02mを分解できるポテンシャルだそうです。ほんと?

ちなみにDPS310の評価ボードはInfineonの純正ボードの他にGroveやAdafuritから出されています。

Grove DPS310

Adafruit DPS310

<Wire.h>を活用する

まず、I2CはWire.hというライブラリで簡単に通信できます。まず、ボードに気圧センサをつないで、アドレスを検索してみます。

この時のスケッチは以下のとおり。

#include <Arduino.h>
#include <Wire.h>

int device_add;
int DevSearch(){
  
  int address;
  int error;
  Serial.printf("\nscanning Address [HEX]\n");
  for(address = 1; address < 127; address++ )
  {
    Wire.beginTransmission(address);
    error = Wire.endTransmission(); 
    Serial.print(".");
    if(error==0)
      return address;
  }
      return 0;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Wire.begin(21, 22);
  delay(3000);
  int device_add = DevSearch();
  if (0!=device_add)
    {
      Serial.println("");
      Serial.print("Device was found ! address is 0x");
      Serial.println(device_add,HEX);
    }
  else
    {
      Serial.println("");
      Serial.println("Device was not found !");
    }
}
void loop() {
}

下が実行結果、アドレス0x77で気圧センサのモジュールが正しく認識されています。

実際の値の読み方は、アナログ信号よりも面倒です。が、大体のセンサは、ライブラリが公開されているので、それを使うとはやいです。今回の圧力センサDPS310だと、Adafruitからライブラリが公開されてました。

AdafruitのDPS310ライブラリ

自分の手持ちは、Groveの評価ボードでしたが、このライブラリでも問題なく計測できました。

下が、ライブラリ内のサンプルコードdps310_simpletestの実行結果になります。

I2Cの信号を自力でとる

ただ、ライブラリに頼りきりだと、応用がきかなかったり、ない場合は困っちゃうので、自力で値とってみます。そのためには、DPS310のデータシートを確認します。

DPS310データシート

測定の流れ

DPS310は、データシートによれば、下のような手順で、測定していくようです。

  1. 圧力のキャリブレ係数 c00, c10, c20, c30, c01, c21の値をレジスタから読む
  2. スケーリングファクタKpを下のテーブルからオーバーサンプリングの設定に応じて、選ぶ
※オーバーサンプリングとは、通常のADで必要な周波数の数倍はやくサンプリングする処理のこと。ノイズとかエイリアシングの影響をうけずらくなる。

3.信号値をレジスタから読む

4. 下の式を使って、生値をスケーリングする

5. 次に下の変換式を使って、圧力(Pa)を求める

レジスタの設定方法 (Wire.write)

手順2のスケーリングファクタを求めるためには、まず、サンプリング設定する必要があります。ここではPRS_CFGの設定を例に説明します。

データシートの中に、下画像のように気圧センサの測定条件を設定するレジスタPRS_CFGの説明があります。ここでは、測定レートを8Hz, オーバーサンプリングレートを8倍に設定します。

実際に各項目で設定を決めた後は、青字のようにビットシフトをして、指定のビットに値がおさまるようにします。

実際のコードでは、下のようにしました。まず、見た目わかりやすいようにenumをつかいます。組み込みコードではこういうのがよくでてきます。レジスタ値そのものだと全く意味不明なので、ミスも防げます。

// PM_RATEの設定
typedef enum { 
  DPS310_1HZ,   ///< 1 Hz =000
  DPS310_2HZ,   ///< 2 Hz =001
  DPS310_4HZ,   ///< 4 Hz =010
  DPS310_8HZ,   ///< 8 Hz =011
  DPS310_16HZ,  ///< 16 Hz 
  DPS310_32HZ,  ///< 32 Hz
  DPS310_64HZ,  ///< 64 Hz
  DPS310_128HZ, ///< 128 Hz
} dps310_rate_t;

// PM_PRCの設定
typedef enum {
  DPS310_1SAMPLE,    ///< 1 Hz =000
  DPS310_2SAMPLES,   ///< 2 Hz =001
  DPS310_4SAMPLES,   ///< 4 Hz =010
  DPS310_8SAMPLES,   ///< 8 Hz =011
  DPS310_16SAMPLES,  ///< 16 Hz
  DPS310_32SAMPLES,  ///< 32 Hz
  DPS310_64SAMPLES,  ///< 64 Hz
  DPS310_128SAMPLES, ///< 128 Hz
} dps310_oversample_t;

実際の書き込みの方法は下のようにします。アドレスを書き込んだ後に、値を書いてます。

  uint8_t temp = (((uint8_t)DPS310_8HZ)<<4U)|((uint8_t)DPS310_8SAMPLES);
  Wire.beginTransmission(0x77);//0x77はデバイスアドレス
  Wire.write(0x6); //0x6はレジスタアドレス
  Wire.write(temp);//tempは設定値
  Wire.endTransmission(); 

レジスタの値を読み込む方法は下のような感じです。writeメソッドで読み込みたい先頭アドレスを指定して、そのあとrequestFromでデータ長を指定して、readで読みます。

  Wire.beginTransmission(0x77);
  Wire.write(0x6);
  Wire.endTransmission();
  Wire.requestFrom(0x77,1,true);

  uint8_t temp = Wire.read(); //値をget

温度センサも上と同じように設定した後に、次はMode and Statusレジスタを設定します。モードはリセット直後にアイドル状態(000)となっているため、今回は、気圧、温度を取りにいく前に、(001)か(010)を書き込んで、明示的に値をとりにいきます。

例のごとく、enumでレジスタ値を定義しときます。

typedef enum {
  DPS310_IDLE = 0b000,            //Stopped/idle
  DPS310_ONE_PRESSURE = 0b001,    //Take single pressure measurement
  DPS310_ONE_TEMPERATURE = 0b010, //Take single temperature measurement
  DPS310_CONT_PRESSURE = 0b101,   //Continuous pressure measurements
  DPS310_CONT_TEMP = 0b110,       //Continuous pressure measurements
  DPS310_CONT_PRESTEMP = 0b111,   //Continuous temp+pressure measurements
} dps310_mode_t;

コードは下のとおりです。

  uint8_t temp = 1U; //気圧をとる場合
  Wire.beginTransmission(0x77);//0x77はデバイスアドレス
  Wire.write(0x8); //0x8はレジスタアドレス
  Wire.write(temp);//tempは設定値
  Wire.endTransmission(); 

複数バイトの一気読み

手順1のキャリブレ値は、0x10~0x21のレジスタに格納されてます。

係数用の配列をつくって、一気に読みにいきたいです。writeメソッドで、係数の先頭アドレス0x10を書き込み、requestFromメソッドでデータ長18バイトを指定すればバッファに値がたまり、readメソッドで値を順に読み込めそう。

  uint8_t coeffs[18];

  Wire.beginTransmission(0x77);
  Wire.write(0x10);
  Wire.endTransmission();
  Wire.requestFrom(0x77,18,true);

  for (uint8_t i = 0; i < 18; i++) {
    coeffs[i] = Wire.read();
    String s = "Coeff[" + String(i) + "] =";
    Serial.print(s);
    Serial.println(coeffs[i]);
  }

下が結果です。とれてそうです。

あとは、これらの値を変換式に代入するだけです。

2の補数の計算

ゲットしたCoeffから、ためしにC20の計算をしてみます。係数はマイナス値も取りうるため、そこらへんの考慮が必要です。

0x1Cのデータをval_msb, 0x1Dのデータをval_lsbとすると、下のように2バイトデータに変換します。

((uint16_t)val_msb<<8U)|((uint16_t)val_lsb); //2バイトデータにする

この値は、マイナスをとりうるらしいので、最上位の符号ビットに1がたつと、値をビット反転して、1足して、マイナス値に換算します。(2の補数+1)

色々やり方がありますが、

val_out = val - (1U<<bits);

でも、

val_out = -(~val + 1);

でもOKです。実際は下のように書きました。

  uint8_t bits=16; //データサイズ
  int16_t val_out; 
  
 uint8_t val_msb = coeffs[12];
  uint8_t val_lsb = coeffs[13];
 
  uint16_t val = ((uint16_t)val_msb<<8U)|((uint16_t)val_lsb);
  
  if ((val) & ((uint16_t)1 << (bits - 1))) {
    Serial.println("符号ビットが1");
    val_out = val - (1U<<bits);
  }
  else{
    val_out = val;
  }
  Serial.print("val_out = ");  
  Serial.println(val_out);

こんな感じで係数を換算していきます。

最終的なコード

大体、ポイントとなるところはこれまでの説明で網羅できました。最終的なコードは下のとおり。一部、簡単のため、関数化してたりしますが、基本やってることは同じです。

#include <Arduino.h>
#include <Wire.h>

#define DPS310_I2CADDR_DEFAULT (0x77) ///< Default breakout addres

#define DPS310_PRSB2 0x00       ///< Highest byte of pressure data
#define DPS310_TMPB2 0x03       ///< Highest byte of temperature data
#define DPS310_PRSCFG 0x06      ///< Pressure configuration
#define DPS310_TMPCFG 0x07      ///< Temperature configuration
#define DPS310_MEASCFG 0x08     ///< Sensor configuration
#define DPS310_CFGREG 0x09      ///< Interrupt/FIFO configuration
#define DPS310_COEFF  0x10      ///< Start address of sensor coefficient
#define DPS310_RESET 0x0C       ///< Soft reset
#define DPS310_PRODREVID 0x0D   ///< Register that contains the part ID
#define DPS310_TMPCOEFSRCE 0x28 ///< Temperature calibration src

#define KP_8TIMES     7864320
#define KT_8TIMES     7864320
/** The measurement rate ranges */
typedef enum {
  DPS310_1HZ,   ///< 1 Hz
  DPS310_2HZ,   ///< 2 Hz
  DPS310_4HZ,   ///< 4 Hz
  DPS310_8HZ,   ///< 8 Hz
  DPS310_16HZ,  ///< 16 Hz
  DPS310_32HZ,  ///< 32 Hz
  DPS310_64HZ,  ///< 64 Hz
  DPS310_128HZ, ///< 128 Hz
} dps310_rate_t;

/** The  oversample rate ranges */
typedef enum {
  DPS310_1SAMPLE,    ///< 1 Hz
  DPS310_2SAMPLES,   ///< 2 Hz
  DPS310_4SAMPLES,   ///< 4 Hz
  DPS310_8SAMPLES,   ///< 8 Hz
  DPS310_16SAMPLES,  ///< 16 Hz
  DPS310_32SAMPLES,  ///< 32 Hz
  DPS310_64SAMPLES,  ///< 64 Hz
  DPS310_128SAMPLES, ///< 128 Hz
} dps310_oversample_t;

/** The  oversample rate ranges */
typedef enum {
  DPS310_IDLE = 0b000,            ///< Stopped/idle
  DPS310_ONE_PRESSURE = 0b001,    ///< Take single pressure measurement
  DPS310_ONE_TEMPERATURE = 0b010, ///< Take single temperature measurement
  DPS310_CONT_PRESSURE = 0b101,   ///< Continuous pressure measurements
  DPS310_CONT_TEMP = 0b110,       ///< Continuous pressure measurements
  DPS310_CONT_PRESTEMP = 0b111,   ///< Continuous temp+pressure measurements
} dps310_mode_t;

uint8_t coeffs[18];
uint8_t buf[3];
int16_t c0,c1,c01,c11,c20,c21,c30;
int32_t c00,c10;
int32_t PRS_raw,TMP_raw;
float TMP,PRS;

//2の補数計算(マイナス値を算出する)
int32_t two_complement(uint32_t val,uint8_t bits)
{
  int32_t val_out;
  
  if ((val) & ((uint32_t)1 << (bits - 1))) {
    val_out = val - (1U<<bits);
  }
  else{
    val_out = val;
  }
  return val_out;
}

//係数を読み込む関数
void GetCoeff(uint8_t *coeffs)
{
  Wire.beginTransmission(DPS310_I2CADDR_DEFAULT);
  Wire.write(DPS310_COEFF);
  Wire.endTransmission();
  Wire.requestFrom(DPS310_I2CADDR_DEFAULT,18,true);
  
  for (uint8_t i = 0; i < 18; i++) {
    coeffs[i] = Wire.read();
    String s = "Coeff[" + String(i) + "] =";
  }
  c0 = two_complement(((uint16_t)coeffs[0] << 4) | (((uint16_t)coeffs[1] >> 4) & 0x0F), 12);
  c1 = two_complement((((uint16_t)coeffs[1] & 0x0F) << 8) | coeffs[2], 12);
  c00 = two_complement(((uint32_t)coeffs[3] << 12) | ((uint32_t)coeffs[4] << 4) |
        (((uint32_t)coeffs[5] >> 4) & 0x0F),20);  
  c10 = two_complement((((uint32_t)coeffs[5] & 0x0F) << 16) | ((uint32_t)coeffs[6] << 8) |
         (uint32_t)coeffs[7],20);
  c11 = two_complement(((uint16_t)coeffs[10] << 8) | (uint16_t)coeffs[11], 16);        
  c01 = two_complement(((uint16_t)coeffs[8] << 8) | (uint16_t)coeffs[9], 16);         
  c20 = two_complement(((uint16_t)coeffs[12] << 8) | (uint16_t)coeffs[13], 16);
  c21 = two_complement(((uint16_t)coeffs[14] << 8) | (uint16_t)coeffs[15], 16);  
  c30 = two_complement(((uint16_t)coeffs[16] << 8) | (uint16_t)coeffs[17], 16);
/*  
  Serial.println("--- coefficient was loaded ---");
  Serial.print("c0 =");  Serial.println(c0);
  Serial.print("c1 =");  Serial.println(c1);  
  Serial.print("c00 ="); Serial.println(c00);    
  Serial.print("c10 ="); Serial.println(c10);
  Serial.print("c01 ="); Serial.println(c01);
  Serial.print("c11 ="); Serial.println(c11);           
  Serial.print("c20 ="); Serial.println(c20);
  Serial.print("c21 ="); Serial.println(c21);  
  Serial.print("c30 ="); Serial.println(c30);
  Serial.println("------------------------------");
*/
}
//指定のアドレスに値を書き込む関数
void WriteReg(uint8_t addrs, uint8_t val)
{
  Wire.beginTransmission(DPS310_I2CADDR_DEFAULT);
  Wire.write(addrs);
  Wire.write(val);
  Wire.endTransmission(); 
}
//指定のアドレスから値を読む関数 lengthで読み込むバイト数を指定する
void ReadReg(uint8_t addrs,uint8_t * buf,uint8_t length)
{
  Wire.beginTransmission(DPS310_I2CADDR_DEFAULT);
  Wire.write(addrs);
  Wire.endTransmission();
  Wire.requestFrom(DPS310_I2CADDR_DEFAULT,length,true);

  for(uint8_t i =0;i<length;i++)
  {
  buf[i] = Wire.read();
  }
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Wire.begin(21, 22);
  delay(3000);

  uint8_t temp = (((uint8_t)DPS310_8HZ)<<4U)|((uint8_t)DPS310_8SAMPLES);
  //Serial.println(temp);
  //圧力センササンプリング設定
  WriteReg(DPS310_PRSCFG,temp);
  //設定値確認  
  ReadReg(DPS310_PRSCFG,buf,1);
//  Serial.print("PRSCFG = 0X");
//  Serial.println(buf[0],HEX);
  //温度センササンプリング設定
  WriteReg(DPS310_TMPCFG,temp);
  //設定値確認    
  ReadReg(DPS310_TMPCFG,buf,1);  
//  Serial.print("TMPCFG = 0X");
//  Serial.println(buf[0],HEX);
  
  //計算に必要な係数をロードする
  GetCoeff(coeffs);

}
void loop() {
  //温度のセンシング指示
  WriteReg(DPS310_MEASCFG,0x02); 
  //温度データを3バイト分読む
  ReadReg(DPS310_TMPB2,buf,3);
  TMP_raw = two_complement(((uint32_t)buf[0])<<16|((uint32_t)buf[1])<<8|((uint32_t)buf[2]),24);
  float TMP_raw_scale = (float)TMP_raw / KT_8TIMES;
  TMP = TMP_raw_scale * c1 + c0 / 2.0;
  //結果表示
  //Serial.print("TMP[degC] ="); Serial.println(TMP);
  //気圧のセンシング指示
  WriteReg(DPS310_MEASCFG,0x01); 
  ReadReg(DPS310_PRSB2,buf,3);
  PRS_raw = two_complement(((uint32_t)buf[0])<<16|((uint32_t)buf[1])<<8|((uint32_t)buf[2]),24);
  float PRS_raw_scale = (float)PRS_raw / KP_8TIMES;
  PRS =(int32_t)c00+PRS_raw_scale * ((int32_t)c10 + PRS_raw_scale * ((int32_t)c20 + PRS_raw_scale * (int32_t)c30)) +
        TMP_raw_scale *((int32_t)c01 + PRS_raw_scale * ((int32_t)c11 + PRS_raw_scale * (int32_t)c21));
  //結果表示
  //Serial.print("PRS[kPa] ="); Serial.println(PRS/1000); 
  Serial.print("$"); Serial.println(PRS/1000); 
  //Serial.println();
  delay(1000);
}

下が実行結果。

おしまい

説明は、以上です。M5StampPicoの説明なのかDSP310の説明なのか、よく分からなくなってしまいましたが、頑張ってまとめてみました。

そんでわ happy DSP310 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です