加速度センサとフルカラーLEDを使う

GenesisLightningTalks vol.12行ってきたデモの詳細とソースコードをエントリしておく。デモなんで電子玩具でもゲームでもないが、こういうのの組み合わせ(…と、よいアイディア)で面白いものはできている。

まずは動画。この手のエントリが増えるようだと、もう少しまともな撮影機材 / 環境を検討する必要があるかもしれない。携帯電話(SH905i)ではちょっと、なぁ…。

090131_1740

デモに使ったブレッドボード上はこんな感じ。実はプレゼン資料に載っている画像とは別のブレッドボードに組んでいる。資料作成後にもう一回り小さくても実装可能なことに気づいてしまったのだ。


Glt12

回路図はこちら。+Gainerのサンプルを寄せ集めただけではあるのだが、入力と出力を同時に行うデモをしたかったのである。


ソースコードも載せてしまう。デモで使ったコードからは、コメントなどを整理する修正を加えてある。前回しれっと使っていたMovingAverageFilterクラスも同梱したアーカイブも用意してある。

「GLT_00.zip」をダウンロード

/**
* Accel sensor + Full color LED
*  for GenesisLightningTalks vol12
*  @author kwappa (http://www.kwappa.net/)
*/
import processing.gainer.*;
//******************************************************************************
// constants
//******************************************************************************
private static final int   SCREEN_WIDTH         = 640 ;   // 画面幅
private static final int   SCREEN_HEIGHT        = 640 ;   // 画面高さ
private static final int   COLOR_RECT_SIZE      =  32 ;   // カラーピッカのセルサイズ
private static final int   COLOR_RECT_STEP      =  16 ;   // カラーピッカのセル分割数
private static final int   ACC_SENSOR_IN_X      =   1 ;   // X : 1
private static final int   ACC_SENSOR_IN_Y      =   0 ;   // Y : 0
private static final int   DIMENSINOS           =   2 ;   // 軸の数
private static final int   SENSOR_MIN_THRESHOLD =   3 ;   // カーソルが動き出す閾値
private static final int   MAF_RESOLUTION       =  16 ;   // 移動平均フィルタの解像度
private static final float ACC_SENSOR_RANGE     =  20.f ; // センサの振れ幅
private static final float CURSOR_MOVE_LENGTH   =   8.f ; // カーソル移動速度
// グローバル変数
Gainer gainer;                  // Gainerオブジェクト
PFont font ;                    // フォント
MovingAverageFilter maf[] ;     // 移動平均フィルタ
int[][] colors = {              // RGB3色
{ 255,   0,   0 },
{   0, 255,   0 },
{   0,   0, 255 }
} ;
int cursorPos[] ;               // カーソル位置
int sensorCentor[] ;            // センサ初期値
boolean mafInit = false ;       // maf初期化フラグ
int cursorCounter = 0 ;         // カーソルサイズのカウンタ
/**
*  下準備
*/
void setup()
{
// キャンバスサイズ
size(SCREEN_WIDTH, SCREEN_HEIGHT) ;
// フォントの準備
font = loadFont("Eureka-90.vlw") ;
textFont(font, 16) ;
// Gainerの準備
gainer = new Gainer(this) ;
gainer.beginAnalogInput() ;
// 移動平均フィルタの準備
maf = new MovingAverageFilter[DIMENSINOS] ;
for (int i = 0 ; i < DIMENSINOS ; i ++)
{
maf[i] = new MovingAverageFilter(MAF_RESOLUTION) ;
}
// カーソルの初期位置を生成
cursorPos = new int[2] ;
cursorPos[ACC_SENSOR_IN_X] = width / 2 ;
cursorPos[ACC_SENSOR_IN_Y] = width / 2 ;
// センサの初期値を控える
sensorCentor = new int[DIMENSINOS] ;
for (int i = 0 ; i < DIMENSINOS ; i ++)
{
sensorCentor[i] = gainer.analogInput[i] ;
}
}
/**
*  メインループ
*/
void draw()
{
//**************************************************************************
// 初期化
//**************************************************************************
// センサ初期値が取れるまで待つ
for (int i = 0 ; i < DIMENSINOS ; i ++)
{
if (sensorCentor[i] == 0)
{
sensorCentor[i] = gainer.analogInput[i] ;
maf[i].processSample(gainer.analogInput[i]) ;
}
}
for (int i = 0 ; i < DIMENSINOS ; i ++)
{
if (sensorCentor[i] == 0) return ;
}
if (!mafInit)
{
for (int i = 0 ; i < 2 ; i ++)
{
for (int j = 0 ; j < MAF_RESOLUTION ; j ++)
{
maf[i].processSample(gainer.analogInput[i]) ;
}
}
mafInit = true ;
return ;
}
//**************************************************************************
// 処理
//**************************************************************************
// ボタンを押すとキャリブレーション
if (gainer.buttonPressed)
{
for (int i = 0 ; i < DIMENSINOS ; i ++)
{
sensorCentor[i] = 0 ;
cursorPos[i] = width / 2 ;
}
return ;
}
// 平滑化したデータを取得してカーソル移動
int accData[] = new int[DIMENSINOS] ;
for (int i = 0 ; i < DIMENSINOS ; i ++)
{
// センサ入力を取得
accData[i] = maf[i].processSample(gainer.analogInput[i]) ;
// 傾き量を取得 (閾値を越えてなければ変化しない)
int sence = sensorCentor[i] - accData[i] ;
if (Math.abs(sence) < SENSOR_MIN_THRESHOLD) continue ;
// 傾き量を正規化
float moveRange = (sensorCentor[i] - accData[i]) / ACC_SENSOR_RANGE ;
if (i == ACC_SENSOR_IN_Y) moveRange *= -1 ; // Y軸は上下反転
// 移動量を算出して加算
cursorPos[i] += (int)(CURSOR_MOVE_LENGTH * moveRange) ;
// カーソルを画面端で止める
if (cursorPos[i] < 0) cursorPos[i] = 0 ;
int limit = 0 ;
if (i == ACC_SENSOR_IN_X)   limit = width ;
else                        limit = height ;
if (cursorPos[i] > limit) cursorPos[i] = limit ;
}
//**************************************************************************
// 描画
//**************************************************************************
colorMode(RGB, 255) ;
// 背景の塗りつぶし
background(0x7f, 0x7f, 0x4f) ;
// カラーピッカを描画
drawColorPicker() ;
// カーソルの描画
noFill() ;
ellipseMode(CENTER_DIAMETER) ;
cursorCounter ++ ;
for (int i = 0 ; i < 8 ; i ++)
{
stroke(i * 8) ;
int size = 12 + cursorCounter % 20 + i ;
ellipse(cursorPos[ACC_SENSOR_IN_X], cursorPos[ACC_SENSOR_IN_Y], size, size) ;
}
text(cursorPos[ACC_SENSOR_IN_X] + "," + cursorPos[ACC_SENSOR_IN_Y], 24, 24) ;
noStroke() ;
// 加速度センサの値と帯を描画
for (int i = 0 ; i < DIMENSINOS ; i ++)
{
int w = getWidth(accData[i], 16) ;
int y = height - 48 + 20 * i ;
// 帯
fill(colors[i][0], colors[i][1], colors[i][2]) ;
rect(16, y, w, 16) ;
// 値
fill(255, 255, 255) ;
text(i + "/" + accData[i] + "/" + w, 16, y + 12) ;
}
// フルカラーLEDを調光
colorMode(HSB, 100) ;
color ledColor = getLedColor() ;
gainer.analogOutput(0, (int)red(ledColor)) ;
gainer.analogOutput(1, (int)green(ledColor)) ;
gainer.analogOutput(2, (int)blue(ledColor)) ;
colorMode(RGB, 255) ;
fill(0,0,0) ;
}
/**
*  画面に応じた帯の幅を返す
*      @param      int     value           値(0 - 255)
*      @param      int     offset          幅の余白
*      @return     int
*/
private int getWidth(int value, int offset)
{
float maxW = width - offset * 2 ;
return (int)((float)value / 255.0 * maxW) ;
}
/**
*  カラーピッカを描画
*/
private void drawColorPicker()
{
colorMode(HSB, 100) ;
int beginX = (width - COLOR_RECT_SIZE * COLOR_RECT_STEP) / 2 ;
int beginY = beginX ;
int colorStep = 100 /  COLOR_RECT_STEP ;
for (int x = 0 ; x < COLOR_RECT_STEP ; x ++)
{
for (int y = 0 ; y < COLOR_RECT_STEP ; y ++)
{
fill(x * colorStep, y * colorStep, 99) ;
noStroke() ;
rect(beginX + COLOR_RECT_SIZE * x,
beginY + COLOR_RECT_SIZE * y,
COLOR_RECT_SIZE,
COLOR_RECT_SIZE
) ;
}
}
colorMode(RGB, 255) ;
}
/**
*  カーソル位置の色を取得
*      @return     color
*/
private color getLedColor()
{
int offset = (width - COLOR_RECT_SIZE * COLOR_RECT_STEP) / 2 ;
int[] colIndex = new int[DIMENSINOS] ;
int colorStep = 100 /  COLOR_RECT_STEP ;
for (int i = 0 ; i < DIMENSINOS ; i ++)
{
colIndex[i] = (cursorPos[i] - offset) / COLOR_RECT_SIZE ;
// カラーピッカからはみだしていたらハズレっぽい色を返す
if (colIndex[i] < 0 || colIndex[i] >= COLOR_RECT_STEP)
{
return color(10, 10, 10) ;
}
}
return color(colIndex[ACC_SENSOR_IN_X] * colorStep,
colIndex[ACC_SENSOR_IN_Y] * colorStep,
99) ;
}

回路図はEScadという回路図ドローソフトで作成した。パーツエディタには少してこずったが、本体はなかなか使いやすい。2000年のソフトで作者のwebサイトもすでに消えているのが大変残念である。OSSにしていただけると大変ありがたいのだが。

というわけでなんだかいろいろ載ったエントリになってしまった。他愛のないものを作っただけなのだが、正直に言うとものすごく楽しかった。本業のwebプログラミングをおろそかにしないよう注意が必要である。

Leave a comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です