Arduinoで冷凍庫半開き検出ブザーを作ってみた 後編

# はじめに

[Arduinoで冷凍庫半開き検出ブザーを作ってみた 前編](http://qiita.com/isop/items/ed4aa2e77ed0d684fc2e) の続きです。
当初はArduino Nanoで作成するつもりでしたが、[Arduino UNOでATmega328 生DIPにbootloaderを書き込む](http://qiita.com/isop/items/7b1a63ea81664fd5beec) で書いてある通り、Arduino Nanoの入手に手間取ったため、ATmega328Pを使う方針に切り替えました。

今回は、以下の対応を行いました。

* 省電力モードの導入
* ユニバーサル基板への実装

## 必要なもの

* ATmega328P-PU ([Arduino UNOでATmega328 生DIPにbootloaderを書き込む](http://qiita.com/isop/items/7b1a63ea81664fd5beec)でスケッチまで焼かれたもの)
* ユニバーサル基板 (PCB 50mm×70mm 432穴)
* 水晶 16 MHz
* 抵抗 220Ω, 10kΩ, 20kΩ
* コンデンサ 22pF × 2個, 100nF
* リードスイッチ
* 電子ブザー
* LED 赤色
* すずメッキ線
* 半田ごて
* ケース (タカチ電機工業 SS-90A)
* 電動ドリル
* mini USBコネクタ
* mini USBケーブル
* USB電源アダプタ

## 省電力モード

Arduinoは通常loop関数をひたすら実行し続けるため、ある程度電力を消費してしまいます。
消費電力を抑えるためには、sleep modeに移行する必要があります。

http://playground.arduino.cc/Learning/ArduinoSleepCode 

sleep modeは以下の5つのモードがあり、下に行くほど消費電力を抑えることができますが、利用可能なHW blockも減るため、使い分けが必要です。

* SLEEP_MODE_IDLE
* SLEEP_MODE_ADC
* SLEEP_MODE_PWR_SAVE
* SLEEP_MODE_STANDBY
* SLEEP_MODE_PWR_DOWN

[ATmega328Pのデータシート](http://www.atmel.com/images/atmel-8271-8-bit-avr-microcontroller-atmega48a-48pa-88a-88pa-168a-168pa-328-328p_datasheet_complete.pdf)を参照すると、以下のようにどのHW blockが使えるかがわかります。

![WS000005.JPG](https://qiita-image-store.s3.amazonaws.com/0/35314/4766ddf4-a2b6-5969-9266-15f267ba83c6.jpeg)


今回、リードスイッチを使用して、外部割込みによるsleep modeからの復帰が可能なため、SLEEP_MODE_PWR_DOWNを使ったsleep modeの設定を行います。
sleep modeに入るためには、sleep_cpu()の実行が必要となりますが、sleep modeからの復帰はsleep_cpu()を実行したところからとなります。
外部割込みによるsleepからの復帰を行う場合は、割り込みを受けられる状態でsleep modeに遷移しないといけないので、ISR(割り込み処理ルーチン)内でsleep modeに入ることができません。
そのため、loop関数内でsleep modeに遷移するかどうかを判断して、必要があればsleep modeに遷移するようにしました。
sleep modeに遷移するかどうかは、リードスイッチのOn/Offによる割り込みとタイマー割り込みのISR内でフラグを立てています。
また、loop関数内でsleep modeに遷移する途中で他のISRが動くと中途半端な状態になるため、割り込み禁止にした上で、sleep modeに遷移しています。

```c
#include <avr/sleep.h>

/* share sleep mode state for main loop and interrupt  */
static volatile bool enabled_sleep = true;

void loop() {
  enableSleepMode();
}

void enableSleepMode()
{
  noInterrupts();
  if (enabled_sleep) {
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
    sleep_bod_disable();
    interrupts();
    /* sleep here */
    sleep_cpu();
    /* wakeup here after external interrupt */
    sleep_disable();
  }
  interrupts();
}

void enableSleep()
{
  enabled_sleep = true;
}

void disableSleep()
{
  enabled_sleep = false;
}

void handleSwitchStateChanged()
{
  if (isOpenedSwitch()) {
    disableSleep();
    disableBuzzer();
    disableTimer();
    enableTimer(SWITCH_OPEN_TIMEOUT_MSEC, handleBuzzerTimer);
    enableLed();
  } else {
    disableTimer();
    if (!enabled_buzzer_timer) {
      enableBuzzer();
    } else {
      disableBuzzer();
    }
    enableTimer(BUZZER_CLOSED_DURATION_MSEC, handleSwitchClosedBuzzerTimer);
    enabled_buzzer_timer = false;
    disableLed();
  }
}

void handleSwitchClosedBuzzerTimer()
{
  enabled_buzzer_timer = false;
  disableTimer();
  disableBuzzer();
  enableSleep();
}
```

消費電流の計測をやろうと思ったのですが、手元にある電流計が不良で計測できませんでした。。。
変更の詳細は以下を参照してください。

https://github.com/isop/freezer-door-open-detector-arduino/commit/07e5668e7d11e165eb4565830a3bc4cc6dfaaaa4

## ユニバーサル基板への実装

ブレッドボードでの動作検証はできたので、実際に冷凍庫に取り付けて常用できるようにするため、ユニバーサル基板に実装して、ケースに入れることにします。
以下の図ではDCジャックをマウントしていますが、実際はminiUSBコネクタを実装しています。
もともとArduino Nanoを使う予定だったので、電源はminiUSBから取るつもりだったので、このようにしています。

![freezer_alarm_avr_ブレッドボード.png](https://qiita-image-store.s3.amazonaws.com/0/35314/fe8691ea-bf56-939d-d02e-2bc39df48cb8.png)

![freezer_alarm_avr_回路図.png](https://qiita-image-store.s3.amazonaws.com/0/35314/1e3e02ef-1196-37a2-b89f-3bb5cc7de1b5.png)

ユニバーサル基板に実装してケースに取り付けると以下のようになります。
ケースに入るように、ユニバーサル基板はカットしています。

![2015-10-25 00.39.37.jpg](https://qiita-image-store.s3.amazonaws.com/0/35314/1ac99a37-f710-4169-081a-439d33f1e03a.jpeg)

ふたをして電源をつなげると以下のようになります。

![2015-10-25 00.46.19.jpg](https://qiita-image-store.s3.amazonaws.com/0/35314/1cb2cd48-a492-29ee-09dc-4860366a98b9.jpeg)

スイッチをケースから遠ざけると、LEDが点灯します。
この状態から60秒が経過すると、ビープ音が鳴り続けます。

![2015-10-25 00.46.28.jpg](https://qiita-image-store.s3.amazonaws.com/0/35314/478443b0-7398-895f-4185-d92d88563da6.jpeg)

スイッチをケースに再度近づけると、LEDが消灯して、ピッとビープ音が鳴ります。
問題なく動作することが確認できました。

# おわりに

初めての電子回路作成でしたが、実際に作ってみることで学べることも多くありました。
部品など必要なものを最初にそろえる必要があるので、完全に0から始めることはできませんが、ソフトだけではできないこともできるようになります。
何を作ったらよいかのネタ出しは大変ですが、今回は実際の生活からネタが見えたので助かりました。
具体的にどうしたらよいかも、世の先人達の知恵を参照することでわかる範囲だったのでよかったです。
世の先人の皆様に感謝です。

## おまけ

Serial.print()ファミリのデバッグ用ラッパ関数を作って、リリース時にはSerial.print()を無効化しようと思ったら、print(), println()メソッドが大量にオーバーロードされていたのでこれを全部再実装するのは面倒だなぁ、と思いました。
幸いArduinoIDEでもC++が使えたので、テンプレート関数でラッパ関数を実装しました。

```c:Arduino/hardware/arduino/avr/cores/arduino/Print.h
class Print
{
  private:
    int write_error;
    size_t printNumber(unsigned long, uint8_t);
    size_t printFloat(double, uint8_t);
  protected:
    void setWriteError(int err = 1) { write_error = err; }
  public:
    Print() : write_error(0) {}
  
    int getWriteError() { return write_error; }
    void clearWriteError() { setWriteError(0); }
  
    virtual size_t write(uint8_t) = 0;
    size_t write(const char *str) {
      if (str == NULL) return 0;
      return write((const uint8_t *)str, strlen(str));
    }
    virtual size_t write(const uint8_t *buffer, size_t size);
    size_t write(const char *buffer, size_t size) {
      return write((const uint8_t *)buffer, size);
    }
    
    size_t print(const __FlashStringHelper *);
    size_t print(const String &);
    size_t print(const char[]);
    size_t print(char);
    size_t print(unsigned char, int = DEC);
    size_t print(int, int = DEC);
    size_t print(unsigned int, int = DEC);
    size_t print(long, int = DEC);
    size_t print(unsigned long, int = DEC);
    size_t print(double, int = 2);
    size_t print(const Printable&);

    size_t println(const __FlashStringHelper *);
    size_t println(const String &s);
    size_t println(const char[]);
    size_t println(char);
    size_t println(unsigned char, int = DEC);
    size_t println(int, int = DEC);
    size_t println(unsigned int, int = DEC);
    size_t println(long, int = DEC);
    size_t println(unsigned long, int = DEC);
    size_t println(double, int = 2);
    size_t println(const Printable&);
    size_t println(void);
};
```

```cpp:debug_print.h
template <typename T> void debugPrint(T data) {
#if (DEBUG_PRINT==1)
  Serial.println(data);
#endif
}
```