micro:bit / mbed / I2C デバイス (温湿度センサー HTU21D) を外づけする

#概要
I2C デバイス (温湿度センサー HTU21D) を micro:bit に外づけしてみる。I2C バスは micro:bit の標準端子 (SCL は P19、SDA は P20) をそのまま使う。標準の I2C バスを使うのであればプルアップ抵抗はスレーブ側には不要である。

ここではマルツ製のブレークアウトボード ([温度/湿度センサーモジュール基板【MHTU21D】](https://www.marutsu.co.jp/pc/i/253906/))を使った。<br><img src="https://qiita-image-store.s3.amazonaws.com/0/193936/d8e41125-7c22-cf4b-c7c0-d4b2923a850f.png" width= 40%>


#(1) 温度と湿度とを読み取る方法

```cpp
#include "MicroBit.h"
MicroBit uBit;

#define HTDU21D_ADDRESS           0x40<<1
#define TRIGGER_TEMP_MEASURE_HOLD 0xE3
#define TRIGGER_HUMD_MEASURE_HOLD 0xE5

float readTemp(){
    // master hold モードで温度データ (8 ビットデータを 2 つ) を読み出す。
    char buf[] = {TRIGGER_TEMP_MEASURE_HOLD};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 1); 
    uBit.i2c.read (HTDU21D_ADDRESS, buf, 2);
    
    // 2 つの 8 ビットデータを 16 ビットデータに並べ換えて、
    // 下 2 ビットは不要なので 00 にして、
    // 変換式を適用して返す。
    unsigned short raw = (buf[0]<<8 | buf[1]) & 0xfffc;
    return -46.85F + 175.72F * (float)raw / 65536.0F;
}

float readHumid(){
    // master hold モードで湿度データ (8 ビットデータを 2 つ) を読み出す。
    char buf[] = {TRIGGER_HUMD_MEASURE_HOLD};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 1); 
    uBit.i2c.read (HTDU21D_ADDRESS, buf, 2);
    
    // 2 つの 8 ビットデータを 16 ビットデータに並べ換えて、
    // 下 2 ビットは不要なので 00 にして、
    // 変換式を適用して返す。
    unsigned short raw = (buf[0]<<8 | buf[1]) & 0xfffc;
    return -6.0F + 125.0F * (float)raw / 65536.0F;
}

int main(void){
    uBit.init();
    
    while(1){
        printf("%.1f C, %.1f %%\n", readTemp(), readHumid());
        wait(1.0);
    }

    release_fiber();
    return 0;
}
```

#(2) CRC を試してみる場合

```cpp

#include "MicroBit.h"
MicroBit uBit;

#define HTDU21D_ADDRESS 0x40<<1
#define SHIFTED_DIVISOR 0x988000UL // (2^8 + 2^5 + 2^4 + 1) << 15

unsigned short check_crc(char msb, char lsb, char crc){
    unsigned int divisor = SHIFTED_DIVISOR;
    
    // 測定値の上位 8 ビット、下位 8 ビット、CRC 値の順に並べて、
    unsigned int remainder = msb<<16 | lsb<<8 | crc;
        
    // その左端から順番に 1 が立っているかどうかを調べて何かをする。
    for(int i=23; i>7; i--){
        if(remainder & 1<<i){
            remainder ^= divisor;
        }
        divisor >>= 1;        
    }
    // 正しい場合は 0 を、誤りの場合は 999 を返す。
    return (remainder == 0) ? 0 : 999;
}

int main(void){
    uBit.init();
    
    // 正しい例 (データシートから引用) (0 が返る)
    printf("%d\n", check_crc(0x68,0x3A,0x7c));
    printf("%d\n", check_crc(0x4E,0x85,0x6b));
    
    // 誤りの例 (999 が返る)
    printf("%d\n", check_crc(0x68,0x3A,0x7d));
    printf("%d\n", check_crc(0x4E,0x85,0x6c));

    release_fiber();
    return 0;
}
```

#(3) 温度、湿度の測定と CRC とを組み合わせる方法

```cpp
#include "MicroBit.h"
MicroBit uBit;

#define HTDU21D_ADDRESS           0x40<<1
#define TRIGGER_TEMP_MEASURE_HOLD 0xE3
#define TRIGGER_HUMD_MEASURE_HOLD 0xE5
#define SHIFTED_DIVISOR           0x988000UL // (2^8 + 2^5 + 2^4 + 1) << 15

unsigned short check_crc(char msb, char lsb, char crc){
    unsigned int divisor   = SHIFTED_DIVISOR;
    unsigned int remainder = msb<<16 | lsb<<8 | crc;

    for(int i=23; i>7; i--){
        if(remainder & 1<<i){
            remainder ^= divisor;
        }
        divisor >>= 1;        
    }
    return (remainder == 0) ? 0 : 999;
}

float readTemp(){
    char buf[3] = {TRIGGER_TEMP_MEASURE_HOLD};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 1); 
    uBit.i2c.read (HTDU21D_ADDRESS, buf, 3);
    
    unsigned short raw       = (buf[0]<<8 | buf[1]) & 0xfffc;
    unsigned short crcResult = check_crc(buf[0], buf[1], buf[2]);
    
    // CRC が正しい場合は測定値を返し、誤りの場合は 999 を返す。
    return (crcResult == 0) ?
           -46.85F + 175.72F * (float)raw / 65536.0F :
           crcResult;
}

float readHumid(){
    char buf[3] = {TRIGGER_HUMD_MEASURE_HOLD};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 1); 
    uBit.i2c.read (HTDU21D_ADDRESS, buf, 3);

    unsigned short raw       = (buf[0]<<8 | buf[1]) & 0xfffc;
    unsigned short crcResult = check_crc(buf[0], buf[1], buf[2]);

    // CRC が正しい場合は測定値を返し、誤りの場合は 999 を返す。    
    return (crcResult == 0) ?
           -6.0F + 125.0F * (float)raw / 65536.0F :
           crcResult;
}

int main(void){
    uBit.init();

    while(1){
        printf("%.1f C, %.1f %%\n", readTemp(), readHumid());
        wait(1.0);
    }

    release_fiber();
    return 0;
}
```


#(4) 測定分解能を変更する方法

```cpp
#include "MicroBit.h"
MicroBit uBit;

#define HTDU21D_ADDRESS 0x40<<1
#define WRITE_USER_REG  0xE6
#define READ_USER_REG   0xE7

// ユーザーレジスタを読み出すための函数
char read_user_register(void){
    char buf[] = {READ_USER_REG};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 1); 
    uBit.i2c.read (HTDU21D_ADDRESS, buf, 1);
    
    return buf[0];
}

// 測定分解能を設定するための函数 (ビット 7, 0 だけを書き換える)
void setResolution(char resolution){
    
    // 現在のユーザーレジスタを読み出す。
    char userRegister = read_user_register();
    
    // 測定分解能に関係するビット (7, 0) だけを書き換える。
    // ビット 7, 0 以外を誤って書き換えてしまわないようにする。
    userRegister &= 0b01111110;
    resolution   &= 0b10000001;
    userRegister |= resolution;
    
    char buf[] = {WRITE_USER_REG, userRegister};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 2);
}

int main(void){
    uBit.init();
    
    // 湿度 12 ビット、温度 14 ビット (デフォルト) にする。0b 0000 0010 = 0x2 になるはずである。 
    setResolution(0b00000000);
    printf("User Register: 0x%X\n", read_user_register());
    
    // 湿度 8 ビット、温度 12 ビットにする。0b 0000 0011 = 0x3 になるはずである。 
    setResolution(0b00000001);
    printf("User Register: 0x%X\n", read_user_register());
    
    // 湿度 10 ビット、温度 13 ビットにする。0b 1000 0010 = 0x82 になるはずである。 
    setResolution(0b10000000);
    printf("User Register: 0x%X\n", read_user_register());

    // 湿度 11 ビット、温度 11 ビットにする。0b 1000 0011 = 0x83 になるはずである。 
    setResolution(0b10000001);
    printf("User Register: 0x%X\n", read_user_register());
    
    // デフォルトに戻す。0b 0000 0010 = 0x2 になるはずである。 
    setResolution(0b00000000);
    printf("User Register: 0x%X\n", read_user_register());
    
    release_fiber();
    return 0;
}
```

#(5) ソフトリセットをかける方法
データシートによればソフトリセットを推奨するとの由。

```cpp
#include "MicroBit.h"
MicroBit uBit;

#define HTDU21D_ADDRESS 0x40<<1
#define WRITE_USER_REG  0xE6
#define READ_USER_REG   0xE7
#define SOFT_RESET      0xFE

char read_user_register(void){
    char buf[] = {READ_USER_REG};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 1); 
    uBit.i2c.read (HTDU21D_ADDRESS, buf, 1);
    
    return buf[0];
}

void setResolution(char resolution){
    char userRegister = read_user_register();
    
    userRegister &= 0b01111110;
    resolution   &= 0b10000001;
    userRegister |= resolution;
    
    char buf[] = {WRITE_USER_REG, userRegister};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 2);
}

// ソフトリセットをかけるための函数
void softReset(void){
    char buf[] = {SOFT_RESET};
    uBit.i2c.write(HTDU21D_ADDRESS, buf, 1);
}

int main(void){
    uBit.init();
    
    // 湿度 12 ビット、温度 14 ビット (デフォルト) にする。0b 0000 0010 = 0x2 になるはずである。 
    setResolution(0b00000000);
    printf("User Register: 0x%X\n", read_user_register());
    
    // 湿度 11 ビット、温度 11 ビットにする。0b 1000 0011 = 0x83 になるはずである。 
    setResolution(0b10000001);
    printf("User Register: 0x%X\n", read_user_register());
    
    // ソフトリセットをかける。デフォルト (0b 0000 0010 = 0x2) に戻るはずである。
    softReset();
    printf("User Register: 0x%X\n", read_user_register());

    release_fiber();
    return 0;
}
```

#(6) 全体をごく簡単にライブラリにまとめた。
https://github.com/ti-nspire/microbit-in-mbed-library-for-HTU21D-sensor

```main.cpp
#include "MicroBit.h"
#include "HTU21D.h"

MicroBit uBit;
HTU21D   HTU;

int main(void){
    uBit.init();

    //HTU.softReset();

    //HTU.setResolution(RH12_TEMP14);
    //HTU.setResolution(RH08_TEMP12);
    //HTU.setResolution(RH10_TEMP13);
    //HTU.setResolution(RH11_TEMP11);
    while(1){
        //printf("User Register: 0x%X\n", HTU.read_user_register());

        float temp  = HTU.readTemp();
        float humid = HTU.readHumid();
        printf("%.1f C, %.1f %%\n", temp, humid);

        wait(1.0);
    }

    release_fiber();
    return 0;
}
```

実行結果 (値が 999.0 になっているのは SDA を引っこ抜いたとき):
<img src="https://qiita-image-store.s3.amazonaws.com/0/193936/905e14c2-4926-b7b9-a6bb-c4fcaa7dc2c3.png" width=80%>


#参考
[HTU21D データシート](http://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FHPC199_6%7FA6%7Fpdf%7FEnglish%7FENG_DS_HPC199_6_A6.pdf%7FHPP845E031R4)
https://lancaster-university.github.io/microbit-docs/ubit/i2c/
Harry Fairhead, _micro:bit IoT in C_, I/O Press, pp.91-101
勝 純一, mbed 電子工作レシピ, 翔泳社

#関連
[はてなブログ / 温湿度センサー HTU21D](http://ti-nspire.hatenablog.com/archive/category/%E6%B8%A9%E6%B9%BF%E5%BA%A6%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%20HTU21D%20%2F%20I2C)