2017年2月27日月曜日

HDC1000をArduinoで使って不快指数計を作る ブレッドボードで実験

ブレッドボード図


HDC1000


温度、湿度が分かれば不快指数が出せるので、秋月で売っているHDC1000というI2C接続の温湿度モジュールを使って、不快指数計を作ってみる。HDC1000は以前7Seg LEDを使って実験したことがある。

Nokia 5110


表示器には不快指数のアイコンを表示できたらな~と思って、aitendoで売っているSPI接続のグラフィックLCDのLCD5110を使ってみた。これもArduinoやNucleoで何度か実験したことがある。

HDC1000は動作電圧が3V~5Vなのでそのまま5V駆動のArduino Unoで使えるが、Nokia 5110は3.3V駆動なので、74HC4050を使ってSPI信号の5V->3.3Vの電圧の変換を行った。(参考:http://dad8893.blogspot.jp/search?q=74HC4050

Nokia5110のライブラリには、Rinky-Dink ElectronisさんのLCD5110_Basic Library を使った。最初は色々なLCDに対応しているu8glibを使ってみたが、LCD5110_Basicは5110のスリープ・モードに対応しているのでこちらにした。

スリープ機能


電池駆動させようと思ったので、消費電力節約のために、AVRのSleep機能を使って見た。使い方は「初心者だけど、一歩ずつ Arduino 超小型マイコン電子工作」さんの記事を参考にした。

オルタナティブのタクトスイッチでスリープモードからの復帰、モーメンタリのスイッチでスリープ有り/無しのモードを切り替える。

自作の簡易電流計で測定したら、

動作時:約44mA
スリープ時:約12mA

となった。Arduinoボード上にLEDがついているのでこれの消費電流がそこそこあるのかもしれない。

HDC1000はI2Cのコマンドを送信しないとスリープモードに入るようなので(←ちゃんとは確認していないです)プログラムでは特には何もしていない。

Arduinoのスケッチ

HDC1000_Thermometer.ino

/*
 * HDC100_Thermometer
 * 
 * 2017.02.26
 * 
 */

#include <Wire.h>
#include <LCD5110_Basic.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

#define HDC1000_ADDRESS 0x40 /* or 0b1000000 */
#define HDC1000_RDY_PIN A3   /* Data Ready Pin */

#define HDC1000_TEMPERATURE_POINTER     0x00
#define HDC1000_HUMIDITY_POINTER        0x01
#define HDC1000_CONFIGURATION_POINTER   0x02
#define HDC1000_SERIAL_ID1_POINTER      0xfb
#define HDC1000_SERIAL_ID2_POINTER      0xfc
#define HDC1000_SERIAL_ID3_POINTER      0xfd
#define HDC1000_MANUFACTURER_ID_POINTER 0xfe

#define HDC1000_CONFIGURE_MSB 0x10 /* Get both temperature and humidity */
#define HDC1000_CONFIGURE_LSB 0x00 /* 14 bit resolution */

#define WAKEUP_PIN     2
#define SLEEP_MODE_PIN 3
#define BACK_LIGHT_PIN 7

#define WAKEUP_PERIOD 5

LCD5110 myGLCD(8,9,10,11,12);

bool isSleepMode = true;
int count;

extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
extern uint8_t BigNumbers[];

void setup() {
  // Sleep Mode
  pinMode(WAKEUP_PIN, INPUT_PULLUP);
  pinMode(SLEEP_MODE_PIN, INPUT_PULLUP);

  // LCD Power
  pinMode(BACK_LIGHT_PIN, OUTPUT);
  digitalWrite(BACK_LIGHT_PIN, HIGH);

  // LCD
  myGLCD.InitLCD();

  // HDC1000
  Wire.begin();
  pinMode(HDC1000_RDY_PIN, INPUT);
  delay(15); /* Wait for 15ms */
  configure();

  Serial.begin(9600);
  Serial.print("Manufacturer ID = 0x");
  Serial.println(getManufacturerId(), HEX);
  Serial.println();
}

float calcDiscomfort(float temperature, float humidity) {
  return 0.81 * temperature + 0.01 * humidity * (0.99 * temperature - 14.3) + 46.3;
}

void loop() {
  float temperature, humidity, discomfort;
 
  getTemperatureAndHumidity(&temperature, &humidity);
  discomfort = calcDiscomfort(temperature, humidity);
  Serial.print("Temperature = ");
  Serial.print(temperature);
  Serial.print(" degree, Humidity = ");
  Serial.print(humidity);
  Serial.print("%, Discomfort = ");
  Serial.println(discomfort);

  myGLCD.clrScr();
  myGLCD.setFont(BigNumbers);
  myGLCD.printNumF(temperature, 1, RIGHT, 0);
  myGLCD.setFont(MediumNumbers);
  myGLCD.printNumF(humidity, 1, LEFT, 32);
  myGLCD.printNumF(discomfort, 0, RIGHT, 32);
  myGLCD.setFont(SmallFont);
  if (isSleepMode) {
    myGLCD.print("SLP", LEFT, 0);
  } else {
    myGLCD.print("CON", LEFT, 0);
  }

  delay(1000);

  // Check Sleep Mode
  isSleepMode = digitalRead(SLEEP_MODE_PIN);

  // Sleep
  if (isSleepMode) {
    count++;
    if (count >= WAKEUP_PERIOD) {
      Serial.println("Sleep mode start!!");
      count = 0;
  
      sleepAndWakeup(WAKEUP_PIN);
    }
  }
}

//-----------------------------------------------------------------------------------------------
// Sleep Mode
//-----------------------------------------------------------------------------------------------
void wakeup() {
  Serial.println("Wakeup!!");
}

int sleepAndWakeup(int interruptNo) {
  Serial.println("sleepAndWake Process start!!");
  if (digitalRead(WAKEUP_PIN) == LOW) {
    Serial.println("WAKEUP_PIN Low Level");
  } else {
    Serial.println("WAKEUP_PIN High Level");
  }
  Serial.println("sleep enable");
  delay(100);

  // Sleep
  digitalWrite(BACK_LIGHT_PIN, LOW);
  myGLCD.enableSleep();
  attachInterrupt(digitalPinToInterrupt(interruptNo), wakeup, FALLING);
  noInterrupts();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  interrupts();
  sleep_cpu();

  // Wakeup
  sleep_disable();
  detachInterrupt(digitalPinToInterrupt(interruptNo));
  digitalWrite(BACK_LIGHT_PIN, HIGH);
  myGLCD.disableSleep();
  
  return 0;
}

//-----------------------------------------------------------------------------------------------
// HDC1000
//-----------------------------------------------------------------------------------------------
void configure() {
  Wire.beginTransmission(HDC1000_ADDRESS);
  Wire.write(HDC1000_CONFIGURATION_POINTER);
  Wire.write(HDC1000_CONFIGURE_MSB);
  Wire.write(HDC1000_CONFIGURE_LSB);
  Wire.endTransmission();
}

int getManufacturerId() {
  int manufacturerId;

  Wire.beginTransmission(HDC1000_ADDRESS);
  Wire.write(HDC1000_MANUFACTURER_ID_POINTER);
  Wire.endTransmission();

  Wire.requestFrom(HDC1000_ADDRESS, 2);
  while (Wire.available() < 2) {
    ;
  }

  manufacturerId = Wire.read() << 8;
  manufacturerId |= Wire.read();

  return manufacturerId;
}

void getTemperatureAndHumidity(float *temperature, float *humidity) {
  unsigned int tData, hData;

  Wire.beginTransmission(HDC1000_ADDRESS);
  Wire.write(HDC1000_TEMPERATURE_POINTER);
  Wire.endTransmission();

  while (digitalRead(HDC1000_RDY_PIN) == HIGH) {
    ;
  }

  Wire.requestFrom(HDC1000_ADDRESS, 4);
  while (Wire.available() < 4) {
    ;
  }

  tData = Wire.read() << 8;
  tData |= Wire.read();

  hData = Wire.read() << 8;
  hData |= Wire.read();

  *temperature = tData / 65536.0 * 165.0 - 40.0;
  *humidity = hData / 65536.0 * 100.0;
}


Arduinoで書き込んだAVRを生で使う


Arduinoはプログラムを書くのが楽でいいんだが、そのまま使うとお金もかかるし場所も取るのでArduinoからATMega328Pを取り外して、Fuse Bitを書き換えて内蔵RCクロックで動作するようにして動くかどうか試してみた。

Arduino Unoは外付け水晶振動子で16MHzで動いているが、Fuse Bitで内蔵RC1MHzに設定してやると遅~いスピードで動作するようだ。

Arduino Unoで使う場合のFuse Bit

h: D6
l: FF
e: 05

内蔵RC(1MHz)で使ったFuse Bit

h: D9
l: 62
e: ff(←書き込み時)

I2C、SPIの通信速度もあるので、もうちょっと検証が必要。

Arduinoから取り外してクロック数が少ない内蔵RCをクロックとして使えば消費電流も少なくて済むし3.3V駆動もできるので電池で使うにはよさそう。

3.3V駆動できれば、LCDのロジックレベル変換用に使っている74HC4050も不要になる。

メモ:

Sleep時にはLCDのバックライトだけでなく、AVR以外の電源をバッサリ切るようにする?⇛復帰時にデバイスの初期化をし直さないとだめ?

外部電源(3.3V)が使える場合は、UART通信できるようにしておく?⇛3.3VLDO+USBシリアル変換

Arduino Pro mini(8MHz)も参考にする?