「PIC AVR 工作室」サイトの日記的なブログです。
サイトに挙げなかった他愛ないことを日記的に書き残してます。
PIC AVR 工作室 ブログ



昨日のarduino用FFTライブラリのスケッチを
もう少し見直ししてみた。


ライブラリのページに細かい使い方とか書いて
なかったので、FFTとTimerOneのライブラリソース
をザックリ読んでみた。

その内容を加味して、なんでサンプルレートを
上げた時にフリーズしちゃうのかを考えてみた。
どうやら、スケッチ中でcliやseiで割り込み許可を
直接弄ってること…特にFFTの計算と数十文字の
シリアル出力を行うまで一切の割込みを禁止したまま
っていうのは長すぎたみたい。まぁ普通に考えると
行儀悪すぎだな。きっとここが原因だろう。

と思って、タイマ1割込みの制御をTimerOneオブジェクト
のdetachInterruptとattachInterruptを使うように
修正してみた。↓こんな感じ。

#define LIN_OUT8 1 // use the lin output function
#define FFT_N 64 // set number of point fft

#include <FFT.h> // include the library
#include <TimerOne.h>


volatile int i;    // index for data points


void timerIsr()
{
  while(!(ADCSRA & 0x10)); // wait for adc to be ready
  ADCSRA = 0xf5; // restart adc
  byte m = ADCL; // fetch adc data
  byte j = ADCH;
  int k = (j << 8) | m; // form into an int
  k -= 0x0200; // form into a signed int
  k <<= 6; // form into a 16b signed int
  fft_input[i] = k; // put real data into even bins
  fft_input[i+1] = 0; // set odd bins to 0
  i += 2;
}


void setup() {
  Serial.begin(115200); // use the serial port
  TIMSK0 = 0; // turn off timer0 for lower jitter
  ADCSRA = 0xe5; // set the adc to free running mode
  ADMUX = 0x40; // use adc0
  DIDR0 = 0x01; // turn off the digital input for adc0
  
  i = 0;

  Timer1.initialize(30); // set a timer of length 100 microseconds (10000Hz)
  Timer1.attachInterrupt( timerIsr ); // attach the service routine here
}

void loop() {
  while(i < FFT_N*2) {
  }

  Timer1.detachInterrupt();
//  cli();  // UDRE interrupt slows this way down on arduino1.0
  fft_window(); // window the data for better frequency response
  fft_reorder(); // reorder the data before doing the fft
  fft_run(); // process the data in the fft
  fft_mag_lin8(); // take the output of the fft
  i = 0;
  for (int l=0; l<FFT_N/2; l++){
    Serial.print(fft_lin_out8[l]); // send out the data
    Serial.print(",");
  }
  Serial.println();    
  Timer1.attachInterrupt( timerIsr );
//  sei();
}

とりあえずサンプルレートを上げても普通に動く
様になった。よかった、よかった。



んで、音声信号を取り込むことを考えると、やっぱ
サンプリングレートを40000spsくらいまで上げられ
ないかという欲望がわいてくるのは必然。でも、
あまり割り込み頻度を上げすぎると、きっとどこかで
割り込み処理が完了する前に次の割込み要因が発生したり
して破綻するはず。破綻しないところを探ってみる。

まずは10000sps(ナイキスト周波数は5000Hz)、大丈夫。
20000sps(同10000Hz)、大丈夫。25000sps(同12500Hz)、
大丈夫。40000sps(同20000Hz)、駄目。

割り切れる綺麗な周波数では12500Hzあたりまでって
ことになるんだけど、もうちょっといけるはずだろう
と思って33333sps(同16667Hz)を試す…大丈夫。
どうやら、16667Hzと20000Hzの途中に境目があるっぽい。

(ちなみに、サンプリングレートを25000sps、33333sps、
 40000spsとする時のTimer1.initialize(xxx)で指定する
 引数は、それぞれ40、30、25となります…1000000÷指定値
 なので)

まぁ、16000Hz以上となるとあまりこだわっても仕方ない
と思うのでこれでよしとすることに。

ただねぇ…mp3プレイヤーとかから直接音声を取り込ん
じゃおうとすると、16667Hzを越える周波数成分は
折り返しノイズとして拾っちゃうはずだから、もし
LPFで削り落としておかないと、13000hz付近~16667Hz
のところに折り返されたノイズが載っちゃうんだよな。
この程度と無視出来るなら、LPF無しでもそのままで
良いんだけどな。

補足:このスケッチでは、サンプル数を64点FFTとして、
あとFFT結果のスカラー化の後で対数を取らずに
8ビット値のまま扱ってみた。
(この方が、シリアルモニタで結果見るときに見やすい)

グライコのような処理をするなら、当然対数で扱った
方が都合が良いので、昨日みたいに対数指定にして
おいた方がよいかと。

とりあえず音声信号をリアルタイムでグリグリ処理する
用途でも結構使えそうなことはわかった。

あとはサンプルレートをどこまで上げられるかなんだ
けど、これについてはいくつか案を。
一つは、10ビットADCをやめて8ビットADCにすること。
ハンドリングする変数の数が減るので、少しだけ
計算時間を短く出来るかと。
もう一つは、タイマ割り込み処理内のスキーム見直し。
もうちょっと効率上げられるんじゃないかと。

そこまでやればギリギリ40000spsくらいまでは
上げられるんじゃないかな?ADCSRAを参照しなくても
良いんじゃね?とか、配列のインデックスはi++と
指定しておけば、その後ろのi+=2も要らなくて、
多分この辺は効率的にアセンブリ変換できるんじゃ
ないかと。

とりあえずFFTはこんなところまで見ておけば、
あとでサクサク使えそうかな。


http://zasshi.news.yahoo.co.jp/article?a=20121113-00000000-natiogeo-int
今日の天気と明日の天気が入れ替わってくれればなぁ…


http://zasshi.news.yahoo.co.jp/article?a=20121116-00000007-sasahi-ent
脱出ゲームの記事。へぇ、「大人は部活を求めている」
のかぁ。ニコニコ技術部とかじゃ(以下略)

simさんのツイッター経由でおち(転生準備中) さんの
ツイッター
に。
http://www.nxp-lpc.com/lpc_micon/cortex-m0+/lpc800/
おぉ。NXPは8ピンのARM出すのか!しかも出力ピン
はスイッチマトリックスでコンフィギュアラブルなんだな。
弱点は…ないんじゃね?とか思ったんだけど、もし
発売されたら… 8ピンだろ?



こんな感じになるのか?



コメント ( 0 )




この間Arduinoの寄贈ライブラリを改めて眺めてたら
見つかったFFTライブラリ。(いつの間に公開されてた
んだろうね?)

サンプルプログラムが付いてたので、とりあえず
コンパイルして流し込んでみることに。お試し。


…どうやらFFT結果を16進数のまま吐き出す仕様に
なっているみたいなので、シリアルモニタじゃぁ
訳がわかんない状態。

あと、適当にADCからサンプリングしてFFTを掛けて
いるっていう仕様なので、どんな周波数の信号を
検出してるのかも全然不明。

これじゃぁ嫌なので、タイマ割込み使ってサンプリング
させつつ、テキスト文字出力するように変更してみた。


…とりあえずそれっぽいデータが吐き出されてきた。
うん。ただ、実数、虚数の平方和の平方根を取ってから
log(対数)取ってるので、なんとなく数字が微妙か?

対数取らずにリニアに出力するように変更してみた。
スケッチはこんな感じ。

#define LIN_OUT 1 // use the lin output function
#define FFT_N 32 // set number of point fft

#include <FFT.h> // include the library
#include <TimerOne.h>


volatile int i;    // index for data points


void timerIsr()
{
  while(!(ADCSRA & 0x10)); // wait for adc to be ready
  ADCSRA = 0xf5; // restart adc
  byte m = ADCL; // fetch adc data
  byte j = ADCH;
  int k = (j << 8) | m; // form into an int
  k -= 0x0200; // form into a signed int
  k <<= 6; // form into a 16b signed int
  fft_input[i] = k; // put real data into even bins
  fft_input[i+1] = 0; // set odd bins to 0
  i += 2;
}


void setup() {
  Serial.begin(115200); // use the serial port
  TIMSK0 = 0; // turn off timer0 for lower jitter
  ADCSRA = 0xe5; // set the adc to free running mode
  ADMUX = 0x40; // use adc0
  DIDR0 = 0x01; // turn off the digital input for adc0
  
  i = 0;

  Timer1.initialize(1000); // set a timer of length 100 microseconds (10000Hz)
  Timer1.attachInterrupt( timerIsr ); // attach the service routine here
}

void loop() {
  while(i < FFT_N*2) {
  }

  cli();  // UDRE interrupt slows this way down on arduino1.0
  fft_window(); // window the data for better frequency response
  fft_reorder(); // reorder the data before doing the fft
  fft_run(); // process the data in the fft
  fft_mag_lin(); // take the output of the fft
  i = 0;
  for (int l=0; l<FFT_N/2; l++){
    Serial.print(fft_lin_out[l]); // send out the data
    Serial.print(",");
  }
  Serial.println();    
  sei();
}


ちなみに#define LIN_OUTとか、fft_mag_lin()関数とか、
出力先の配列fft_lin_outとかは、FFT.hの中身を
見ないとどれを指定すればいいかがワカラナイっぽい。
(説明書いてあるページが見当たらなかった)


まぁ、スケッチの通り32点FFTにして、窓関数を掛けて
(なんの窓か良くわかんない)、線形のまま出力として、
ADCのサンプリングはTimerOneライブラリで自由に間隔を
変えられるようにしておいた。

目下1000マイクロ秒毎(1m秒毎)に割り込み発生する
ようにしてあるので、ナイキスト周波数は500Hz。
これに、以前作ったDDSファンクションジェネレータ
100Hzとか200Hzとか適当な周波数で信号を入れて、
FFTを掛けてみた。

確かに想定したところに値が載るのがわかってなにより。
good。


本当は、サンプルレートをもっと高くして10Kspsくらい
にしたかったんだけど、なぜかシリアル出力の文字数
(1行分の文字数)が一定以上多くなるときに、
Arduinoがフリーズを起こしてしまうことが判ったので、
とりあえず1000spsにしてある。…原因不明。
とりあえず32点FFTで数値を16個表示させるには、
このくらいのサンプリングレートが限界ギリギリ。

シリアル出力の時間が結構長く掛かるのはガッテン
承知の助だったので、サンプリング完了からシリアル出力
が済むまで割り込み禁止にしておいたんだけど、
そこらへんが何か悪さしてるみたい。良くわかんない。

TimerOneライブラリか、FFTライブラリか、どっちかが
うまく噛みあわない状態になってるんだろうな…

改めて、対数取る指定と、8ビットに切り詰める指定も
試してみた。動く、動く。

とりあえず今日はココまで。


http://akizukidenshi.com/catalog/g/gM-06255/
STM32F0DISCOVERYが800円。STM32もLPCも買ったまま
あまり手が進んでないんだよな…


http://www.sanspo.com/geino/news/20121115/eco12111512310002-n1.html
なんじゃ?この前身エアバッグの電気自動車…


http://www.youtube.com/watch?v=_kK2xkyXjW4
スペースシャトルが飛んじゃったり、
http://www.youtube.com/watch?v=4QoLmpPX63Y
タイファイターが飛んじゃったり、
http://www.youtube.com/watch?v=A6STlxPrwJE
USSエンタープライズまで…。ラジコンって、
結構自由度高いんだな。
アンドロメダとか、あの形のまま飛ばないかな…

http://www.youtube.com/watch?v=KxLuShpaKrQ
ダクテットファンじゃなくてジェットエンジンの
音だよな。これも。
ラジコン用ジェットエンジンが小さくなったのか、
それとも機体がでかいのか…



コメント ( 0 )