Hatena::ブログ(Diary)

言語ゲーム このページをアンテナに追加 RSSフィード Twitter

とあるエンジニアが嘘ばかり書く日記
ホームページ | blog | twitter

2008-12-10

世界聴診器を作るその1

では、Arduino を使って世界聴診器 http://swikis.ddo.jp/WorldStethoscope/2 を作ってゆきます。世界聴診器とは、アナログ入力の電圧を元に周波数が変化するような物ですが、これだけだと実は Arduino で実現するのは超たやすいです。例えば http://todbot.com/blog/bionicarduino/ にある Theremin のサンプルが分かりやすいです。digitalWrite() 関数で入力された電圧を元に delayMicroseconds() でウエイトをかけながら出力信号を ON/OFF するだけです。

しかしこのままでは世界聴診器としては使えません。タイミングが厳密ではなく音がキタナイのと、周波数ではなく、波長が電圧に比例しているからです。周波数と電圧が比例するというのは、高い電圧で高い音が鳴った方が直感的だという阿部さんのこだわりなのだろうと思いますが、尊重してそのまま作ります。以下のプログラムは Analog 1 が入力、Digital 9 が音声出力になっています。5V - 光センサ - Analog1 - 抵抗 - GND のように接続すると、テルミンみたいに遊べますし、もちろん世界聴診器モーフでもかっちり動きます。

今回は Output Compare Register A というのを使って、プリスケール 32 のカウンタを 4 周期固定で回しています。するとカウンタ周期は 16MHz / 32 / 4 = 125KHz となり、それを基準周波数として、欲しい周波数を作り出します。125KHz じゃ説明しにくいので、もしも 基準周波数が 10KHz で欲しい周波数が 4KHz だとすると、

  • アルゴリズム
    • 初期値:
      • 余分周波数 := 基準周波数
      • 目的周波数 := 鳴らしたい周波数 * 2 (ON/OFF を切り替えるので)
    • タイマ発生: 余分周波数 := 余分周波数 - 目的周波数
    • 余分周波数 < 0 なら一回休み
    • 余分周波数 >= 0 なら
      • トグル
      • 余分周波数 := 基準周波数 + 余分周波数
  • 例: 4KHz が欲しい時は、4KHz の倍の 8KHz を目的周波数として
    • 10KHz + 0KHz - 8KHz = 2KHz 待ち
    • 2KHz - 8KHz = -6KHz ON
    • 10KHz + -6Hz - 8KHz = -4KHz OFF
    • 10KHz + -4Hz - 8KHz = -2KHz ON
    • 10KHz + -2Hz - 8KHz = 0KHz OFF
    • 10KHz + 0Hz - 8KHz = 2KHz 待ち
    • 2Hz - 8KHz = -6KHz ON

のようにすると、10Hz の 2/5 すなわち 4KHz の矩形波を足し算と引き算だけで作る事が出来ます。途中で波長を求めなくて良いのがミソです。最高でも 5KHz 出せれば良いので基準周波数として 10KHz あれば十分そうですが、なんか音が汚かったのであれこれ試して 125KHz にしました。それでもまだ変な音が混じりますがマルチメーターで測ると割とちゃんと思った通り周波数がとれるのでこれで良いという事にします。

どういうわけだか最新版の開発環境では動かず、Arduino 0010 を使いました。

// World Stethoscope Clone
// for Arduino 0010
#define PRESCALE 32
#define COUNTER_MAX 4
#define PRECISION 1024
#define FREQ_SIGNAL_MAX (5000L * 2)

#define PIN_OUTPUT 9
#define PIN_INPUT 1

long SAMPLING_RATE = F_CPU / PRESCALE / COUNTER_MAX;
long margin = SAMPLING_RATE;
long frequency = FREQ_SIGNAL_MAX;
long nextFrequency = FREQ_SIGNAL_MAX;
char signal = 0;

void setup() {
  pinMode(PIN_OUTPUT, OUTPUT);

  TCCR2A = 1 <<WGM21 | 0<<WGM20; // Clear Timer on Compare Match
  TCCR2B = 0<<WGM22 | 0<<CS22 | 1<<CS21 | 1<<CS20; // clk/32 prescale
  OCR2A = COUNTER_MAX - 1; // Output Compare Register A
  TIMSK2 = 1<<OCIE2A; // Timer/Counter2 Output Compare Match A Interrupt Enable
  TCNT2 = 0; // Reset Timer/Counter2
}

ISR(TIMER2_COMPA_vect) { // Timer/Counter2 Overflow

  margin -= frequency;
  if (margin <= 0) {
    margin += SAMPLING_RATE;
    digitalWrite(PIN_OUTPUT, signal); // Output signal
    signal ^= 1;
    frequency = nextFrequency;
  }
}

void loop() {
  int inputValue = analogRead(PIN_INPUT) + 1; // 1 ... 1024
  long freq = inputValue * FREQ_SIGNAL_MAX / PRECISION;
  nextFrequency = freq;
}

追記

nekosan さんのブログ http://brown.ap.teacup.com/nekosan0/382.html で紹介して頂きました。有り難うございました!で、ソースが切れる件ですが、はてなダイアリーのバグのようですが、再現方法が分かりません。とりあえず上では TCCR2A の行の 1<< を 1 << に変えるとなおりました。他にも使ってる部分あるのに、なんでかな???

nekosannekosan 2008/12/10 15:17 propellaさん、こんにちは。

>どういうわけだか最新版の開発環境では動かず、Arduino 0010 を使いました。

バージョンによって動く/動かないが生じるのは、今回の場合コンパイラの最適化処理が影響しているような気がします。

貼られているスケッチは私の環境ではまだ実行していないのですが、眺めていて1ヶ所気になるところがありました。volatile修飾子をつけると解消できるような気がします。具体的には、

long nextFrequency = FREQ_SIGNAL_MAX;
の変数宣言文を
volatile long nextFrequency = FREQ_SIGNAL_MAX;

とすることで上手くいくのではないかと思います。

volatile修飾子については公式サイトの
http://arduino.cc/en/Reference/Volatile
ここにもあるのですが、説明文が少し判り難かったので、今回の件に特化して触れます。nextFrequency変数はmain側(loop関数)だけで更新していますが、直接ISR(TIMER2_COMPA_vect)を呼び出しているわけではないため(call,called関係が無い)、割り込みルーチン内だけで眺めるとnextFrequencyを更新しているところが無いと解釈され、最適化処理でfrequencyへの代入文が1回だけに済まされてしまう(最適化される)ことになってしまいます。そうすると、実際にはnextFrequencyが変化しているにも関わらず、その値がfrequency変数に反映されなくなります。
この最適化を明示的に止めさせるのがvolatile修飾子ということです。

arduinoの場合は、公式サイトの説明によると割り込み処理(ピンチェンジやタイマー割り込み)の時に使うと書かれていました。

というわけで、多分volatile修飾子をつけると0012でも動くようになる気がします。お時間あるときにお試しいただければと存じます。(それでも動かなかったら、ゴメンナサイ)

nekosannekosan 2008/12/10 15:34 http://www.kumikomi.net/article/explanation/2003/10kumi/13.html
こちらにもっと解り易い解説が有りました。

abee2abee2 2008/12/11 01:09 さすが仕事が早いですね。締切りのプレッシャーの大きさを感じます。
おっしゃるとおり、電圧と周波数が線形に比例するのは譲れない線です。
出力は圧電ブザーですよね。歪み成分がむちゃくちゃありそうな気がするのにFFTでちゃんと取れるというのは驚きです。もし可能ならMP3かなんかで音をもらえますか。波形を見てみたいです。
arduinoは現行の世界聴診器より安いので、使えるものならひとつのオプションとして考えたいと思います。

propellapropella 2008/12/11 02:55 nekosan さん。volatile のお話ありがとうございました!残念ながら Arduino 0012 ではやっぱり動きませんでしたが、全然知らなかったので勉強になりました。普段こういうプログラムを書かないので、もしや組み込みならではの問題かと思うとなかなかワクワクします。

阿部さん。音声ファイルを http://languagegame.org/tmp/sekaclone.wav に置きましたので、よろしければ参考にしてください。出力は小さいスピーカーと抵抗をつなげてモニタしています。arduino の値段は確かに魅力ですね。3000 円くらいで買えるので、試しに一個買ってみる事をお勧めしますよ!

nekosannekosan 2008/12/12 00:14 propellaさん、こんにちは。
そうですか、0012で動きませんでしたか…。

ちなみに、面白そうだったのでスケッチをお借りしてうちでも
実行してみました。
0012を使って、出力は圧電スピーカーに、入力は可変抵抗で
電源を分圧して0〜5Vを作り出して、可変抵抗をくるくると
回してみました。

抵抗の角度に合わせてプーとかピーと、鳴っている
音の高さが変わって聞こえました。
(なんか、歯医者さんのあの音みたいでした(゜o゜))

なんとなく動いているようです。

(ちなみにvolatileは付けても付けなくても
 一緒でした…あまり関係なかったみたいです)

abee2abee2 2008/12/12 00:44 WAVありがとうございます。
すばらしいです。これは使えます。4KHz弱まで出ていますね。
高調波もありますが、無視できるレベルです。
購入を検討します。クリップしやすそうなのとかわいいのでLilyPadにしようと思うのですが、一緒ですよね。

propellapropella 2008/12/12 03:46 nekosan さん。試していただいて有り難うございます。歯医者さんの音、嫌ですね〜。世界聴診器はシリアル入力の無いパソコンでどうやって簡単に外部センサーとやり取りするかという話から生まれた計画で、本当はその歯医者さんの音をパソコンのマイク入力に突っ込んでソフトで取り込んで使うのです。実は Arduino を使ってる時点でシリアルがある事前提なので、あまり意味ないですけど、いったん音を経由する事で何が起こってるか分かりやすくする狙いもあります。

阿部さん。4KHzまでしか出てないのは、適当に可変抵抗を回てしまったからで、一杯まで回したら正確に 5KHz 出ます。LilyPad 面白そうなので、是非試してみてください。USB アダプタが必要なのでちょっと高くつくかも。。。ソースコードは同じで行けます。クロックが 8MHz と低いので音がちょっと悪くなるかも知れません。このあと 8MHz の attiny チップを裸で使って世界聴診器を作る予定です。そうなると数百円の出費で出来る事になります。

abee2abee2 2008/12/12 05:13 数百円になるのなら夢広がりまくりです。
シリアル-USBアダプタはCロボのがあるので大丈夫でしょう。
LilyPadは服に縫い付けてスタンドアロンでつかうのが前提なので、電池ボックスとか、かわいいセンサとかあるのもポイント高いです。

propellapropella 2008/12/12 06:58 LilyPad が使ってる UART ってシリアルともちょっと違うみたいなので、詳しい人に聞いた方が良いと思います。それよりも阿部さんにお裁縫が出来るのかというのが心配ですが。


カレンダー
<< 2008/12 >>
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
最新コメント一覧