Arduino
おうちハック

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

More than 3 years have passed since last update.

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

うちには蘭の鉢植えがあるのですが、水やりを忘れてふと気づくとしなびてた…なんてことが良くあります。これをなんとかしたいと思い、家に転がってたArduinoをはじめようキットGroveの水分センサを使って、水がほしい時には鉢植えに自分で喋らせるようにしてみました。(自動水やりをお求めの場合はこちらを見ると良いかと思います)

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

talkative_plant.png

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

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

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

要るもの

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

Arduinoの準備

おもむろに Getting Started w/ Arduino のページを開いて言うとおりにします。

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

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

  1. 音を出す適当なサンプルをスケッチに貼りつける (こちらを参考にこんな感じのコードをコピペ)
  2. 上のツールバーから「→」印のアイコンをクリックしてArduinoに書き込む

arduino_upload.png

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

スピーカーの準備

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

mini-speaker.png

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

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

cable.png

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

音声データの準備

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

今回は日本語を喋らせたいので、Mac OS Xで動作する日本語音声合成ソフトウェア SayKana を使い、それが出力するAIFFファイルからPCMデータを取り出す適当なGoプログラムを書きました。

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

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

Terminal
$ saykana "あり'がとう"

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

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

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

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のリファレンスを参照してください。

Grove水分センサの準備

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

grove_arduino.png

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

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

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

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

(細かい説明はいいからすぐ喋らせたい!という方はここまで飛ばして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][2][3][4]ATmega328のデータシートを読めばわかりますが、ざっくりはしょって書くと setup 関数内で以下のようにすると Pin 3 が Fast PWM モードになり、最大周期の 16 MHzで 8-bit タイマが動作します。

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) として書いてもいいのですが(サンプル)、こちらの例のように単にループで 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, ...
};
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分おきに「のどかわいた」、急に水分量があがったら乾燥度に応じて「ありがとう」あるいは「死ぬかと思った」としゃべらせることにします。(本当は人感センサもつけて、乾燥してるときは人が前に来たらしゃべる方がそれっぽいと思うのですが、手元にないので今回はナシで…)

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

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) に
  2. スピーカーをピン 3 (とGND) に
  3. センサを植木鉢に刺して設置すれば完成

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

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

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