Seeeduino XIAOのWireでDHT20の温湿度を取得

組み込みエンジニア
本記事はプロモーションが含まれています。

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

Seeeduino XIAOはArduinoと互換性があるためWireライブラリが使用できます。温湿度センサーであるDHT20モジュールから温湿度の情報を取得しシリアルモニターに表示して動作確認を行います。

DHT20モジュールはGrove Temperature&Humidity Sensor(DHT20)(Seeed Studio)を使用しています。またSeeeduino XAIO用Groveシールド(Seeed Studio)を使用しています。

Seeeduino XIAOで動作確認したことについてリンクをまとめています。

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

Wireライブラリの使い方

DHT20はASAIR製の湿度と温度のセンサーを備えたモジュールです。DHT20モジュールの通信方式はI2CなのでWireライブラリを使用することでセンサーのデータを取得することができます。

広告

Wireライブラリの初期化

#include <Wire.h>

void setup() {
  Wire.begin();
  //Wire.setClock(400000);
}

最初に「Wire.h」をインクルードする必要があります。begin()関数でI2Cの条件を初期化してスタートします。Seeeduino XIAOのWireはSDAが4ピン、SCLが5ピンになります。通信速度のデフォルトは100kHzですが、変更する場合はsetClock()関数で変更するボーレートを指定します。

ボーレートを高速にしすぎるとプルアップ抵抗やボード上の配線長によっては通信データが鈍ってしまい通信不良の要因となるため注意が必要です。

データ送信の例

/* DHT20へコマンド送出 */
uint8_t Dht20SetData(uint8_t* cmd, uint8_t len){
  uint8_t ret;

  Wire.beginTransmission(SLAVE_ADRS);
  for( uint8_t i=0; i < len; i++ ){
    Wire.write(*cmd); //lenサイズ分だけデータを書き込む
    ++cmd;
  }
  ret = Wire.endTransmission(); //ストップ・コンディションの発行
}

Wireでデータ送信する例を示します。beginTransmisson()関数でスレーブアドレスを指定し初期化を行います。

write()関数で送信するデータをセット(一時保存のキューイング)を行います。引数に送信するデータを指定します。例では引数のポインタアドレスの値を*で参照しポインタを更新しながらlenサイズ分データをセットしています。

endTransmission()関数でスレーブアドレスからセットしたデータを送信します。endTransmisson()関数の戻り値を確認することで送信の状況を確認することができます。送信が成功した場合は戻り値が0になります。

PR:技術系の通信教育講座ならJTEX

データ受信の例

/* DHT20からデータを取得 */
bool Dht20GetData(uint8_t reg_adr, uint8_t* reg_data, uint8_t len){
  bool  ret = false;

  if( reg_adr != NULL ){//レジスタを指定して読み込み
    Wire.beginTransmission(SLAVE_ADRS); //スタート・コンディションの発行
    Wire.write(reg_adr); //書き込む対象のアドレスをセット(ライトで指定)
    Wire.endTransmission(); //ストップ・コンディションの発行
  }

  if( Wire.requestFrom(SLAVE_ADRS, len) == len ){
    for( uint8_t i=0; i < len; i++ ){
      *reg_data = Wire.read(); //len分だけデータをリードする
      ++reg_data;
    }
    ret = true;
  }
  return ret;
}

Wireでデータ受信する例を示します。最初にレジスタを指定して読み込む場合とスレーブアドレスを読み込む場合を引数のreg_adrによって処理を分岐しています。

レジスタを指定して読み込む場合はレジスタアドレスを書き込んで指定するため送信と同じ処理を行います。スレーブに対してレジスタを指定した状態にして受信の処理を行います。

requestFrom()関数でデータを取得します。第1引数にスレーブのアドレスを指定し、第2引数に読み込むデータのサイズを指定します。

requestFrom()関数の戻り値は読み込んだデータ数になるため指定したサイズと一致したことを確認し、一時保存したデータをread()関数で読み込んで変数に格納します。

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

スポンサーリンク

DHT20モジュールからデータを取得する

DHT20モジュールは電圧範囲がDC2.2V~5.5Vと幅広いことや湿度と温度のデータが20ビットデータで高精度で測定できることが特徴です。データシートに記載されている手順に従ってDHT20モジュールから湿度と温度のデータを取得する手順について説明します。

DHT20モジュールのデータ取得の手順
  • 手順1
    センサーの状態確認(初期化時)

    電源ONから100ms経過後、0x71を送信してセンサーの状態を確認する。
    0x18であれば手順3に進む0x18以外であれば0x1B、0x1C、0x1Eレジスタを初期化する。

  • 手順2
    レジスタの初期化(初期化時)

    Seeed Studioに公開されているソースコードで確認した0xbaコマンドでレジスタを初期化して手順3に進む。

  • 手順3
    測定開始コマンドの送信

    10ms経過後0xACコマンドを送信する。0xACコマンドに続けて0x33、0x00を送信する。

  • 手順4
    測定待ちと測定状態の確認

    測定に80ms必要なので待機しますが、測定完了はスレーブアドレスから1バイト目のstateの8ビット(bit7)で確認する。bit7が0であれば測定完了なので手順5に進む。

  • 手順5
    CRCの計算

    stateを含めた湿度と温度データまでの6ビットに対してCRC計算を行い最後尾に付与されたCRCと一致するかをチェックする。一致したら手順6に進む。

  • 手順6
    湿度と温度のデータを換算する

    取得したデータを20ビットのデータで整理して換算式を使って湿度と温度のデータに換算する。再度測定する場合は手順3に戻る

頻繁なデータ測定は計算に伴う消費電流の増加など内部で発生した熱の影響を受けるため最低でも2秒おきに測定データを取得することが推奨されています。

手順2ですが、データシートにはウェブサイトで初期化ルーチンを確認となっていますが見当たりませんでした。

電源ONして測定を行った後で電源をOFFせずにリスタートすると0x71レジスタの値が0x18以外になります。電源をOFFして数秒経過してから0x71レジスタを確認すると0x18になるため、確認せずにデータ取得の手順に進んでも問題ないと考えています。

以下ではコマンドの送信例と手順に沿ったデータの取得方法の例を説明します。また取得したデータの健全性を確認するCRC8の計算方法について説明します。

広告

測定をモードで管理する

  switch(mode){
    case DHT20_MEASURE: //手順3
      //測定開始トリガの送信
      mode = DHT20_WAIT;
      break;
    case DHT20_WAIT: //手順4
      //stateのbit[7]の状態を確認する
   //bit[7]が0であればデータを読み込む
      mode = DHT20_CHK;
      break;
    case DHT20_CHK: //手順5,6
      //CRCをチェックして一致するとデータを換算する
      Datachk(); 
      mode = DHT20_MEASURE;
      break;
  }

DHT20モジュールのデータ取得の手順の手順3から手順6をモードで管理します。

  1. DHT20_MEASURE(手順3)
  2. DHT20_WAIT(手順4)
  3. DHT20_CHK(手順5、手順6)

1.DHT20_MEASUREは測定開始トリガを送信し、次のモードに進みます。

2.DHT_WAITはstateのbit[7]ビットが0であれば測定が完了しているためデータを取得して次のモードに進みます。

3.DHT20_CHKは取得したデータのCRCを計算して受信したCRCと一致するか確認します。CRCが健全であればデータを受け入れて湿度と温度に換算します。

測定開始トリガを送信する

uint8_t trig[] = { 0xAC, 0x33, 0x00};

Dht20SetData( &trig[0], sizeof(trig));

測定開始コマンドは0xACを送信しますが、続けてコマンドパラメータとして0x33と0x00を送信する必要があります。測定開始コマンド用の配列を準備してデータ送信の例で説明したDht20SetData()関数に配列のアドレスとサイズを引数に指定しています。測定が完了するまで80ms待つ必要があります。

広告

CRCチェックを行う

データシートに記載されているCRC8のチェックの多項式は以下の通りです。

CRC[7:0] = 1+X4+X5+X8

この多項式から標準値を求めます。X=2として計算するとCRCは0x131になります。CRC8はLSBから8ビットが対象となるためPolynomial(多項式)標準値は0x31になります。またデータシートにCRCの初期値に0xFFが指定されています。

#define POLYNOMIAL 0x31

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の計算方法は初期値と1バイト目のバイトデータのXORを計算します。この値を8ビット分左にシフトしていきますが、最上位ビットが立った時に1ビット分シフトしてからPolynomialとXORをとります。

2バイト目以降は前のバイトの結果を初期値として8ビット分シフトとXORを繰り返していき対象のデータのバイト数だけ繰り返します。

測定データの最後尾にstateデータから温度データまでの6バイトデータに対するCRCが付加されています。付加されたCRCと受信したデータからCRC計算した結果が一致した場合に有効なデータとして採用します。

広告

測定データの換算

引用:Data Sheet DHT20 Read temperature and humidity data
引用:Data Sheet DHT20 Read temperature and humidity data

DHT20のデータシートを確認するとstateデータに続けて湿度データが20bit、温度データが20bit配置されています。湿度と温度データを換算するために20bitのデータを4byteデータに構成し直します。

読み込みデータを4byteデータに構成に変換
読み込みデータを4byteデータに構成に変換

湿度データについて説明します。Humidity-1は20bitデータのMSBを含むデータなのでbit0から左に12回シフトして加算します。Humidty-2はbit4からbit11を構成するデータなのでbit0から左に4回シフトして加算します。Humidty-3はreadData[3]の上位4bitなので右に4回シフトして加算します。

温度データについて説明します。温度を構成するTemperature-1は20bitデータのMSBを含むデータですがreadData[3]の下位4bitを使用するため0x0Fで論理積をとったデータを使用します。Temperature-1はbit16からbit19までを構成するデータなのでbit0から左に16回シフトして加算します。Temperature-2はbit8からbit15を構成するデータなのでbit0から左に8回シフトして加算します。Temperature-3はそのまま加算します。

uint32_t humid;
uint32_t temp;

humid = ( readData[1] << 12 ) + ( readData[2] << 4 )
      + ( readData[3] >> 4 ) ;

 temp = ( (readData[3] & 0x0F) << 16 ) + ( readData[4] << 8 )
      + ( readData[5] );

4byteデータへの置き換えの例を示しています。humidとtempは次の換算式におけるSRHとSTになります。

引用:Data Sheet DHT20 Signal Conversion
引用:Data Sheet DHT20 Signal Conversion

データシートによると上記の式によって換算すると湿度と温度の値になります。

float tempdata;
float humiddata;

humiddata = ((float)humid / (1 << 20)) * 100; //湿度
tempdata = ((float)temp / (1 << 20))*200 - 50; //温度

4byteデータに構成した湿度と温度のデータを換算します。小数点以下を表示するためfloatの型にキャストしてから220(1を左に20回シフト)で割っています。

スポンサーリンク

動作確認

動作確認用の回路図
動作確認用の回路図

Seeeduino XIAO用GroveシールドにSeeeduino XIAOを挿入します。SDA-4、SCL-5のシルク印刷されているGrove端子にDHT20モジュール(Grove)を接続します。Wireを使用するためプルアップ抵抗が必要ですがDHT20モジュールに実装されているため不要です。

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

シリアルモニタの結果
シリアルモニタの結果

温度(Temperature)は部屋に置いている温湿度計によると24.4℃、湿度(Humidity)は59.8%でした。結果がほぼ一致しているため湿度と温度が取得できていることが確認できました。

広告

ソースコード全体

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

#include <Wire.h>

#define SLAVE_ADRS 0x38
#define STATUS_CHK 0x71
#define POLYNOMIAL 0x31
#define RESET_REG_ADDR 0xba

#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIME_DHT20_WAIT 10 //測定ウェイト
#define TIME_DHT20_MAX 300 //測定ウェイト
#define TIME_OUT_MAX 500  //通信タイムアウト

typedef enum{
  DHT20_MEASURE = 0,
  DHT20_WAIT,
  DHT20_CHK,  
  DHT20_MAX  
}DHT20_MODE;

typedef enum{
  INIT_CHK = 0,
  INIT_RESET,
  INIT_END,
  INIT_MAX
}INIT_MODE;

uint8_t trig[] = { 0xAC, 0x33, 0x00};
uint8_t status;
uint8_t readData[7];

uint32_t beforetimCnt = millis();
float temp;
float humi;
int16_t timDht20start = TIME_DHT20_WAIT;
int16_t timDht20Out = TIME_OFF;
int16_t timDht20init;
DHT20_MODE mode = DHT20_MEASURE;
INIT_MODE initmode;

void InitDht20(void);
void mainApp(void);
void mainTimer(void);
uint8_t Dht20SetData(uint8_t* cmd, uint8_t len);
bool Dht20GetData(uint8_t reg_adr, uint8_t* reg_data, uint8_t len);
void Datachk(void);
uint8_t Crc8Calc(uint8_t *data, uint8_t sz );

void setup() {

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

  while( initmode != INIT_MAX ){
    InitDht20();
    mainTimer();
  }
}

void loop() {

  mainTimer();
  mainApp();
}

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

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

    if( timDht20start > TIME_UP ){
      --timDht20start;
    }
    if( timDht20Out > TIME_UP ){
      --timDht20Out;
    }
    if( timDht20init > TIME_UP ){
      --timDht20init;
    }
  }
}
/* 初期化 */
void InitDht20(void){
  uint8_t buf;

  switch (initmode){
    case INIT_CHK:
      if( timDht20init == TIME_UP ){
        timDht20init = TIME_DHT20_WAIT;
          if( Dht20GetData(STATUS_CHK, &status, 1) ){
            //Serial.print("status:");
            //Serial.println(status);
            if( status != 0x18 ){
              //initmode = INIT_RESET;
              initmode = INIT_END; 
              //電源OFFしても以前の測定の状態が残っていれば0x18以外になるため不要
              timDht20init = TIME_DHT20_WAIT;
            }
            else{
              initmode = INIT_MAX;
              //Serial.println("OK");
            }
          }
          else{
            Serial.println("slave-err");
            while(1);
          }
      }
      break;
    case INIT_RESET:
      if( timDht20init == TIME_UP ){
        timDht20init = TIME_DHT20_WAIT;
        buf = RESET_REG_ADDR;
        if( Dht20SetData( &buf, 1) == 0 ){
          initmode = INIT_END;
        }
      }
      break;
    case INIT_END:
      if( timDht20init == TIME_UP ){
        timDht20init = TIME_DHT20_WAIT;
        initmode = INIT_MAX;
      }
      break;
  default:
    break;
  }
}
/* メイン処理関数 */
void mainApp(void){

  switch(mode){
    case DHT20_MEASURE:
      if( timDht20start == TIME_UP ){
        timDht20start = TIME_DHT20_WAIT;
        timDht20Out = TIME_OUT_MAX;
        Dht20SetData( &trig[0], sizeof(trig));
        mode = DHT20_WAIT;
      }
      break;
    case DHT20_WAIT:
      if( timDht20start == TIME_UP ){
        timDht20start = TIME_DHT20_WAIT;
        if( Dht20GetData(NULL, &readData[0], sizeof(readData)) ){
          //Serial.print("status:");
          //Serial.println(readData[0]);
          if( (readData[0] & 0x80 ) == 0){
            mode = DHT20_CHK;
          }
          else{
            mode = DHT20_MEASURE;
          }
        }
      }
      break;
    case DHT20_CHK:
      if( timDht20start == TIME_UP ){
        timDht20start = TIME_DHT20_MAX;
        timDht20Out = TIME_OFF;
        Datachk();
        mode = DHT20_MEASURE;
      }
      break;
  }

  if( timDht20Out == TIME_UP ){
    timDht20Out = TIME_OFF;
    mode = DHT20_MEASURE;
    timDht20start = DHT20_MEASURE;
  }
}

/* DHT20へコマンド送出 */
uint8_t Dht20SetData(uint8_t* cmd, uint8_t len){
  uint8_t ret;

  Wire.beginTransmission(SLAVE_ADRS);
  for( uint8_t i=0; i < len; i++ ){
    Wire.write(*cmd); //lenサイズ分だけデータを書き込む
    ++cmd;
  }
  
  ret = Wire.endTransmission(); //ストップ・コンディションの発行
}
/* DHT20からデータを取得 */
bool Dht20GetData(uint8_t reg_adr, uint8_t* reg_data, uint8_t len){
  bool  ret = false;

  if( reg_adr != NULL ){
    Wire.beginTransmission(SLAVE_ADRS); //スタート・コンディションの発行
    Wire.write(reg_adr); //書き込む対象のアドレスをセット(ライトで指定)
    Wire.endTransmission(); //ストップ・コンディションの発行
  }

  if( Wire.requestFrom(SLAVE_ADRS, len) == len ){
    for( uint8_t i=0; i < len; i++ ){
      *reg_data = Wire.read(); //len分だけデータをリードする
      ++reg_data;
    }
    ret = true;
  }
  return ret;
}

/* CRCチェックと湿度と温度データに換算 */
void Datachk(void){
  uint32_t temp;
  uint32_t humid;
  float tempdata;
  float humiddata;
  uint8_t crc;

  crc = Crc8Calc( &readData[0],6);
  //Serial.print("crc:");
  //Serial.println(crc);

  if( crc == readData[6]){
    humid = ( readData[1] << 12 ) + ( readData[2] << 4 )
          + ( readData[3] >> 4 ) ;

    temp = ( (readData[3] & 0x0F) << 16 ) + ( readData[4] << 8 )
          + ( readData[5] );

    humiddata = ((float)humid / (1 << 20)) * 100;
    tempdata = ((float)temp / (1 << 20))*200 - 50;

    Serial.print("Tempareture: ");
    Serial.print(tempdata);
    Serial.print("℃ ");
    Serial.print("Humidity: ");
    Serial.print(humiddata);
    Serial.println("%");
  }
}
/* 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;
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

PR: 未経験OK、20代の理系に特化した就職、転職サービス UZUZ(ウズウズ)

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

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