もう観葉植物を枯らさない！ Arduinoで鉢植えをしゃべらせる


どうも観葉植物を枯らしてしまう…というあなたへ。

うちには蘭の鉢植えがあるのですが、水やりを忘れてふと気づくとしなびてた…なんてことが良くあります。これをなんとかしたいと思い、家に転がってた[Arduinoをはじめようキット](http://www.amazon.co.jp/%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%82%B9-Arduino%E3%82%92%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86%E3%82%AD%E3%83%83%E3%83%88/dp/B0025Y6C5G)と[Groveの水分センサ](https://www.switch-science.com/catalog/814/)を使って、水がほしい時には鉢植えに自分で喋らせるようにしてみました。(自動水やりをお求めの場合は[こちら](http://qiita.com/interestor/items/d59590b64820a8cf973e)を見ると良いかと思います)

乾いてると「のど乾いたんだけど〜」、水をあげると乾燥具合いによって「ありがとう〜」もしくは「死ぬかと思った…」と喋ります。

![talkative_plant.png](https://qiita-image-store.s3.amazonaws.com/0/2758/7e7c16e0-fb08-80b9-872e-343094c5edae.png "talkative_plant.png")

動画: https://www.youtube.com/watch?v=EL0n1X9qQeo

半月くらい運用していますが、忘れた頃に呟いてきて、水をあげるとお礼を言われるのでちょっと和むと夫に好評です。とりあえず運用はじめてから枯れてない！

私が Arduino 初心者なので説明も割と初心者向けのつもりです。

# 要るもの

* Arduino Uno ([Arduinoをはじめようキット](http://www.amazon.co.jp/%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%82%B9-Arduino%E3%82%92%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86%E3%82%AD%E3%83%83%E3%83%88/dp/B0025Y6C5G)のものを使用)
* ジャンパケーブル (オス - オス) 6本 ([Arduinoをはじめようキット](http://www.amazon.co.jp/%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%82%B9-Arduino%E3%82%92%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86%E3%82%AD%E3%83%83%E3%83%88/dp/B0025Y6C5G)のものを使用)
* [Grove 水分センサ](https://www.switch-science.com/catalog/814/)
* [Grove - 4ピン変換ケーブル](https://www.switch-science.com/catalog/1252/)もしくは[Grove ベースシールド](https://www.switch-science.com/catalog/1293/)
* 100均で売ってるiPhone用とかのミニスピーカー
* はさみ、セロテープ、ライター
* PC (Mac)

半田は使ってもいいけどなくても大丈夫。PC 側は Mac じゃなくてもいいですが説明は Mac 向けです。

# Arduinoの準備

おもむろに [Getting Started w/ Arduino](http://arduino.cc/en/guide/macOSX) のページを開いて言うとおりにします。

1. [ここ](http://arduino.cc/en/main/software)から Mac OS X 向けのソフトをダウンロードしてインストール
1. ArduinoとPC (Mac) をUSBで接続する
1. 1.で入れたArduinoアプリを立ち上げる
1. ツール (Tools) -> マイコンボード (Board) から Arduino Uno を選択
1. ツール (Tools) -> シリアルポート (Serial Port) から /dev/tty.usbmodemXXX を選択

ここからはサンプルとかLチカとかすっ飛ばして、とにかく音をだす準備をしていきます。

1. 音を出す適当なサンプルをスケッチに貼りつける ([こちら](http://qiita.com/tadfmac/items/7fb26596fe6240959c48)を参考に[こんな感じのコード](https://gist.github.com/kinu/58163b63105cd54ea614)をコピペ)
1. 上のツールバーから「→」印のアイコンをクリックしてArduinoに書き込む


![arduino_upload.png](https://qiita-image-store.s3.amazonaws.com/0/2758/4d4def89-de61-68df-0883-3eda60416ce6.png "arduino_upload.png")

これでスケッチで指定したピン (例えば `pinMode(8, OUTPUT)` としてたらピン8) から音が出力されている状態になります。

# スピーカーの準備

Arduinoに接続するスピーカーを作ります。安くてまあまあ聞こえるものが欲しいということで、100均で打ってるiPhone用のスピーカーを使いました。こんなの

![mini-speaker.png](https://qiita-image-store.s3.amazonaws.com/0/2758/e2c7c5e1-ddb9-0b09-b5e4-012300a087d8.png "mini-speaker.png")

1. 100均スピーカーのケーブルの途中をはさみでちょん切る
1. 切ったところからケーブルの周りの皮膜をある程度はがす
1. 3本のエナメルでコーティングされてる線が出てくるので、ライターなどであぶってからウェットティシューで拭くなどしてエナメルをはがす ([参考にしたページ](http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1326995948))
1. ジャンパケーブルとエナメルをはがした線をつなげる。半田でやるといいと思いますが、ぐるぐる巻きつけてセロテープとかでも可

ここで出てきた3本の線がそれぞれ GND、右、左のはず。他のスピーカーでもそうなのかわかりませんが、うちのケーブルでは黄色が GND、が右、緑が左っぽかったので、黄色の線を Arduino の GND に、赤か緑を音を出力しているピン (上の例ではピン8) に挿します。

![cable.png](https://qiita-image-store.s3.amazonaws.com/0/2758/37bbcad5-ba3d-e2b8-03a9-8485ad6ee995.png "cable.png")

これでつないだあと、スピーカーからピロピロ音が鳴ってれば準備完了！ （スピーカーは割と適当につないでも壊れない（はず）なので、うまくいかなかったら適当に線を差し替えてみます）

# 音声データの準備

調べてみると Arduino Uno があれば特別なシールドなどはなくても単体で PCM　音声データを鳴らすことができるようです。そこで、Arduino で使える 8bit PCM データを用意することにします。

今回は日本語を喋らせたいので、Mac OS Xで動作する日本語音声合成ソフトウェア [SayKana](http://www.a-quest.com/quickware/saykana/) を使い、それが出力するAIFFファイルからPCMデータを取り出す[適当なGoプログラム](https://github.com/kinu/AIFF2Arduino)を書きました。

1. SayKanaを http://www.a-quest.com/quickware/saykana/ からダウンロードしてインストール
1. (go言語環境がなければ) go言語の Mac OS X 用の pkg ファイルを[公式サイト](https://golang.org/dl/)からダウンロードしてインストール
1. https://github.com/kinu/AIFF2Arduino から `aiff2arduino.go` をダウンロード

SayKanaをインストールすると `saykana` コマンドで日本語をしゃべらせることができるようになります（ひらがなのみ）。また、シングルクオートでアクセントが指定できるようです。例えばこんな感じでしゃべります：

```bash:Terminal
$ saykana "あり'がとう"
```

`-o`オプションで出力ファイルを指定すると AIFF ファイルとして音声を出力できるので、これで必要なデータを作ります。

```bash:Terminal
$ saykana "の'ど/かわ'いたんだけど" -o thirsty.aiff
$ saykana "あり'がとう" -o thankyou.aiff
$ saykana "しぬ'かとおもった" -o wasdying.aiff
```

これで3種類の音声データができました。ここで作られるAIFFファイルは 16bit/8kHz のPCMデータです。これを`aiff2arduino.go` でArduinoで使えるように 8bit/8kHz のデータに変換し、8bit の数値の配列にします。

```bash:Terminal
$ go run aiff2arduino.go --dither thirsty.aiff > thirsty.h
$ go run aiff2arduino.go --dither thankyou.aiff > thankyou.h
$ go run aiff2arduino.go --dither wasdying.aiff > wasdying.h
```

それぞれ中身は次のようなデータになります：

```thirsty.h
prog_uchar thirsty[] PROGMEM = {
  128, 128, 128, 127, ...
};
```

PROGMEM は SRAM のかわりに Flash メモリのプログラム領域へデータを格納するためのキーワードです。詳しくは [Arduinoのリファレンス](http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=1824)を参照してください。

# Grove水分センサの準備

Grove のセンサはすべて4ピンが1つになったソケットのようなコネクタを持っています。これを Arduino やブレッドボードに接続するには、[Grove ベースシールド](https://www.switch-science.com/catalog/1293/)を使うのが普通のようです。ただ、今回はセンサーを1つつなぐだけなので、お手軽に[Grove - 4ピン変換ケーブル](https://www.switch-science.com/catalog/1252/)を使って普通のジャンパケーブルで Arduino と接続しました。

![grove_arduino.png](https://qiita-image-store.s3.amazonaws.com/0/2758/ac5038ec-7f62-aba7-bbb4-428caa4c243b.png "grove_arduino.png")


Grove 水分センサーの回路図は[スイッチサイエンスの販売ページ](https://www.switch-science.com/catalog/814/)に載っています。ピンは端から GND, VCC, (No Connection), SIG になっているので、GND を Arduino の GND に、VCC を Arduino の電源 (5V) に、SIG を A0 につなぎます。

水分センサの値は `analogRead(A0)` とすると読み出せます(A0につないでる場合)。簡単ですね〜

```cpp:moisture_sample.ino
void setup() {
  Serial.begin(9600);
}

void loop() {
  // 1秒毎に値を読みだしてシリアルに書く
  int sensorValue = analogRead(A0);
  Serial.println(sensorValue);
  ...
  delay(1000);
}
```

`Serial.println`で書きだした値は、Arduinoアプリのツールバーの虫眼鏡アイコンをクリックすると見ることができます。

![arduino_console.png](https://qiita-image-store.s3.amazonaws.com/0/2758/d87cda7b-c7f5-2d49-1a95-aafdb0949979.png "arduino_console.png")


# Arduinoスケッチ(コード)の準備

(細かい説明はいいからすぐ喋らせたい！という方は[ここ]
(http://qiita.com/kinu/items/6cd5da0415e31834e7da#%E5%85%A8%E9%83%A8%E3%81%A4%E3%81%AA%E3%81%92%E3%81%A6%E3%81%BF%E3%82%8B)まで飛ばしてOK)

## Fast PWMモードの設定

基本的には Arduino Uno のシステムクロックは 16 MHz なのですが、通常時は Pin 9, 10 (Timer/Counter1), 3, 11 (Timer/Counter2) は 490Hz、Pin 5, 6 (Timer/Counter0) は 1kHz で動作しています。これではどうやっても出力したい音声波形を作ることができないので、PWM (Pulse-with modulation) の波形生成周期をあげる設定をする == Fast PWM モードに設定する必要があります。


詳細は[[1]](http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM)[[2]](http://playground.arduino.cc/Code/PCMAudio)[[3]](http://makezine.com/projects/make-35/advanced-arduino-sound-synthesis/)[[4]](http://ameblo.jp/aretaumitohirunosunahama/entry-11442063598.html)や[ATmega328のデータシート](http://www.atmel.com/Images/doc8161.pdf)を読めばわかりますが、ざっくりはしょって書くと setup 関数内で以下のようにすると Pin 3 が Fast PWM モードになり、最大周期の 16 MHzで 8-bit タイマが動作します。

```cpp:fastpwm_sample.ino
void setup() {
  // PIN#3/#11: Timer/Counter2
  pinMode(3, OUTPUT);

  // http://www.atmel.com/Images/doc8161.pdf
  // Non-inverting fast PWM mode on Pin 3.
  // COM2B1:0 ==  10: Non-inverting mode
  // WGM22:0  == 011: Fast PWM mode, 256 cycle (16MHz / 256 == 62.5kHz)
  // CS2:0    == 001: No prescaler (runs at maximum rate, 62.5kHz)
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(CS20);
}
```

この状態で `OCR2B` に 0 から 255 の値をセットすることで PWM の duty (タイマが1周する間にどれくらい出力矩形を High にするか) を変更して出力波形を作ります。

SayKana コマンドでは 8kHz のリニア PCM が作られるので、`OCR2B` の値を 8kHz で更新すれば音が出ます。8kHz は Arduino のタイマをもう1つ使って割り込みハンドラ (ISR) として書いてもいいのですが([サンプル](http://playground.arduino.cc/Code/PCMAudio))、[こちらの例](http://ameblo.jp/aretaumitohirunosunahama/entry-11442063598.html)のように単にループで 1 sec / 8000 = 125 msec (`delayMicroseconds(125)`) をはさんで出力するだけでも動きます。

## PCM音声データの出力

上で準備した音声データを Arduino コードから読むには、`aiff2arduino.go` で作成したヘッダファイルをインクルードし、プログラム領域におかれたメモリを読み出すユーティリティ関数 `pgm_read_byte_near()` を使います。例えば `thirsty.h` のデータをディレイを入れながら `OCR2B` にセットする場合、`thirsty.h` ファイルを Arduino のコードと同じディレクトリにコピーし、以下のようにします：

```thirsty.h
prog_uchar thirsty[] PROGMEM = {
  128, 128, 128, 127, ...
};
```

```cpp:pcm_sample.ino
#include "thirsty.h"
#define ARRAYSIZE(x)  (sizeof(x) / sizeof((x)[0]))

void play() {
  for (int i = 0; i < ARRAYSIZE(thirsty); i++) {
    OCR2B = pgm_read_byte_near(&thirsty[i]);
    delayMicroseconds(125);
  }
} 
```

これで Arduino がしゃべりました！

## 全部つなげてみる

これでほぼ準備がすんだので、ぜんぶをつなげてみます。Grove 水分センサでは水分量に応じて以下のように値が変わるので、200〜250 くらいを閾値にして乾燥してるかどうかを判断することにします。

| 状態  | 値  |
|:------|-----|
| 乾いた土の中 | 0〜300 |
| 潤った土の中 | 300〜700 |
| 水中 | 700〜950 |

乾燥していたら10分おきに「のどかわいた」、急に水分量があがったら乾燥度に応じて「ありがとう」あるいは「死ぬかと思った」としゃべらせることにします。（本当は人感センサもつけて、乾燥してるときは人が前に来たらしゃべる方がそれっぽいと思うのですが、手元にないので今回はナシで…）

だいたい以下の様な感じになりました：

```cpp:talkative_plant.ino
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#include "thirsty.h"
#include "thankyou.h"
#include "wasdying.h"
#define ARRAYSIZE(x)  (sizeof(x) / sizeof((x)[0]))

// センサはA0に、スピーカーはピン3に接続する
const int sensorPin = A0;
const int speakerPin = 3;

void setup() {
  Serial.begin(9600);

  // PIN#3/#11: Timer/Counter2
  pinMode(speakerPin, OUTPUT);

  // http://www.atmel.com/Images/doc8161.pdf
  // Non-inverting fast PWM mode, with no prescaling on pin 3.
  // COM2B1:0 ==  10: Non-inverting mode
  // WGM22:0  == 011: Fast PWM mode, 256 cycle (16MHz / 256 == 62.5kHz)
  // CS2:0    == 001: No prescaler (runs at maximum rate, 62.5kHz)
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(CS20);
  OCR2B = 0;
}

volatile unsigned long lastPlay = 0;
volatile int lastSensorValue = 0;

void playSound(prog_uchar* audio, int audio_length, bool force) {
  // Don't spam too much.
  if (!force && millis() < lastPlay + 600000) {
    return;
  }
  lastPlay = millis();
  for (int i = 0; i < audio_length; i++) {
    OCR2B = pgm_read_byte_near(&audio[i]);
    delayMicroseconds(125);
  }
}

void loop() {
  int sensorValue = analogRead(sensorPin);
  int diff = sensorValue - lastSensorValue;
  delay(1000);
  Serial.println(sensorValue);

  // http://seeedstudio.com/wiki/Grove_-_Moisture_Sensor
  // dry: 0-300
  // humid: 300-700
  if (sensorValue < 200) {
    playSound(thirsty, ARRAYSIZE(thirsty), abs(diff) > 100);
  } else if (lastSensorValue < 50) {
    playSound(wasdying, ARRAYSIZE(wasdying), true);
  } else if (diff > 100) {
    playSound(thankyou, ARRAYSIZE(thankyou), true);
  }

  lastSensorValue = sensorValue;
}
```

あとはこのスケッチをボードに書き込んで、

1. Groveセンサを A0 (とGNDと5V) に
1. スピーカーをピン 3 (とGND) に
1. センサを植木鉢に刺して設置すれば**完成**！

冬の乾燥もおうちハックで乗り切ろう！

なお、全コードは https://github.com/kinu/ArduinoTalkativePlant/
こちらにおいてます。

# 参考にさせて頂いたページ

* [Arduino公式: Getting Started](http://arduino.cc/en/Guide/MacOSX)
* [Qiita: [WIP] Arduinoで音を出したい（メモ）](http://qiita.com/tadfmac/items/7fb26596fe6240959c48)
* [イヤホン線の切断＆接続方法 (Yahoo!知恵袋)](http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1326995948)
* [Audio Interchange File Format: "AIFF" (PDF)](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/Docs/AIFF-1.3.pdf)
* [ATmega328 のデータシート (PDF)](http://www.atmel.com/Images/doc8161.pdf)
* [【冬休みの工作】Arduinoをしゃべらせてみた | あれたうみと ひるのすなはま](http://ameblo.jp/aretaumitohirunosunahama/entry-11442063598.html)
* [Makezine: Advanced Arduino Sound Synthesis](http://makezine.com/projects/make-35/advanced-arduino-sound-synthesis/)
* [Arduino Playground - PCM Audio](http://playground.arduino.cc/Code/PCMAudio)
* [Grove - Moisture Sensor - Wiki](http://www.seeedstudio.com/wiki/Grove_-_Moisture_Sensor)