温湿度計の自作
100円ショップで温湿度計は売っているし、SwitchBotの温湿度計も試しに買ってみたので、基本的には自作する必要などありません。次のステップでのラズパイとかにつなげてネットワーク上で室温・湿度をモニターできるようにしたいので、まずは技術蓄積が目的です。ATmegaマイコンチップでの消費電力の削減もこのプロジェクトのもう一つテーマです。
実際にはSwitchBotの温湿度計の方が安上がりなので、一時はだいぶ凹みましたが、何かをコントロールしたりMacからSwitchBotへアクセスするにはハブ2が必要とか言うので、自作も悪くはないと思い直して再開しました。
ハードウェアのメインパーツは以下のものを取り上げました。
- AVRマイコン ATmega88V−10PU: マイコン関連 秋月電子通商-電子部品・ネット通販
- 温湿度センサ AHT21B: センサ一般 秋月電子通商-電子部品・ネット通販
- LCDモジュール 16×2行 AQM1602Y−RN−GBW: ディスプレイ・表示器 秋月電子通商-電子部品・ネット通販
- ニッケル水素電池パック 3.6V830mAh HHR−P104(パナソニック製): 電池一般 秋月電子通商-電子部品・ネット通販
秋月ばっかりなのは、一つのところで済ませた方が送料をまとめられるからですね。上の他にもバッテリーケースだのトランジスタだのLEDだのダイオードだの抵抗だのコンデンサだの細かい部品もあります。
機能
以下のような機能をつけたいと考えました:
- 温湿度の測定と表示
- 電源電圧測定
- USBコネクタを繋げたら充電。充電中は充電制御
- USBコネクタを外されたら低消費電力モードになる
回路図
最初、電源であるバッテリーの3.6V、フル充電で4.3Vで稼働させるつもりで作り始めたのですが、LCDが突然表示されなくなりよくよく説明書読むと「Vddは3.3Vまで」と書いてあり、どうやら壊してしまったようです。
そこから内部動作は3.3V、給電電圧は5Vと2電源構成になってしまったので、読みづらくなってしまいました。申し訳ない。
5Vと3.3Vの1.7Vの差を埋めるために、そこかしこにダイオードやショットキーバリアダイオードなどが入ってます(逆流防止の意味もありますが)。ダイオードを通る際の電圧降下で5Vの電圧を下げ、3.3V動作のマイコン出力でLEDやらトランジスタをコントロール可能になってます。
特にLEDのところにつながっているD8(回路図中ではVSENSE)ピンをOUTPUTモードでHIGHにすることで、LEDを点滅可能にしています。
ショットキーバリアダイオード(SD103A)とトランジスタ(2SA1015)の電圧降下でピッタリ充電電圧の4.3Vになっています(ほとんど偶然ですが^^;)。
スケッチ
デバッグ用のコードが残っていて読みにくいです。ごめんなさい。
いろんな人のコードをパクりまくってます。先に謝っておきます。
/* From https://qiita.com/kurasho/items/dd908522b3784a9829d7 http://radiopench.blog96.fc2.com/blog-entry-486.html */ #include <avr/power.h> // PM関連 #include <avr/sleep.h> // スリープライブラリ #include <avr/wdt.h> // ウォッチドッグタイマー ライブラリ // include the library code: #include <Wire.h> #include <ST7032_asukiaaa.h> #include <SparkFun_Qwiic_Humidity_AHT20.h> //Click here to get the library: http://librarymanager/All#Qwiic_Humidity_AHT20 by SparkFun // initialize the library ST7032_asukiaaa lcd; AHT20 humiditySensor; #define WDT_8s 9 #define WDT_4s 8 #define WDT_2s 7 #define WDT_1s 6 #define VSENSE_PIN 8 #define CHG_PIN 9 // 基準電圧はチップごとに異なるので、逆算してから設定する #define REFV 1.08 // #define DEBUG #ifdef DEBUG #define WDT_INTERVAL 2 #else // DEBUG #define WDT_INTERVAL 30 #endif // DEBUG void setup() { // Power Reduction on unused peripheral power_all_disable(); power_adc_enable(); power_timer0_enable(); power_twi_enable(); # ifdef DEBUG power_usart0_enable(); # endif // DEBUG // TIMERのどれかはdelayやI2Cで使いそうな気がする。-> delayはtimer0でした。 // I2Cもtimer0を必要とするようだ。 // 使用するポートはINPUTモードに # ifdef DEBUG Serial.begin(9600); # endif // DEBUG pinMode(VSENSE_PIN, INPUT); // D8:外部電源検知用 pinMode(CHG_PIN, OUTPUT); // D9:充電制御用 digitalWrite(CHG_PIN, HIGH); // Default HIGH in order to power save. // set up the LCD: lcd.begin(16, 2); lcd.setContrast(35); // Print a message to the LCD. lcd.print("hello!"); humiditySensor.begin(); } int count = 0; int chgTime = 0; void loop() { // put your main code here, to run repeatedly: float vcc; float temperature; float humidity; int wdtInterval = WDT_INTERVAL; // 通常モードに復帰 vcc = cpuVcc(); lcd.begin(16, 2); lcd.setContrast(40); while (humiditySensor.available() != true) delay(1); temperature = humiditySensor.getTemperature(); humidity = humiditySensor.getHumidity(); # ifdef DEBUG Serial.print("Vcc = "); Serial.println(vcc, 2); //Print the results Serial.print("Temperature: "); Serial.print(temperature, 2); Serial.print(" C\t"); Serial.print("Humidity: "); Serial.print(humidity, 2); Serial.println("% RH"); # endif // DEBUG // set the cursor to column 0, line 0 lcd.setCursor(0, 0); lcd.print("Tmp:"); lcd.print(temperature, 1); lcd.print("C\337"); lcd.setCursor(0, 1); lcd.print("Hum:"); lcd.print(humidity, 1); lcd.print("%"); lcd.setCursor(11, 0); lcd.print("Vcc:"); lcd.setCursor(11, 1); lcd.print(vcc, 2); lcd.print("V"); pinMode(VSENSE_PIN, INPUT); // WDTによってOUTPUTモードにリセットされる? if (digitalRead(VSENSE_PIN) == HIGH) { // Shift to charge mode # ifdef DEBUG Serial.println("DC on"); // for debug # endif // DEBUG wdtInterval = 2; // Shortly loop when power supplyed if (chgTime > 27000) { // 15h analogWrite(CHG_PIN, (256 - 32)); // Trickle Charge delay(40); // Shortly blink LED pinMode(VSENSE_PIN, OUTPUT); digitalWrite(VSENSE_PIN, HIGH); // 電位差が1.5v以下になればLEDは点灯しない } else { chgTime++; pinMode(CHG_PIN, OUTPUT); digitalWrite(CHG_PIN, LOW); // Charge on # ifdef DEBUG if (chgTime % 2) { // 交互 pinMode(VSENSE_PIN, OUTPUT); digitalWrite(VSENSE_PIN, HIGH); // 電位差が1.5v以下になればLEDは点灯しない digitalWrite(CHG_PIN, HIGH); } else { pinMode(VSENSE_PIN, INPUT); digitalWrite(CHG_PIN, LOW); } # else pinMode(VSENSE_PIN, INPUT); # endif } } else { # ifdef DEBUG Serial.println("DC off"); // for debug if (digitalRead(CHG_PIN) != LOW) // 交互 for debug digitalWrite(CHG_PIN, LOW); else # endif // DEBUG digitalWrite(CHG_PIN, HIGH); // Default HIGH in order to power save. pinMode(VSENSE_PIN, INPUT); chgTime = 0; } // 低電流でスリープ delayWDT(wdtInterval); // 引数でスリープ時間指定 } void setupWDT( uint8_t delay ) { // ウォッチドッグタイマのタイムアウト設定 if ( delay > WDT_8s ) delay = WDT_8s; uint8_t reg_data = delay & 7; if ( delay & 0x8 ) { reg_data |= 0x20; } MCUSR &= ~(1 << WDRF); WDTCSR |= (1 << WDCE) | (1 << WDE); // 設定変更許可 // WDTCSR = reg_data | (1 << WDE); // タイムアウト設定 // これは誤り。これではタイムアウトでリセットもかけられる設定になる。 // 正しくは以下。割り込みだけ有効になる。 WDTCSR = reg_data; // タイムアウト設定 WDTCSR |= (1 << WDIE); // 割り込み許可 } /* sleep t seconds */ void delayWDT(unsigned long t) { // byte prevPRR; # ifdef DEBUG Serial.flush(); // シリアルバッファが空になるまで待つ # endif // DEBUG // ADCを停止(消費電流 147→27μA) // ADCSRA &= ~(1 << ADEN); power_adc_disable(); while (t >= 8) { sleepWDT(WDT_8s); t -= 8; } while (t >= 4) { sleepWDT(WDT_4s); t -= 4; } while (t >= 2) { sleepWDT(WDT_2s); t -= 2; } while (t > 0) { sleepWDT(WDT_1s); t--; } // 元に戻す power_adc_enable(); // ADCSRA |= (1 << ADEN); // ADCの電源をON } void sleepWDT(uint8_t mode) { setupWDT(mode); set_sleep_mode(SLEEP_MODE_PWR_DOWN); // パワーダウンモード指定 sleep_enable(); // sei(); // 割込みを許可する。 // sleep_bod_disable(); sleep_cpu(); // ここでCPU停止 // sleep_mode(); // cli(); sleep_disable(); // WDTがタイムアップでここから動作再開 } ISR(WDT_vect) { // WDTがタイムアップした時に実行される処理 // wdt_cycle++; // 必要ならコメントアウトを外す } float cpuVcc() { // 電源電圧(AVCC)測定関数 long sum = 0; adcSetup(0x4E); // Vref=AVcc, input=internal1.1V for (int n = 0; n < 10; n++) { sum = sum + adc(); // adcの値を読んで積分 } return (REFV * 10240.0) / sum; // 電圧を計算して戻り値にする. チップごとに補正の必要あり } void adcSetup(byte data) { // ADコンバーターの設定 ADMUX = data; // ADC Multiplexer Select Reg. ADCSRA |= ( 1 << ADEN); // ADC イネーブル ADCSRA |= 0x07; // AD変換クロック CK/128 delay(10); // 安定するまで待つ } unsigned int adc() { // ADCの値を読む unsigned int dL, dH; ADCSRA |= ( 1 << ADSC); // AD変換開始 while (ADCSRA & ( 1 << ADSC) ) { // 変換完了待ち } dL = ADCL; // LSB側読み出し dH = ADCH; // MSB側 return dL | (dH << 8); // 10ビットに合成した値を返す }
低消費電力化のためにパワーダウンモードというCPUを止めてWatch Dog Timerで起きるという大技を使っているため、loop関数の先頭でいくつかの項目の初期化が必要になってます。
動作中の消費電力も最小限に抑えるために、一旦全てをdisableしてから使うものだけenableしています。
基準電圧はマイコンの説明書には1.1Vだと書かれているのですが、チップごとに異なるので、実際にADCで測定したのち逆算してからREFVという定数に設定してください。
ブレッドボードでの試作
今回はここまでです。本番の制作記事はもうしばらくお待ちください。_O_