ESP32-WROOM-32EのWireでSTH35の情報を取得

組み込みエンジニア

こんにちは、ENGかぴです。

Arduino環境ではWireライブラリを使うと簡単にI2C通信が実現できます。SHT35-DISは温湿度センサーで 測定レンジが-40℃から+125℃と広いのが特徴です。Wireでセンサー情報を取得しする方法をまとめました。

ESP32-WROOM-32Eの開発環境はArduino IDEを使用しています。またESP32-WROOM-32E開発ボード(秋月電子)及びAE-SHT35(秋月電子)を使用しています。

SHT35-DISの情報を取得する

SHT35-DISはSENSIRION社の高精度温湿度センサーであり通信方式はI2CであるためWireライブラリを使用します。ESP32-WROOM-32Eから測定コマンド及びリードを送信することでSHT35-DISからデータを取得できます。

Wire(I2C通信)を実装する

#include <Wire.h>

void setup() {
  Wire.begin();

  //Wire.begin(SDA, SCL, 10000); 任意のピンを使用する場合
}

最初に「Wire.h」をインクルードする必要があります。Wire.begin()でI2Cの条件を初期化してスタートします。引数にSDA、SCLに指定したいピン及び通信速度を指定します。引数を指定しない場合の初期値はシルク印刷番号ではSDAが21番、SCLが22番、通信速度は100kHzになります。

任意のピンでのソフトウェアWireでも動作しますが動作が安定しないことが多いためI2C機能を搭載しているピンを使用することをお勧めします。

Wireによるデータ送信の例

ArduinoのWireを組み込んで送信する例を示します。

void Sht35Measure(uint8_t dev_id,uint8_t* cmd, uint16_t len){

  Wire.beginTransmission(dev_id); //スレーブが存在するか確認
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    Wire.beginTransmission(dev_id);
    for( uint16_t i=0; i < len; i++ ){
      Wire.write(*cmd); //lenサイズ分だけデータを書き込む
      ++cmd;
    }
    Wire.endTransmission(); //ストップ・コンディションの発行
  }
  else{
    //スレーブが存在しない場合の処理
  }
}

最初にスレーブが存在するかを確認するためにbeginTransmisson()の引数にスレーブアドレスを指定しています。endTransmisson()でスレーブアドレスが送出され戻り値が0であれば存在するアドレスとなります。スレーブが存在しない場合の処理を作らない場合は特に確認する必要はありません。

スレーブが存在する場合はbeginTransmisson()でスレーブアドレスを指定します。write()で送信するサイズ分書き込みを行います。write()は送信するデータを一時的に保管するキューイングとなります。

endTransmission() でスレーブアドレスを含めてキューイングしたデータをスレーブに送信します。

Wireによるデータ受信の例

ArduinoのWireを組み込んでデータ受信する例を示します。

bool Sht35GetData(uint8_t dev_id,uint8_t* reg_data, uint16_t len){
  bool  ret = false;

  Wire.beginTransmission(dev_id); //スタート・コンディションの発行
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    if( Wire.requestFrom(dev_id, len) == len ){
      for( uint16_t i=0; i < len; i++ ){
        *reg_data = Wire.read(); //len分だけデータをリードする
        ++reg_data;
      }
      ret = true;
    }
  }
  else{
    //スレーブが存在しない場合の処理
  }
  return ret;
}

送信時と同様に初めにスレーブが存在を確認します。スレーブが存在しない場合の処理がない場合は特に必要ありません。

スレーブが存在する場合は requestFrom ()を使用してスレーブアドレスに対して指定するバイト数を要求します。第1引数にスレーブアドレスを指定し、第2引数に要求するバイト数を指定します。戻り値はスレーブから読み込んだバイト数となります。

要求したデータ数が準備できていることを確認しrequestFrom()で要求したデータをread()を使用して読み込み変数に格納します。

requestFrom()でデータを要求した場合はendTransmission()の発行は必要ありません。

SHT35-DISの測定とデータ取得

SHT35-DISの計測をシングルショットモード( クロックストレッチを有効 )で行いデータの取得する方法を説明します。取得したデータの健全性を確認するCRC8の計算方法について説明します。

コマンドを送信する

引用:SHT3x-DISデータシート(シングルショットモード)
引用:SHT3x-DISデータシート(シングルショットモード)

SHT35-DISはコマンドを16ビット長になるように送信(Write)する必要があります。シングルショットモード(繰り返し精度レベル:高)をクロックストレッチを有効にする場合は0x2c06がコマンドになります。

クロックストレッチを有効にすると測定中にSCLの信号レベルがLOWに維持されるためSCLがHighに復帰するタイミングで測定が完了したことが確認できます。

Wireによるデータ送信の例で説明したSht35Measure()を使用して測定を開始します。

#define SLAVE_ADRS 0x45 //ADRオープン時のアドレス
uint8_t singleshot[2] = { 0x2C, 0x06}; //シングルショットモードでクロックストレッチ有効
//使用例
Sht35Measure(SLAVE_ADRS, &singleshot[0], sizeof(singleshot));

Sht35Measure ()の第1引数にスレーブのアドレス、第2引数に送信する値を格納しているアドレス、第3引数に送信するサイズを指定します。

測定データを取得する

Wireによるデータ受信の例で説明したSht35GetData()を使用してデータを取得します。

uint8_t rxdata[6];
//使用例
Sht35GetData(SLAVE_ADRS, &rxdata[0], sizeof(rxdata));

Sht35GetData ()の第1引数にスレーブのアドレス、第2引数に受信した測定データを格納するアドレス、第3引数に受信したいデータサイズを指定します。

受信データは温度データ16ビットと温度データのCRC、湿度データ16ビットと湿度データのCRCの合計6バイトを受信します。受信した測定データのCRCの計算結果と取得したCRCの結果を比較して一致したとき有効なデータとして採用します。CRC計算の条件がデータシートに記載されています。

引用:SHT3x-DISデータシート(CRCの条件)
引用:SHT3x-DISデータシート(CRCの条件)
uint8_t Crc8Calc(uint8_t *data, uint8_t sz ){
  uint8_t crc = 0xFF;
  uint8_t i,j;
    
  for( i = 0; i < sz; i++){
    crc ^= *data;
        
    for( j = 0; j < 8; j++ ){
      if( crc & 0x80 ){
        crc = ( crc << 1 ) ^ POLYNOMIAL;
      }
      else{
        crc = crc << 1;
      }
    }
    ++data;
  }
  return crc;
}

CRC-8の計算方法は初期値を0xFFとして1バイト目のバイトデータのXORを計算します。この値を8ビット分左にシフトしていき最上位ビットが立った時にPolynomialとXORをとり2バイト目の初期値とします。2バイト目も同様にしてシフトとXORを繰り返していき対象のデータのバイト数だけ繰り返します。

測定データの換算

SHT35-DISから受信した測定データは16ビットの符号なしの整数型の数値ですが温湿度に換算する必要があります。測定データの換算方法はデータシートに記載されています。

引用:SHT3x-DISデータシート(温湿度データの換算)
引用:SHT3x-DISデータシート(温湿度データの換算)

受信したデータは1バイトずつ取得しているため2バイトデータに置き換えた後換算を行います。小数点以下が出るためfloatを使用します。

uint16_t tempHex;
uint16_t humiHex;
float temp;
float humi;

tempHex = ((uint16_t)rxdata[0] << 8) | rxdata[1]; //2バイトデータに変換
humiHex =((uint16_t)rxdata[3] << 8) | rxdata[4]; //2バイトデータに変換
temp = (tempHex / 65535.00) * 175 - 45; //温度に換算
humi = (humiHex / 65535.0) * 100.0; //湿度に換算

動作確認

動作確認用の回路図(I2C通信)
動作確認用の回路図(I2C通信)

ESP32-WROOM-32EとSHT35-DISの配線例を示しています。回路図の番号はESP32-WROOM-32Eの左上を1ピンとした時反時計回りにピンを数えた場合の番号としています。ピン番号横の()内の番号はシルク印刷されているピンの名称です。

SHT35モジュールの2ピンのADRをオープンで使用するとアドレスが0x45になりGNDで使用すると0x44になります。今回はオープンで使用しています。

電源ONしてから1秒間隔でSHT35-DISの測定とデータの取得を行います。シリアルモニタの表示を確認するとデータが取得できていることが分かりました。

SHT35-DISのデータをWireで取得した場合の結果
SHT35-DISのデータをWireで取得した場合の結果

温度(Temperature)は部屋に置いている温度計によると24.0℃であり、湿度(Humidity)についてはスマホのデータでは66%でした。設置する環境にも左右されますがWireでI2C通信を行いデータが取得できていることが分かります。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include <Wire.h>

#define SLAVE_ADRS 0x45
#define POLYNOMIAL 0x31
#define PIN_DI  22
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIME_SHT35_MAX 100 //SHT35の計測タイマ  
#define TIME_OUT_MAX 20  //SHT35の通信タイムアウト

typedef enum{
    SHT35_MEASURE = 0,
    SHT35_WAIT,
    SHT35_READ,       
    SHT35_MAX  
}SHT35_MODE;

/* 変数宣言 */
SHT35_MODE mode = SHT35_MEASURE;
uint32_t beforetimCnt = millis();
float temp;
float humi;
int16_t timSht35start;
int16_t  timSht35Out = TIME_OFF;
uint8_t singleshot[2] = { 0x2C, 0x06};
uint8_t chksum[2];
uint8_t rxdata[6];

/* プロトタイプ宣言 */
void mainApp(void);
void Sht35Measure(uint8_t dev_id,uint8_t* cmd, uint16_t len);
bool Sht35GetData(uint8_t dev_id,uint8_t* reg_data, uint16_t len);
uint8_t Crc8Calc(uint8_t *data, uint8_t sz );

void setup() {

  Serial.begin(115200);
  Wire.begin();
}

void loop() {

  mainTimer();
  mainApp();
}
/* タイマ管理 */
void mainTimer(void){

  if ( millis() - beforetimCnt > BASE_CNT ){
    beforetimCnt = millis();

    if( timSht35start > TIME_UP ){
      --timSht35start;
    }
    if( timSht35Out > TIME_UP ){
      --timSht35Out;
    }
  }
}
/* メイン処理関数 */
void mainApp(void){
    
  switch(mode){
    case SHT35_MEASURE:
      if( timSht35start == TIME_UP ){
        timSht35start = TIME_OFF;
        timSht35Out = TIME_OUT_MAX;
        Sht35Measure(SLAVE_ADRS, &singleshot[0], sizeof(singleshot));
        mode = SHT35_WAIT;
      }
      break;
    case SHT35_WAIT:
      if( digitalRead(PIN_DI) == 1){
        mode = SHT35_READ;
      }
      break;
    case SHT35_READ:   
      if( Sht35GetData(SLAVE_ADRS, &rxdata[0], sizeof(rxdata))){
        uint16_t tempHex;
        uint16_t humiHex;

        chksum[0] = Crc8Calc(&rxdata[0],2); //tempのCRCチェック
        chksum[1] = Crc8Calc(&rxdata[3],2); //humiのCRCチェック
            
        if( chksum[0] == rxdata[2] && chksum[1] == rxdata[5]){
          tempHex = ((uint16_t)rxdata[0] << 8) | rxdata[1];
          humiHex =((uint16_t)rxdata[3] << 8) | rxdata[4];
          temp = (tempHex / 65535.00) * 175 - 45;
          humi = (humiHex / 65535.0) * 100.0;

          Serial.print("temp: ");
          Serial.print(temp);
          Serial.print("℃  ");
          Serial.print("humi :");
          Serial.print(humi);
          Serial.print("%  ");
          Serial.println();
        }
            
        timSht35start = TIME_SHT35_MAX;
        timSht35Out = TIME_OFF;
        mode = SHT35_MEASURE;
       }
      break;
    }
    
    if( timSht35Out == TIME_UP ){
        timSht35Out = TIME_OFF;
        mode = SHT35_MEASURE;
        timSht35start = TIME_SHT35_MAX;
    }
}
/* SHT35へコマンド送出 */
void Sht35Measure(uint8_t dev_id,uint8_t* cmd, uint16_t len){

  Wire.beginTransmission(dev_id); //スレーブが存在するか確認
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    Wire.beginTransmission(dev_id);
    for( uint16_t i=0; i < len; i++ ){
      Wire.write(*cmd); //lenサイズ分だけデータを書き込む
      ++cmd;
    }
    Wire.endTransmission(); //ストップ・コンディションの発行
  }
}
/* SHT35からデータを取得 */
bool Sht35GetData(uint8_t dev_id,uint8_t* reg_data, uint16_t len){
  bool  ret = false;

  Wire.beginTransmission(dev_id); //スタート・コンディションの発行
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    if( Wire.requestFrom(dev_id, len) == len ){
      for( uint16_t i=0; i < len; i++ ){
        *reg_data = Wire.read(); //len分だけデータをリードする
        ++reg_data;
      }
      ret = true;
    }
  }
  return ret;
}
/* CRC8計算関数 */
uint8_t Crc8Calc(uint8_t *data, uint8_t sz ){
  uint8_t crc = 0xFF;
  uint8_t i,j;
    
  for( i = 0; i < sz; i++){
    crc ^= *data;
        
    for( j = 0; j < 8; j++ ){
      if( crc & 0x80 ){
        crc = ( crc << 1 ) ^ POLYNOMIAL;
      }
      else{
        crc = crc << 1;
      }
    }
    ++data;
  }
  return crc;
}

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

あなたの市場価値を見いだす転職サイト【ミイダス】無料会員登録

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました