Raspberry Pi 4BとセンサHIH-6130で湿度・温度を測定する

##はじめに
　Raspberry Pi 4B(ラズパイ)と湿度・温度センサHIH-6130を使用して湿度と温度を測定する事例を紹介します。Honeywell社製の湿度・温度センサ(HIH-6130)を実装したセンサモジュールが[sparkfun](https://www.sparkfun.com/products/11295)で製造されています。主な仕様は次の通りです。なお、センサモジュールのピンヘッダの取り付けに、はんだ付け作業が必要になります。

・使用電圧　　2.3V～5.5V
・相対湿度RH　10%～90%
・温度　　　 　5℃～50℃
・I2Cバスによる4バイト出力
・アドレス　    0x27
![HIH-6130A.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631966/126041bc-34bb-0b83-0a40-f56f3e012bd5.jpeg)
##HIH-6130の動作概要
　スレーブ側のHIH-6130センサは通常はパワーダウンしていますが、マスター（ラズパイ）側からMeasurement Request(MR)コマンド(図2)を受信するとウェイクアップします。HIH-6130センサは標準で36.65ms後に、測定した湿度と温度をディジタル値に変換して出力レジスタに保存します。マスターはData Fetch(DF)コマンド(図3)で出力レジスタをリードします[1]。I2Cバスの仕様については参考文献[2]を参照してください。
##使用したもの
・Raspberry Pi 4B(4GB)
・Raspberry Pi OS (NOOBS 3.2.1, コードネーム:Buster)
・HIH-6130　湿度・温度センサ　https://www.switch-science.com/catalog/1104/
・ブレッドボード、ジャンパーワイヤ等
・データシート等　https://www.sparkfun.com/products/11295
##配線図
　ラズパイとHIH-6130の配線図を図１に示します。HIH-6130を+3.3Vで動作させ、I2CのSCLクロック信号とSDAデータ信号を相互に接続します。

図１HIH-6130の配線図
![Pi4 HIH-6130.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631966/696fe399-3943-9b3b-d0fe-1dd93456c906.png)
##HIH-6130 I2Cバス通信
###Measurement Request
　マスター（ラズパイ）からHIH-6130にMeasurement Request(MR)コマンドを送ると、HIH-6130はウェイクアップして計測を始めます。標準で36.65ms後に、測定した湿度と温度のデータを出力レジスタに保存します。図2に示すように、MRコマンドはスレーブアドレス(0x27)とライトbit(0)で構成されています。受信したHIH-6130はacknowledge(ACK)を出力し、ラズパイはStop bitを出力します。

図2 I2C Measurement Request Format
![MR format.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631966/583903ee-abbc-48be-4dfd-8990afd3bfea.png)
###Data Fetch
　マスターはData Fetch Formatでスレーブアドレスを指定して、4バイトの測定データをリードします(図3)。1バイト目と2バイト目は、湿度データです。1バイト目のS1とS0はStatus Bitsと呼ばれ、HIH-6130センサの状態を表しています(表1)。測定データを正常にリードできた場合は、normal operationとなります。湿度データは14bit長になります。相対湿度(Relative Humidity:RH(%))は、式1より求まります。

```math
{相対湿度} = \frac{湿度データ[13:0]}{2^{14}－１} \times100　　 (％)   [式1]\\
```
　3バイト目と4バイト目は温度データです。4バイト目の1bit目と0bit目はドントケアなので無視して、データを2bit右シフトして14bitとします。摂氏(℃)は、式2より求まります。

```math
{摂氏} = \frac{温度データ[13:0]}{2^{14}－１} \times165－40　　　(℃) [式2]\\

```
図3 I2C Data Fetch Format
![DF format.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631966/74da8df8-688d-76a9-68ce-61c5b0c15fbf.png)

表１　Status Bits

|S1|S0|概要|
|:-:|:-:|:-:|
|0|0|normal operation(有効データ)|
|0|1|stale data(古いデータ)。既にDFコマンドで読み取れたデータ、もしくは湿度と温度のデータの変換が完了する前に読み取れたデータ。|
|1|0|Command mode(センサのプログラミングモード)|
|1|1|diagnostic condition(診断状態)|

##ソースコード
　Raspberry Pi OSには、GPIOを制御するためのライブラリWiringPiが標準で装備されいます。WiringPiにI2Cバス用のライブラリがあるのですが、スレーブアドレスだけをライトする関数(図2)や4バイトのデータをリードする関数(図3)はありません。そこで、Linuxのi2c-devドライバを利用してC言語で必要な関数を記述しました[3]。スレーブアドレスをライトするのが、I2CWriteAdd関数です。HIH-6130へのMRコマンドとして使用します。また、DFコマンドとして使用するのが、I2CReadData関数です。この関数では引数lenで、リードするバイト数を指定することができます。main関数では、1秒毎にセンサのstatus condition、相対湿度、摂氏をコンソールに表示します。
　なお、参考のために、スレーブ側のレジスタを指定して、1バイトのデータをライトするI2CWriteReg8関数とリードするI2CReadReg8関数を載せました。

```c:HIH-6130C1.c
//gcc -Wall -o HIH-6130C1 HIH-6130C1.c -g -O0

#include <stdio.h>          //入出力
#include <stdlib.h>         //一般ユーティリティ
#include <string.h>         //文字列操作
#include <unistd.h>         //POSIX
#include <fcntl.h>          //POSIX
#include <sys/ioctl.h>      //I/O定義・構造体
#include <linux/i2c-dev.h>  //I2C

#define HIH6130_ADR 0x27    //HIH-6130スレーブアドレス
#define HUMID100   16383    //2^14-1

//I2C用関数のプロトタイプ宣言
int I2CSetup(int *fd, unsigned char slaveAdd);                  //I2Cセットアップ
int I2CWriteAdd(int *fd);                                       //スレーブアドレスライト
int I2CWriteReg8(int *fd, unsigned char reg, unsigned char data);   //レジスタを指定した1バイトのリード
int I2CReadData(int *fd, unsigned char *buff, unsigned char len);   //lenで指定したバイトをリード
int I2CReadReg8(int *fd, unsigned char reg, unsigned char *data);   //レジスタを指定した1バイトのライト

//関数のプロトタイプ宣言
unsigned char fetch_humidity(int fdRh, unsigned char *status, unsigned int *p_H_dat, unsigned int *p_T_dat);

int main(void){
    int fdRh;                
    unsigned char status;   //HIH-6130 status bits
    unsigned int H_dat, T_dat;
    float rh;               //相対湿度(relative humidity)%
    float t_c;              //摂氏C
    if(I2CSetup(&fdRh, HIH6130_ADR) != EXIT_SUCCESS){   //HIH-6130のI2Cセットアップ
        fprintf(stderr,"HIH6130 setup error.\n");
        exit(EXIT_FAILURE);
    }
    sleep(1);               //時間待ち 1秒
    while(1){               //湿度と温度の測定
        if(fetch_humidity(fdRh, &status, &H_dat, &T_dat) != EXIT_SUCCESS){
            fprintf(stderr,"Error fetch_humidity.\n");
            exit(EXIT_FAILURE);
        }
        switch(status){
            case 0: printf("Normal. \n");
                    break;
            case 1: printf("Stale Data.\n");
                    break;
            case 2: printf("In command mode.\n");
                    break;
            default:printf("Diagnostic.\n");
                    break;
        }

        rh = ((float)H_dat/HUMID100)*100.0;
//        rh = (float)H_dat * 6.10e-3;
        t_c = (float)T_dat*1.007e-2 - 40.0;
        printf("%3.1f %% %3.2f C\n\n",rh,t_c);
        sleep(1);           //時間待ち 1秒
    }
}

unsigned char fetch_humidity(int fdRh, unsigned char *status, unsigned int *p_H_dat, unsigned int *p_T_dat)
{
    unsigned int H_dat, T_dat;
    unsigned char buff[4];
    if(I2CWriteAdd(&fdRh) != EXIT_SUCCESS){     //Measurement Request(MR)
        return EXIT_FAILURE;
    }
    usleep(100000);         //mesurement cycle duration 100ms
    
    if(I2CReadData(&fdRh, buff, sizeof(buff)) != EXIT_SUCCESS){ //Data Fetch
        return EXIT_FAILURE;
    }
//    printf("%X %X %X %X \n",buff[0],buff[1],buff[2],buff[3]);  //printfデバッグ
    H_dat = buff[0];        //湿度上位バイトの代入
    H_dat <<= 8;
    H_dat |= buff[1];       //湿度下位バイトの代入
    *status = (0xc000 & H_dat) >> 14;    //Status bitの取得
    H_dat = 0x3fff & H_dat;
    *p_H_dat = H_dat;

    T_dat = buff[2];        //温度上位バイトの代入
    T_dat <<= 8;
    T_dat |= buff[3];       //温度下位バイトの代入
    *p_T_dat = T_dat/4;     //14bitデータに変換
    return EXIT_SUCCESS;
}

//関数名  int I2CSetup(int *fd, unsigned char slaveAdd)
//引数    fd  ファイルディスクリプタ
//戻り値   EXIT_SUCCES/EXIT_FAILURE
//概要    I2Cセットアップ
int I2CSetup(int *fd, unsigned char slaveAdd)
{
    if ((*fd = open("/dev/i2c-1", O_RDWR)) < 0) {
        perror("Error I2Csetup");
        return EXIT_FAILURE;
    }

    if (ioctl(*fd, I2C_SLAVE, slaveAdd) < 0) {
        perror("Error I2Csetup");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

//関数名  int I2CWriteAdd(int *fd)
//引数    fd  ファイルディスクリプタ
//戻り値   EXIT_SUCCES/EXIT_FAILURE
//概要    アドレスだけをライトする
int I2CWriteAdd(int *fd)
{
    unsigned char buf[1];
    if ((write(*fd, buf, 0)) != 0) { 
        perror("Error I2CWriteAdd");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

//関数名  int I2CWriteReg8(int *fd, unsigned char reg, unsigned char data)
//引数    fd  ファイルディスクリプタ
//引数    reg  デバイスのレジスタ番号
//引数    data  １バイトのデータ
//戻り値   EXIT_SUCCES/EXIT_FAILURE
//概要    レジスタを指定して１バイトのデータをライト
int I2CWriteReg8(int *fd, unsigned char reg, unsigned char data)
{
    unsigned char buff[2];
    buff[0] = reg;
    buff[1] = data;
    if ((write(*fd, buff, 2)) != 2) { 
        perror("Error I2CWriteReg8");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

//関数名  int I2CReadData(int *fd, unsigned char *buff, unsigned char len)
//引数    fd   ファイルディスクリプタ
//引数    buff リードしたデータの格納用
//引数    len  リードするデータ数
//戻り値   EXIT_SUCCES/EXIT_FAILURE
//概要    レジスタ指定無しで、lenで指定されたバイト数のデータを連続してリードする
int I2CReadData(int *fd, unsigned char *buff, unsigned char len)
{
    if (read(*fd, buff, len) != len) { 
        perror("Error I2CReadData");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

//関数名  int I2CReadReg8(int *fd, unsigned char reg, unsigned char *data)
//引数    fd   ファイルディスクリプタ
//引数    reg  デバイスのレジスタ番号
//引数    data  １バイトのデータ
//戻り値   EXIT_SUCCES/EXIT_FAILURE
//概要    デバイスのレジスタを指定して、１バイトのデータをリードする
int I2CReadReg8(int *fd, unsigned char reg, unsigned char *data)
{
    /* write address and registor */
    if ((write(*fd, &reg, 1)) != 1) { 
        perror("Error I2CReadReg8");
        return EXIT_FAILURE;
    }

    /* read 1byte data */
    if (read(*fd, &data, 1) != 1) {
        perror("Error I2CReadReg8");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
```

##実行結果
　HIH-6130C1を実行すると、1秒毎にセンサのstatus condition、相対湿度、摂氏をコンソールに表示します。HIH-6130C1.cは、Pi4とArduino Unoの測定結果を比較するために、Sparfunで公開している[Arduino code](https://phanderson.com/arduino/hih6130.html)を模して作成しました。同一のHIH-6130センサを使用して、Pi4とArduino Unoのそれぞれで測定しましたが、計測したデータは近似していました。

```:コンソールの表示
.Normal
67.2 %  22.71 C

.Normal
67.3 %  22.70 C

.Normal
67.1 %  22.72 C
```

##WiringPiとの併用について
　WiringPiのI2CライブラリとLinuxのi2c-devドライバとの併用は、下記のHIH-6130C2.ｃで確認できました。LCDは秋月電子通商が販売しているI2C接続小型キャラクタLCDモジュール(AQM1602)を使用しました。配線図を図4に示します。ラズパイとLCD AQM1602の電気的な仕様の違いにより、LCDに文字が表示されない場合があります。対策として、SDA信号線に4.7kΩのプルダウン抵抗を配線します。また、HIH-6130のプリント基板にI2Cバスのプルアップ抵抗(2.2kΩ)が2個実装されていますが、2個ともはんだこてで除去してください(図5)。プルダウン抵抗が、Pi4とLCD AQM1602との通信に影響を与え、LCDに文字が表示されない場合があります。
　HIH-6130C2.ｃでは、LCDのI2Cバスの通信にWiringPiを使用しています。コード中のLcdWriteString関数等はライブラリlibMyPi.aにあり、参考文献[4]のHPからダウンロードできます。
　HIH-6130C2を実行すると、1秒毎にセンサのstatus condition、相対湿度、摂氏をLCDに表示します(図5)。

図4　追加したLCDの配線図
![Pi4 HIH-6130 LCD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631966/250cf067-3964-5e99-2be8-cff0c21be06e.png)

図5　LCDに湿度と温度を表示した写真
![LCD.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631966/495b2ec5-5e4e-c3fe-c479-d238732a34ef.jpeg)


```c:HIH-6130C2.c
//gcc -Wall -o HIH-6130C2 HIH-6130C2.c -lwiringPi -lpthread -g -O0 -lm libMyPi.a

#include <stdio.h>          //入出力
#include <stdlib.h>         //一般ユーティリティ
#include <string.h>         //文字列操作
#include <unistd.h>         //POSIX
#include <fcntl.h>          //POSIX
#include <sys/ioctl.h>      //I/O定義・構造体
#include <linux/i2c-dev.h>  //I2C
#include <wiringPi.h>       //wiringPi
#include <wiringPiI2C.h>    //I2C用wiringPi
#include "MyPi.h"           //マイライブラリ

#define HIH6130_ADR 0x27    //HIH-6130スレーブアドレス
#define HUMID100   16383    //2^14-1
#define LCD_ADR     0x3e    //LCD スレーブアドレス

//I2C用関数のプロトタイプ宣言
int I2CSetup(int *fd, unsigned char slaveAdd);
int I2CWriteAdd(int *fd); 
int I2CReadData(int *fd, unsigned char *buff, unsigned char len);

//関数のプロトタイプ宣言
unsigned char fetch_humidity(int fdRh, unsigned char *status, unsigned int *p_H_dat, unsigned int *p_T_dat);

int main(void){
    int fdRh;
    int fdLcd;
    unsigned char status;
    unsigned int H_dat, T_dat;
    float rh;               //相対湿度(relative humidity)%
    float t_c;              //摂氏C
    char s1[16];            //LCD16文字分のバッファの確保
        
    if(I2CSetup(&fdRh, HIH6130_ADR) != EXIT_SUCCESS){   //HIH-6130のI2Cセットアップ
        fprintf(stderr,"HIH6130 setup error.\n");
        return EXIT_FAILURE;
    }
    fdLcd = wiringPiI2CSetup(LCD_ADR);      //LCDのI2Cセットアップ
    if(LcdSetup(fdLcd) != EXIT_SUCCESS){
        fprintf(stderr,"LCD setup error.\n");
        return EXIT_FAILURE;
    }
    sleep(1);               //時間待ち 1秒
    while(1){
        LcdClear(fdLcd);
        if(fetch_humidity(fdRh, &status, &H_dat, &T_dat) != EXIT_SUCCESS){
            fprintf(stderr,"Error fetch_humidity.\n");
            exit(EXIT_FAILURE);
        }
        switch(status){
            case 0: printf("Normal. \n");
                    LcdWriteString(fdLcd, "Normal.");
                    break;
            case 1: printf("Stale Data.\n");
                    LcdWriteString(fdLcd, "Stale Data.");
                    break;
            case 2: printf("In command mode.\n");
                    LcdWriteString(fdLcd, "In command mode.");
                    break;
            default:printf("Diagnostic.\n");
                    LcdWriteString(fdLcd, "Diagnostic.");
                    break;
        }

        rh = ((float)H_dat/HUMID100)*100.0;
//        rh = (float)H_dat * 6.10e-3;
        t_c = (float)T_dat*1.007e-2 - 40.0;
        printf("%3.1f %%  %3.2f C\n\n",rh,t_c);
        
        LcdNewline(fdLcd);
        sprintf(s1,"%4.1f %%",rh);           //実数を文字列に変換
        LcdWriteString(fdLcd, s1);          //LCDに湿度を表示

        sprintf(s1,"   %4.1f C",t_c);       //実数を文字列に変換
        LcdWriteString(fdLcd, s1);          //LCDに温度を表示        
        sleep(1);           //時間待ち 1秒
    }
}

unsigned char fetch_humidity(int fdRh, unsigned char *status, unsigned int *p_H_dat, unsigned int *p_T_dat)
{
    unsigned int H_dat, T_dat;
    unsigned char buff[4];
    if(I2CWriteAdd(&fdRh) != EXIT_SUCCESS){     //Measurement Request(MR)
        return EXIT_FAILURE;
    }
    usleep(100000);         //mesurement cycle duration 100ms
    
    if(I2CReadData(&fdRh, buff, sizeof(buff)) != EXIT_SUCCESS){ //Data Fetch
        return EXIT_FAILURE;
    }
//    printf("%X %X %X %X \n",buff[0],buff[1],buff[2],buff[3]);  //printfデバッグ
    H_dat = buff[0];        //湿度上位バイトの代入
    H_dat <<= 8;
    H_dat |= buff[1];       //湿度下位バイトの代入
    *status = (0xc000 & H_dat) >> 14;    //Status bitの取得
    H_dat = 0x3fff & H_dat;
    *p_H_dat = H_dat;

    T_dat = buff[2];        //温度上位バイトの代入
    T_dat <<= 8;
    T_dat |= buff[3];       //温度下位バイトの代入
    *p_T_dat = T_dat/4;     //14bitデータに変換
    return EXIT_SUCCESS;
}

//関数名  int I2CSetup(int *fd, unsigned char slaveAdd)
//引数    fd  ファイルディスクリプタ
//戻り値   EXIT_SUCCES/EXIT_FAILURE
//概要    I2Cセットアップ
int I2CSetup(int *fd, unsigned char slaveAdd)
{
    if ((*fd = open("/dev/i2c-1", O_RDWR)) < 0) {
        perror("Error I2Csetup");
        return EXIT_FAILURE;
    }

    if (ioctl(*fd, I2C_SLAVE, slaveAdd) < 0) {
        perror("Error I2Csetup");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

//関数名  int I2CWriteAdd(int *fd)
//引数    fd  ファイルディスクリプタ
//戻り値   EXIT_SUCCES/EXIT_FAILURE
//概要    アドレスだけをライトする
int I2CWriteAdd(int *fd)
{
    unsigned char buf[1];
    if ((write(*fd, buf, 0)) != 0) { 
        perror("Error I2CWriteAdd");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

//関数名  int I2CReadData(int *fd, unsigned char *buff, unsigned char len)
//引数    fd   ファイルディスクリプタ
//引数    buff リードしたデータの格納用
//引数    len  リードするデータ数
//戻り値   EXIT_SUCCES/EXIT_FAILURE
//概要    レジスタ指定無しで、lenで指定されたバイト数のデータを連続してリードする
int I2CReadData(int *fd, unsigned char *buff, unsigned char len)
{
    if (read(*fd, buff, len) != len) { 
        perror("Error I2CReadData");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
```

##Reference

[1]  "[Technical Note I2C Communication with the Honeywell HumidIcon™ Digital Humidity/Temperature Sensors: HIH-6130/6131 Series](https://phanderson.com/arduino/I2CCommunications.pdf)",Honeywell(Aug.2011)
[2] "[UM10204 I2C バス仕様およびユーザーマニュアル](https://www.nxp.com/docs/ja/user-guide/UM10204.pdf)", NXP Semiconductors(Nov.2012)
[3] 猫ぱーんち！,"[Raspberry Pi でI2C: C言語プログラミング](https://penkoba.hatenadiary.org/entry/20131222/1387689702)"(Dec.2013)
[4] 菊池達也,"[C言語ではじめるRaspberry Pi徹底入門](https://gihyo.jp/book/2020/978-4-297-11299-8)",技術評論社(Apr.2020)