2015年5月7日木曜日

Elecrow発注第二弾:洗面所用LEDランプドライブ基板

長穴の要らないDC Jackが欲しい(右上/下にある3つのでかい穴)

「それってPIR一個でいいんじゃないの?」という声を意識の外に押しやって開発した、洗面室所に入るとライトがフワッと自動点灯し、誰も居ないとすーーーっと消えるLED照明を基板にしました(現状では巨大放熱器によりファンレスで運用しておりますが)。前回よりも「I/Oをいろいろ付けたい、という欲求は必ず出てくる」という反省を元に、当面使う予定のないI2cなども並べてます。右側に横一列にGPIOを並べて、お友達のO野さんから大量にいただいたLEDラインをそのまま差せるようにしています。白、青、緑、黄色、赤…などのLEDが並んでいて、ステータス表示に便利。

発注から到着までは:

4/27 発注、DHL便を指定
5/05 発送
5/07 受取

という感じ。約10日。出荷までの日数は前回より2日遅かったけど、発送からは圧倒的に早いですね(当たり前だけど)。費用は前回は2,202円、基板はほぼ同じ仕様ですが円安とDHLオプションで3,610円になりました。ファミレスでビールでも飲めばこのぐらいは軽く越えちゃうので、「成田届いてるのにまだかよ…」とイライラするよりはマシかもしれません(前回は日本に着いてから7日かかりましたw)。

あと、前回はエアキャップ(プチプチ)で1.5層ほどくるんで封筒に入って届きましたが、今回はDHLを使ったせいかどうかわからないけど、なんと箱入りでした。それも基板の周囲にはしっかりエアキャップが入っていて、空港などで多少ぶん投げられても大丈夫なレベルw 以下開梱図です。





2015年5月2日土曜日

磁束密度をFFTで処理しグラフ表示


わー、なんだか久しぶり。

以前、磁石の動きを検出するのにアナログ出力のホール素子を使ってみたのですが、かなり強力な磁石を使っても10cm程度がやっとで、あとはノイズに埋もれてしまいました。まぁアナログ技術のある人ならもうちょっとマシな結果になるかもしれませんが。

ふと思い立って、FRDM-KL46Zについている電子コンパスMAG3110を使ってみました。測定してみると何もしなくても地球磁場が600-1200ぐらいの値を返してくるのですが、すぐ近くにネオジム磁石をおいてみても200程度しか値が変わりません。ただ、地球磁場は当然ながら安定しているので、ネオジム磁石の動きは十分信号として検出できそうな気配。

ということで、ずっと部品沼で眠っていたAQM1248(秋月の超小型グラフィック液晶)、最速の誉れも高き大浦先生のFFTライブラリなどを寄せ集めて、実験してみました。

■まず予備実験■

FRDM-KL46Z内蔵のMAG3110(ライブラリ:MAG3110…ワーニング出まくるので、あとで直してpush requestしたい)を内蔵の液晶(ライブラリ:SLCD)で表示してみました。MAG3110でボード内部のI2C端子名を指定するんですが、参照した資料が間違っていて苦労しました。
MAG3110 mag(PTE25, PTE24);
これで使えます、ご参考まで。

さて、表示してみると前述の通り、値は向きによって600-1200程度の幅で変化します。そこにネオジム磁石を近づけると200ぐらい増減します。当然離すと急激に弱くなりますが、値としてはわりとはっきりしているので、たとえば自転車のホイール回転のように周波数レンジの決まっているものであれば何とか処理できそう。

ということで信号処理はFFTを使い、表示に超小型グラフィック表示液晶を使うことにしました。

■まずAQM1248■

電子工作を再開したばかりのころ、秋月の「超小型液晶」を基板にナナメにハンダ付けしてしまったのは良い思い出…今も元気に活躍しておられます、ナナメのままで。

ということで、まずフラックスを塗って置いてしばらく放置し一カ所だけハンダ付けをして向き角度などを確認してから装着。FRDM-KL46Zとの配線は以下の通り:

AQM1248FRDM-KL46Z
Vcc3.3v
CSD10
RESETD9
RSD8
SCLKD13
SDID11

mbed用のほどよいライブラリaqm1248a_lcdをリンクしサンプルをそのままコピペして動作を確認。なお、同ライブラリはプラットフォーム(mbedの機種)を識別してGPIOなどを切り替える作りになっているのですが、概ね「LPC1768とKL05、それ以外」という感じなのでLPC1768とKL05, KL46以外の方はソースの中身と配線をご確認の上、お使いになった方が良いです。

一昨日aitendoのカラー液晶に悪戦苦闘したのですが、こっちは一発動作。やっぱ私にはaitendoはハードルが高すぎます。相場?の半値ぐらいで買えたんですが、家でビニール袋から出してみたら基板に液晶が接着済(笑)。工程ミスか指示ミスか知らないけど、液晶が端子を半分覆い隠す状態になっていて普通なら納品できない品だから安かったんでしょうね。まさにジャンク。

まぁそれはともかく、これで表示はばっちり。接続ピン番号埋め込まれていてちょっと不安だったものの動作は安定しているのでグラフ描画もかなり簡素化できそうな気配。ライブラリ開発者の方に改めてお礼申し上げます。

■高速FFTライブラリ■

今回はこれを使います。
コンパクトで高速で美しい…。mbedで使うには、

  1. パッケージをダウンロード、解凍
  2. fft4g.cをブラウザ上のmbedプロジェクトにドラッグ&ドロップ
  3. ファイル名をfft4g.cppに変更
  4. あとはサンプルを参考に書くだけ
余談ですが、サンプルではfft->ifftで誤差を確認する、という処理が書かれていて、「FFT+IFFTでフィルタ作りたい」と思っている私には拍子抜けするほど簡単w

数学どころか算数レベルの私には基数って何よ?の世界なのですが、ともかくこれも一発動作。今まで使ったFFTライブラリの中で一番簡単だったかもしれません。

■ということでプログラムの構造■

  • IntervalでMAG3110を読みバッファに値を書き込んでいく
  • メインループでバッファをチェックし、一杯になったらFFTかけて液晶にグラフ表示
これだけです。

で処理の結果ですが…まず、電子機器で一杯の室内で放置した状態でのスペクトル


ごらんのように、ほぼ全域に不規則な成分が出てます。実は圧倒的に直流成分が多いのですがスケールアウトしてしまうのでカットしてます(そりゃそうだ)。そして次が、FRDM上約10cmのところでネオジム磁石をおおよそ2秒で3往復ほどの周期で振ってみたところのスペクトル


明確にでかい山が出来てます。必死に高速フリフリをすると右へ移動するし、ゆっくりにすると左へ移動するので、なんとなくできてるかなーと。さて、次は、「こういうぐあいにでっかい山ができていることを検出する処理」というのを書かないといけないのですが、パターン認識とか苦手なんす。どなたかアイディアください。

以下ソースです。

ほとんどサンプルからのコピペですが、mbedリポジトリに公開しましたのでご笑覧くださいませ。下にも貼っておきますが、何かの参考にされるのであればリポジトリの方をご覧下さい(基本的に以下のソースは更新し忘れることが多いと思うので)

#include "mbed.h"
#include "SLCD.h"
#include "MAG3110.h"
#include "fft4g.h"
#include "aqm1248a_lcd.h"
#define NMAX 256
#define NMAXSQRT 32
#define MAX(x,y) ((x) > (y) ? (x) : (y))
MAG3110 mag(PTE25, PTE24);
SLCD slcd;
aqm1248a_lcd lcd;
Ticker reader;
bool modeFilling = true;
int nFilled = 0;
double magBuffer[NMAX+1];
void readerFunction() {
if (modeFilling) {
float x;
mag.getX(&x);
if (nFilled < NMAX) {
magBuffer[nFilled++] = x;
if (nFilled >= NMAX) {
modeFilling = false;
}
}
}
}
void putdata(int n, double *a)
{
int j;
double pi2 = 3.14159265*2 / n;
for (j = 0; j <n; j++) {
a[j] = sin(j*pi2*10)*10 + sin(j*pi2*15)*5 + sin(j*pi2*20)*10;
}
}
int main()
{
char buf[80];
int n, ip[NMAXSQRT + 2];
double w[NMAX * 5 / 4];
ip[0] = 0;
n = NMAX;
int cnt = 0;
lcd.setmode(NORMAL);
lcd.set_contrast(25);
mag.enable();
wait(0.1);
reader.attach(&readerFunction, 0.0333333);
while (1) {
while(modeFilling == true) wait(0.1);
rdft(n, 1, magBuffer, ip, w);
int n2 = n/2;
int height = lcd.height();
int width = lcd.width();
double max = 0;
for (int i = 0; i < n2; i++) {
int i2 = i*2;
magBuffer[i] = magBuffer[i2]*magBuffer[i2] + magBuffer[i2+1]*magBuffer[i2+1];
if (i > 0 && magBuffer[i] > max) max = magBuffer[i];
}
lcd.cls();
lcd.locate(0,0);
lcd.printf("%lf", max);
max = height / max;
for (int i = 1; i < n2; i++) {
lcd.line(i, height-1, i, height-1-max*magBuffer[i], 1);
}
sprintf(buf, "%4d", cnt++);
slcd.printf(buf);
if (cnt > 9999) cnt = 0;
nFilled = 0;
modeFilling = true;
}
}