概要
I2C デバイス (温湿度センサー HTU21D) を micro:bit に外づけしてみる。I2C バスは micro:bit の標準端子 (SCL は P19、SDA は P20) をそのまま使う。標準の I2C バスを使うのであればプルアップ抵抗はスレーブ側には不要である。
ここではマルツ製のブレークアウトボード (温度/湿度センサーモジュール基板【MHTU21D】)を使った。
(1) 温度と湿度とを読み取る方法
#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 を試してみる場合
#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 とを組み合わせる方法
#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) 測定分解能を変更する方法
#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) ソフトリセットをかける方法
データシートによればソフトリセットを推奨するとの由。
#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) 全体をごく簡単にライブラリにまとめた。
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 を引っこ抜いたとき):
参考
HTU21D データシート
https://lancaster-university.github.io/microbit-docs/ubit/i2c/
Harry Fairhead, micro:bit IoT in C, I/O Press, pp.91-101
勝 純一, mbed 電子工作レシピ, 翔泳社