INDEX(各項目ごとの目次)

[HOME]  [Processing関係]  [Arduino関係]  [マテリアル関係]  [秋葉原工作マップ]

2008年用ですが、部分的に内容を更新しています(2010/06/14)。
また、[建築農業工作ゼミ2009-2010]とも連動していますので、そちらにも幾つかサンプルがあります。
:

ラベル arduino の投稿を表示しています。 すべての投稿を表示
ラベル arduino の投稿を表示しています。 すべての投稿を表示

10/18/2008

Arduino-Processing シリアル通信6


【変更】以下はArduino1.0まで対応したプログラム内容です。
特にシリアル通信においては、Arduino2.0使用の際、バイト送信する場合、
Serial.print(value,BYTE);
のかわりに、
Serial.write(value);
を使用してください。


これまでのシリアル通信では、ProcessingとArduinoの一対一の通信を行ってきましたが、今回はProcessingで二つのシリアルポートを使い、二つのArduinoとシリアル通信を行う実験をしてみます。それぞれのArduino基盤には可変抵抗器をとりつけて入力値をProcessingへ別々に送信することにします。Processingの画面では、二つの入力信号を個別に読み取ってそれぞれの状態を描画することにします。

具体的なサンプルとして、「Pong」(下画像)のように二つのコントローラによって対戦するプログラムにします。それぞれのArduino基盤がコントローラとしてコンピュータに接続され、画面上でそれぞれのラケットを動かすことになります(得点のプログラムは含まれていません)。


(上画像:Processingの画面「Pong」)

Arduino基盤と可変抵抗器の接続は以下のようになります(二つ必要です)。



Arduinoのプログラム:
void setup(){
  Serial.begin(9600);
}

void loop(){
  //可変抵抗器の読み取り
  int val=analogRead(0);
  //シリアル通信処理
  if(Serial.available()>0){//合図用データが一つ来たら
    //合図用データを読み込んでバッファを空にする
    Serial.read();
    //読取値を4で割り、バイトで送信
    Serial.print(val/4,BYTE);
  }
}

Arduino側のプログラムでは、可変抵抗器からの読取り値を4で割ってスケールダウンした値(0~255)をシリアル通信でProcessing側へ送信しています(同期通信させるために合図用データを用いるシリアル通信の方法については「Arduino-Processing シリアル通信2」を参照して下さい)。二つのArduino基盤とも同じ内容になります。

Processingのプログラム:
//シリアル通信ライブラリを取り込む
import processing.serial.*;
//二つのポートのインスタンス
Serial portA;
Serial portB;
//二つの読取値の変数
int valA=100,valB=100;
//ボ−ル座標用変数
int x=100,y=100;
//ボールの動きの向きの変数(1:正の向き、-1:負の向き)
int dirX=1,dirY=1;

void setup(){
  //画面サイズ設定
  size(300,256);
  //二つのシリアルポート設定
  portA = new Serial(this, "/dev/tty.usbserial-A50019vD", 9600);
  portB = new Serial(this, "/dev/tty.usbserial-A40014iU", 9600);
  //図形外形線なし
  noStroke();
  //塗り色(白)
  fill(255);
  //矩形描画位置を中央に設定
  rectMode(CENTER);
}

void draw(){
  //背景(黒)
  background(0);
  //左ラケット描画(valAをY座標に代入)
  rect(20,valA,10,30);
  //右ラケット描画(valBをY座標に代入)
  rect(280,valB,10,30);
  //ボール描画
  rect(x,y,10,10);

  //ボールX座標の動き
  x+=dirX;//X軸方向に+1または-1ずつ進める

  //ラケットAに当たった時のはね返り
  if(x==30 && y>valA-15 && y<valA+15){
    dirX*=-1;//向きを反転する
  }
  //ラケットBに当たった時のはね返り
  if(x==270 && y>valB-15 && y<valB+15){
    dirX*=-1;//向きを反転する
  }
  //画面左端からはみ出た場合
  if(x<0){
    x=270;//右側に戻る
  }
  //画面右端からはみ出た場合
  if(x>width){
    x=30;//画面左側に戻る
  }

  //ボールY座標の動き
  y+=dirY;//Y軸方向に+1または-1ずつ進める

  //画面上下位置でのはね返り
  if(y<5 || y>251){
    dirY*=-1;//向きを反転する
  }  
}

//キーを押した場合
void keyPressed(){
  //「s」キーでシリアル通信開始
  if(key=='s'){
    //二つのポートへ開始用データ送信
    portA.write(65);
    portB.write(65);
  }
}

//シリアル通信処理
void serialEvent(Serial p){
  //portAの場合
  if(p==portA){
    if(p.available()>0){
      //値を読み込みvalAに代入
      valA=p.read();
      //合図用データ送信
      portA.write(65);
    }
  }
  //portBの場合
  if(p==portB){
    if(p.available()>0){
      //値を読み込みvalBに代入
      valB=p.read();
      //合図用データ送信
      portB.write(65);
    }
  }
}

Processing側のプログラムでは、二つのシリアルポートを用意し、それぞれportA、portB(名前は任意)にしておきます。serialEvent(Serial p){...}の括弧内のpは、ポート名に対応しています(今回の場合は、pはportAまたはportBに対応します)。serialEvent()は、Processingがデータを受信した際に作動するので、if()文を使ってどちらのポートなのかを条件分けして判別し、ポートに応じてそれぞれの読込み値を変数に代入します。同期通信させるために、Arduinoから送信されたデータをp.available()で確認しデータを読み込んだ後に、合図用データ(0~255の数値あるいは'A'や'a'などの一つの文字/1バイト分のデータ)を送信しています。
Processingのプログラムを開始したら、「s」キーを押すことでシリアル通信を開始することにしました(プログラム開始から数秒経った後に「s」キーを押さないと、シリアル通信が開始されないことがあります)。

関連:
Arduino-Processing シリアル通信1」(一つの値を送る/非同期通信)
Arduino-Processing シリアル通信2」(複数の値をバイトで送る/同期通信)
Arduino-Processing シリアル通信3」(大きな値を複数送る)
Processing-Arduino シリアル通信4」(ProcessingからArduinoを制御する)
Arduino-Processing シリアル通信5」(複数の値を文字列で送信する)

10/17/2008

Arduino+Xbee Shield/Processing+XBee Explorer USB

Arduinoにはワイヤレス通信するためのXbee Shieldがあります。今回はArduinoのXbee Shieldのサイトを参考に簡単な通信実験から始めたいと思います。Xbee Shiledを装着したArduino基板が2個必要になります。


(Xbee Shiledを装着したArduino基板)

注意しなければいけないことは、Arduino基板にXbee Shiledを装着したままプログラムをアップロードする際、Xbee Shiled上にある二つのジャンパピン(二つの間には「XBEE/USB」と表示されています)を「USB」側に差し替えなければいけないことです(3本のピンのうち、USB側のピンと中央のピンの2本のピンに差し込まれている状態になります)。
尚、アップロード後は「XBEE」側のピンと中央のピンの2本のピンに差し込まれている状態に戻してください。


「二つのArduino間での通信」
以下では、Xbee Shieldのサイトに従って、LEDの点滅実験を行います。一方のXbee Shiledを装着したArduino基板から、一秒おきに'H'か'L'の文字を送信し、受信したもう一方のXbee Shiledを装着したArduino基板の13番ピンに接続されたLEDが点滅する内容です。基本的には、通常のシリアル通信のプログラムと同じような内容になります。

送信側Arduinoのプログラム:
void setup(){
  //シリアル通信開始
  Serial.begin(9600);
}

void loop(){
  Serial.print('H');//「H」を送信(点灯)
  delay(1000);      //1秒待つ
  Serial.print('L');//「L」を送信(消灯)
  delay(1000);      //1秒待つ
}

送信側は一方的に'H'と'L'を1秒間隔で送信するだけです。受信側は、送信側からのデータが届いたら読み込みをし、データが「H」であれば13番ピンをHIGHで出力し、そうでなければLOWで出力するプログラムになります。

受信側Arduinoのプログラム:
int val;//受信データ用の変数を用意

void setup(){
  //シリアル通信開始
  Serial.begin(9600);
  //13ピンをデジタル出力に設定
  pinMode(13,OUTPUT);
}

void loop(){
  if(Serial.available()>0){ //データが来たら
    val=Serial.read();    //データを読み込み、valへ代入
  }
  if(val=='H'){           //valが「H」の場合
    digitalWrite(13,HIGH);//点灯
  }else{                  //valが「L」の場合
    digitalWrite(13,LOW); //消灯
  }
}

乾電池などの外部電源でArduinoを作動させれば、それぞれスタンドアロンで通信し合います。Decimilaなどの旧型のArduino基板の場合、外部電源を使うにはArduino基板についている「USB/EXT」のジャンパピンを「EXT」側に差し替える必要があります(プログラムをアップロードする際には、パソコンとUSB接続し、ジャンパピンも「USB」側に差し替えて下さい)。



「コンピュータに接続したXbeeと通信」:
コンピュータにXbeeを接続し、Arduinoとワイヤレス通信するには以下のようになります。
Sparkfunで販売されている「XBee Explorer USB」を使うと簡単にコンピュータとUSB接続でき、Processingからワイヤレスにシリアル通信が可能になります。


(左:「XBee Explorer USB」、右:Xbeeを装着した状態)

以下では、先ほどの受信用のXbee Shieldを装着したArduino基板に対して、Xbeeモジュールを装着した「XBee Explorer USB」を通して、Processingからワイヤレスでシリアル通信してみます。
まず、「XBee Explorer USB」のシリアルポートを調べてみます。

import processing.serial.*;
println(Serial.list());

「XBee Explorer USB」をコンピュータに接続し、上記の二行のプログラムをランさせれば、「XBee Explorer USB」のシリアルポートが出力されるはずです。


「Processingのプログラム」
受信側となるArduinoのプログラムは先ほどと同じものを使い、送信側となるProcessingだけのプログラムを以下に書きます。マウスボタンを押したら点灯、放したら消灯する内容とします。

//シリアル通信ライブラリを取り入れる
import processing.serial.*;
//ポートのインスタンス
Serial port;

void setup(){
  //「XBee Explorer USB」のシリアルポート設定
  port=new Serial(this,"/dev/tty.usbserial-A8003VXd",9600);
}
void draw(){
  //特になし  
}

void mousePressed(){//マウスボタンを押したら
  port.write('H');  //「H」を送信(点灯)
}

void mouseReleased(){//マウスボタンを放したら
  port.write('L');   //「L」を送信(消灯)
}

プログラムの内容はこれまでのシリアル通信と同じなので、ポートの設定、接続やジャンパピンの差し替えなどを間違わなければ特に問題はないと思います。



「Xbeeモジュールの設定/ATコマンド」
XbeeにATコマンドを送ることで、Xbeeモジュール自体の設定を確認したり変更することができます。ATコマンドをXbeeモジュールに送信するには、MacOSXなら「ZTerm」など、Windowsなら「ハイパーターミナル」や「Tera Term」などのターミナルアプリケーションで入力すると便利です(「ZTerm」と「ハイパーターミナル」の設定や使い方については、「Arduino-Processing BlueTooth通信+曲げセンサ」にも、説明があります)。

コンフィグレーションモードに入るには、
+++

プラスを3回入力し待機します(リターンキーは押さない)。そうすれば、
OK

という返事が返ってきます(ATコマンドモードでは、「+++」を押したあと1秒後に「OK」が返答され、さらに10秒以内に次のコマンドを送信しなければコマンドモードが自動的に終了してしまうので、返答がない場合は再度「+++」を押す必要があります)。さらに続けて、
ATID(リターンキーを押して送信)

と押せば、デフォルトの状態であれば、
3332

という数値が返ってきます。「3332」という数値は、そのモジュールのネットワークIDであり、このネットワークIDを変更すれば同じネットワークIDを共有しているXbeeモジュール間だけでの通信が可能になります(ひとつのモジュールだけでなく、相手になるモジュールも変える必要があります)。
設定されている通信速度を確認するには、
ATBD(リターンキーを押して送信)

と押せば、デフォルトであれば、
3

という数値が返ってきます。通信速度はそれぞれ
0:1200 bps
1:2400 bps
2:4800 bps
3:9600 bps
4:19200 bps
5:38400 bps
6:57600 bps
7:115200 bps

なので、デフォルトの「3」は9600 bpsということになります。
ATBD4(リターンキーを押して送信)

と打てば(「ATBD」の後に「4」を付け加える)、通信速度は「4」(19200)に変更されます(「OK」という返答がきます)。再度、
ATBD(リターンキーを押して送信)

と打てば、変更した内容を確認できます(この場合「4」という数値が返ってきます)。ただし、この場合電源が切れると設定内容は消えてしまいます。電源を切っても設定内容が戻らないようにするには、
ATWR(リターンキーを押して送信)

を変更後すぐに送信します(「OK」という返答がきます)。あるいは、
ATBD4,WR(リターンキーを押して送信)

という感じで、コマンドを複数合成して送信することもできます(「OK OK」という二つ分の返答がきます)。デフォルトの状態に戻すには、
ATRE(リターンキーを押して送信)

になるのですが、「WR」を付け加えていないので再度電源を入れたときには前回の状態に戻ってしまいます。
ATRE,WR(リターンキーを押して送信)

とすれば、電源を入れ直してもデフォルト状態は保持されます。
尚、コマンドモードから出るには、
ATCN(リターンキーを押して送信)

になります。
デフォルトでは、
ID:3332(ネットワークID)
CH:0x0C(チャンネル)
MY:0(そのモジュールのアドレス)
SH: (シリアルナンバー上位32ビット/モジュールごとに異なる)
SL: (シリアルナンバー下位32ビット/モジュールごとに異なる)
DH:0(送信先アドレス上位32ビット)
DL:0(送信先アドレス下位32ビット)
BD:3(通信速度:9600 bps)

に設定されており、ネットワーク、チャンネル、モジュールのアドレス、送信先アドレスがそれぞれ同じであるため、どのモジュール間でも通信可能です。逆に、ネットワークIDを変更してしまえば、他のモジュール群から干渉を受けずに通信し合うことも可能になります。あるいは、二つのモジュール間で送信先を互いに設定してしまえば、そのアドレスのモジュールだけとの通信が可能になります。そのためには、IDとCHは共有しておき、それぞれのモジュールアドレスを個別に設定しておきます。
ATMY1111,WR(リターンキーを押して送信)
OK OK(Xbeeからの返信)

とすれば、このモジュールの「MY」は「1111」に設定されたことになります(「WR」を付け加えたので、電源を落としても変更内容は記憶されます)。さらに送信先のアドレスを「2222」に設定するには、「DL」を「2222」に「DH」を「0」にします。
ATDL2222,DH0,WR(リターンキーを押して送信)
OK OK OK(Xbeeからの返信)

もう一方のほうも設定する必要があるので、「MY」を「2222」、「DL」を「1111」、「DH」を「0」にします。
ATMY2222,DL1111,DH0,WR(リターンキーを押して送信)
OK OK OK OK(Xbeeからの返信)

とします。こうすることで、「1111」のモジュールと「2222」のモジュールが互いに送信先を特定して通信し合うことができます。

Arduino Xbee Shieldサイトの説明によれば、「DH」を「0」、「DL」を「FFFF」に設定すれば、そのモジュールからの通信は、その他のすべてのモジュールによって受信可能になります。
また、送信先アドレス(上位ビットDHと下位ビットDL)が「FFFF」より大きい値(つまり「DH」が「0」以外の数値に設定したとき)、その「DH」と「DL」が相手モジュールの「SH」と「SL」に等しければ、相手モジュールのみに受信させることが可能になります。ただし、この場合も、ネットワークIDとチャンネルは同じでなければなりません。


「Ztermの設定」
以下は、MacOSXで「ZTerm」を使ったときの画面です。


ATコマンドを打つ画面。



メニューバー>Dial>Directory...をクリックすれば、上画面が現れます。「New」を押せば、新たな接続先を追加する画面(下画像)がでてきます。



「Service Name:」の欄に適当な名前を入れます。「Local Echo」にはチェックをいれておきます。チェックを外すと、自分の打った文字は画面に現れないので、チェックを入れておいた方がいいでしょう。Xbeeがデフォルト状態であれば、その他の項目は上画面のようになります。メニューバー>Setting>Connection...をクリックすることで、再度この画面が現れます。



メニューバー>Setting>Terminal...をクリックすれば、上画面が現れます。ここでは「Auto Line Feed」にチェックをいれておきます。チェックを外すと、ATコマンドが改行されなくなるので、チェックをいれておいたほうがいいでしょう。恐らく、MacOSXの改行コードは、「CR(キャリッジリターン:行頭に戻る)」だけなので、「LF(ラインフィード:次の行に移る)」も付け加えないと改行されなくなるからでしょう。

Zigbee開発ハンドブック (実践入門ネットワーク)
鄭 立
リックテレコム
売り上げランキング: 9866

DESIGNING ZIGBEE NETWORKS AND TRANSCEIVERS: The Complete Guide for Rf/Wireless Engineers
Shanin Farahani
Newnes
売り上げランキング: 21994

10/11/2008

Arduino タッチセンサ

今回は、ArduinoのPlaygroundサイト内のCapacitive Sensing(静電容量式)を参考に、タッチセンサをつくりたいと思います。基本的には、Arduino基盤に抵抗(1MΩ)を接続するだけです(その他ミノムシクリップや金属板などがあるといいかもしれません)。この方法によって、主に抵抗だけでセンサを容易につくることができます。指先などが入力用端子に近づくと静電容量が変化し、その変化量を読み取ることで判断する仕組みになります。
接続方法は以下の通りです。8番、9番ピンに抵抗を接続し、9番ピン側に入力用のタッチセンサとなる金属片を接続しておきます。金属片に指などで触れると感知するセンサとなります。紙などのシートで金属片を覆った上から触れても感知します。



まず以下のプログラムで実験してみることにします。指で金属板に触れると、9番ピンの読み取り値がLOWからHIGHへ変化していきます。HIGHになるまでの時間をカウントアップしていき、その変化量をシリアル通信でモニタリングしてみます。

void setup(){
//モニタリングのためシリアル通信開始
Serial.begin(9600);
//8番ピンをデジタル出力
pinMode(8,OUTPUT);
//9番ピンをデジタル入力
pinMode(9,INPUT);
}

void loop(){
//静電容量変化量の変数を用意
int a=0;
//8番ピンをHIGHで出力
digitalWrite(8, HIGH);
//指が触れたとき9番ピンがHIGHになるまでをカウント
while (digitalRead(9)!=HIGH){
//カウントする
a++;
}
delay(1);
//8番ピンをLOWにする
digitalWrite(8, LOW);
//モニタリング:値を出力
Serial.println(a);
}



モニタリング結果として、指で触れてない時は、

3
3
2
3
3
3
4
3
2
3

という感じで2〜4程度の値が確認されました。
指で触れている間は、

0
32
78
24
0
0
15
32
33
9

という感じで、5以上の値かつ、たまに0を出力していました。
値に多少ばらつきがあるので、プログラムに出力値を滑らかにするフィルタをつけくわえることにします。更に、指先を感知するごとに、13番ピンに接続されたLEDが点灯するプログラムも追加することにします。


int f=0;//フィルタ用変数

void setup(){
//モニタリングのためシリアル通信開始
Serial.begin(9600);
//8番ピンをデジタル出力
pinMode(8,OUTPUT);
//9番ピンをデジタル出力
pinMode(9,INPUT);
//LED点灯用に13番ピンをデジタル出力
pinMode(13,OUTPUT);
}

void loop(){
//静電容量変化量の変数を用意
int a=0;
//8番ピンをHIGHで出力
digitalWrite(8, HIGH);
//指が触れたとき9番ピンがHIGHになるまでをカウント
while (digitalRead(9)!=HIGH){
//カウントする
a++;
}
delay(1);
//8番ピンをLOWにする
digitalWrite(8, LOW);

//値を滑らかにするフィルタ式
f+=(a-f)/2;
//モニタリング:フィルタ値を出力
Serial.println(f);

//LED点灯のプログラム
if(f>5){//値が5より大きい場合点灯
digitalWrite(13,HIGH);
}else{ //それ以外消灯
digitalWrite(13,LOW);
}
}



フィルタ式を付け加えると、指を触れていないときの値は3程度であり、触れている時は

749
375
188
802
401
201
751
376
188

のように、ある程度大きな値が並んで出力されました。よって、LEDのオンとオフのしきい値を5にしておき、5より大きい値であればLEDを点灯させるプログラムにしてみました。1MΩの抵抗のかわりに10MΩの抵抗(より抵抗値が高い)を用いれば感度が高くなり、金属片と指先の距離が離れていても(数センチ)、充分反応するとサイトでは説明しています。

10/04/2008

Processing QRコード/2次元コード

Processingのライブラリには、「QRCode」というものがあります。QRコードとは、以下のような2次元的なマトリクスを利用したコードです。バーコードよりは情報量が多く、様々な場面に用いられています。


このブログのURL情報が含まれたQRコード(上画像:qrcode.png)

Processingの「QRCode」ライブラリを用いることで、QRコードに含まれた情報を解読することができます。逆に、任意の情報のQRコードを生成するには以下のようなサイトで行うことができます。

http://qrcode.kaywa.com
http://qr.quel.jp

QRCode」ライブラリでは、上記サイトなどで直接生成したQRコード以外にも、Webカメラやデジタルカメラで撮影したQRコード(紙上に印刷したQRコード)を認識/解読することができます。尚、このライブラリを使うには「QRCode」ライブラリサイトからライブラリをダウンロード+インストールする必要があります。サイトの説明によれば、以下のようなコードでQRコードを解読することができます。予めQRコードの画像を用意して、スケッチフォルダ内dataフォルダに入れておいて下さい。
以下のサンプルは、上にあるQRコード(qrcode.png)を使って解読するコードです。無事QRコードが解読されれば、link()によって、自動的にこのブログのURLへジャンプするようになっています。


//ライブラリを取り入れる
import pqrcode.*;

//インスタンス名
Decoder decoder;

void setup() {
//オブジェクトの生成
decoder = new Decoder(this);
//イメージのロード
PImage img = loadImage("qrcode.png");
//イメージの解読
decoder.decodeImage(img);
}

void decoderEvent(Decoder decoder) {
//解読結果をテキストとして取り出す
String statusMsg = decoder.getDecodedString();
//テキストを画面に出力
println(statusMsg);
//テキストに書かれているURLへ移動
link(statusMsg, "_new");
}



QRCode」ライブラリのサイトには、Webカメラから撮影したQRコードを読み込んで解読するサンプルがあります。

http://www.shiffman.net/p5/pqrcode_files/Pqrcode_example.zip

このサンプルでは、「スペース」キーでカメラからのQRコードを画像として読み込み解読します。解読されたテキストは画面に文字として表示されます。「f」キーで、既に用意されているテスト用の画像を読み込んで解読します(「http://www.shiffman.net」と表示されるはずです)。「s」キーで、カメラセッティングの画面に切り替わります。
カメラでQRコードを撮影するときに、ピントがずれていると認識できないこともあるので、カメラを調整する必要があるかもしれません。

以下では、デスクトップ上あるいはその他の場所に保存してあるQRコードをファイルチューザーで選び、解読する実験をしてみます。ファイルチューザーについては、前回のブログ「Processing FileChooser/ファイル選択画面の表示」を参照して下さい。
マウスを押したら、ファイルチューザーのダイアログ画面が現れ、任意のQRコードを選択し、解読結果をProcessingのコンソールに表示します。

//JavaのSwingを取り込む
import javax.swing.*;

import pqrcode.*;

//解読用インスタンスを用意
Decoder decoder;

//画像インスタンスを用意
PImage pimage;

//選択ファイル名を用意し
//ファイル名を空にしておく
String getFile = null;

void setup(){
//とりあえず表示画面を400角に設定
size(400,400);
//解読用オブジェクトの生成
decoder = new Decoder(this);
//背景(黒)
background(0);
}

void draw(){
//選択ファイル名が空でないとき
if(getFile != null){
//ファイルを取り込む
fileLoader();
}
}

//マウスを押したら
void mousePressed(){
//選択ファイル取得処理
getFile = getFileName();
}

//ファイルを取り込むファンクション
void fileLoader(){
//選択ファイル名のドット以降の文字列を取得
String ext = getFile.substring(getFile.lastIndexOf('.') + 1);
//その文字列を小文字にする
ext.toLowerCase();
//文字列末尾がjpg,png,gif,tgaのいずれかであれば
if(ext.equals("jpg") || ext.equals("png") || ext.equals("gif") || ext.equals("tga")){
//選択ファイル名のイメージを取り込む
pimage = loadImage(getFile);
//背景(黒)
background(0);
//イメージ表示
image(pimage, 0, 0, pimage.width, pimage.height);
//イメージの解読
decoder.decodeImage(pimage);
}
//選択ファイルパスを空に戻す
getFile = null;
}

//ファイル選択画面、選択ファイル名取得の処理
String getFileName(){
//処理タイミングの設定
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
//ファイル選択画面表示
JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(null);
//「開く」ボタンが押された場合
if (returnVal == JFileChooser.APPROVE_OPTION) {
//選択ファイル取得
File file = fc.getSelectedFile();
//選択ファイルのパス取得
getFile = file.getPath();
}
}
//上記以外の場合
catch (Exception e) {
//エラー出力
e.printStackTrace();
}
}
}
);
//選択ファイルパス取得
return getFile;
}

void decoderEvent(Decoder decoder) {
//解読結果をテキストとして取り出す
String statusMsg = decoder.getDecodedString();
//テキストを画面に出力
println(statusMsg);
}



以下は、授業内で実験した内容です。
数値の情報(100など)を含んだQRコードを生成し、プログラムによって解読された文字列としての数値を整数型の数値に変換します。その数値をArduinoへシリアル通信で送信し、analogWrite()でLEDの輝度やモータの速度あるいはサーボの回転角度などに反映させる内容です。いくつかのQRコードを作成し、ファイルチューザーで選択したQRコードを入れ替わりで送ります。

//シリアル通信ライブラリを取り込む
import processing.serial.*;
//シリアル通信インスタンス
Serial port;

//JavaのSwingを取り込む
import javax.swing.*;
import pqrcode.*;
//解読用インスタンスを用意
Decoder decoder;
//画像インスタンスを用意
PImage pimage;
String getFile = null;

void setup(){
//とりあえず表示画面を400角に設定
size(400,400);
//解読用オブジェクトの生成
decoder = new Decoder(this);
background(0);

//シリアルポートの設定
port = new Serial(this, "/dev/tty.usbserial-A4001Kjl", 9600);
}

void draw(){
//選択ファイル名が空でないとき
if(getFile != null){
//ファイルを取り込む
fileLoader();
}
}

void mousePressed(){
getFile = getFileName();
}

void fileLoader(){
//選択ファイル名のドット以降の文字列を取得
String ext = getFile.substring(getFile.lastIndexOf('.') + 1);
//その文字列を小文字にする
ext.toLowerCase();
//文字列末尾がjpg,png,gif,tgaのいずれかであれば
if(ext.equals("jpg") || ext.equals("png") || ext.equals("gif") || ext.equals("tga")){
//選択ファイル名のイメージを取り込む
pimage = loadImage(getFile);
//背景(黒)
background(0);
//イメージ表示
image(pimage, 0, 0, pimage.width, pimage.height);
//イメージの解読
decoder.decodeImage(pimage);
}
//選択ファイルパスを空に戻す
getFile = null;
}

//ファイル選択画面、選択ファイル名取得の処理
String getFileName(){
//処理タイミングの設定
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
//ファイル選択画面表示
JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(null);
//「開く」ボタンが押された場合
if (returnVal == JFileChooser.APPROVE_OPTION) {
//選択ファイル取得
File file = fc.getSelectedFile();
//選択ファイルのパス取得
getFile = file.getPath();
}
}
//上記以外の場合
catch (Exception e) {
//エラー出力
e.printStackTrace();
}
}
}
);
//選択ファイルパス取得
return getFile;
}

void decoderEvent(Decoder decoder) {
//解読結果をテキストとして取り出す
String statusMsg = decoder.getDecodedString();
//テキストを画面に出力
println(statusMsg);

//解読した文字列を整数値に変換
int val=int(statusMsg);
//シリアル通信で送信
port.write(val);
}



Arduino側は、11番ピン(PWMピン)にLEDを接続し、輝度が変化する内容であれば以下のようになります。


int val;

void setup(){
//シリアル通信開始
Serial.begin(9600);
}

void loop(){
//受信データがひとつ届いたら
if(Serial.available()>0){
//受信データ読み込み
val=Serial.read();
}
//11番ピンをアナログ出力する
analogWrite(11,val);
}



QRコードで、「100,20,35,180」のように、幾つかの数値をコンマ(デリミタ)で区切り連続した数値の文字列を作成し、解読した結果として送信すれば、複数の数値データをArduinoへ送信することもできます。
この場合、Processingのプログラムの最後の部分にあるvoid decoderEvent(){...}の部分を以下のようにします。

void decoderEvent(Decoder decoder) {
String statusMsg = decoder.getDecodedString();
int[] data=int(split(statusMsg,',');
for(int i=0;i<data.length;i++){
port.write(val);
}
}

解読される文字列は

statusMsg = "100,20,35,180"

なので、
この文字列statusMsgをsplit()に代入し「,」コンマを区切り記号として複数の文字列を含んだ配列に変換します。同時にその文字列群をint()で括ることで、文字列データを整数値データに変換し、

data = {100,20,35,180}

という整数値の配列になります。data.lengthによって配列dataに何個のデータが含まれているか確認し(この場合4個、data[0]からdata[3]まで)、for()文でデータ数の分だけport.write()で繰り返しシリアル通信で送信します(4回分送信)。Arduino側で、順番にこれらの数値を受け取ることで、LEDの輝度調整のプログラムであれば、連続した値をもとに変化する輝度調整が可能になります。

応用的な使い方として、LEDの点灯やサーボの回転角度などの連続する動きのデータをQRコードで幾つか作成しておき、Webカメラを通して解読させる度に異なる動作をさせたり、マトリクスLEDなどに文字や模様として表示させたりできます。

8/30/2008

Arduino タッチパネル(4線式)4-wire touch panel

*Some are written in English at the moment(sorry not all of them...)
This example shows how to use a touch panel/screen with an Arduino board plus a serial communication to a Processing program, which draws where to touch on the touch panel.
This is not about a multi-touch function, only single point on the touch panel can be detected.

今回は、4線式のタッチパネルをArduino基盤に接続し操作実験してみたいと思います。
タッチパネルには、4線式や5線式という比較的簡単な構造になっているものがあります。今回使うタッチパネルは、指先やペン先で触れた一点の位置(X座標値とY座標値)を検出可能にするものです(複数の点を同時検出可能なマルチタッチではありません)。
基本的には、X座標に2線、Y座標に2線あり、合計4線あります。手順としては、まずX座標を検出、そしてY座標を検出というように別々(交互)に行います。タッチパネル自体がX座標用とY座標用に対応した2層の抵抗になっており、X座標(横方向)だけで考えれば、タッチパネルの左端に0V、右端に5Vを接続しておいて、指先で触れた箇所で分圧される仕組みになっています。つまり、X座標の左に行くほど0Vに近く、中心に触れれば約2.5V、右に行くほど5Vに近い電圧が読み取れることになります。X座標を読み取る際には、使用していないY座標の2線のうちの1本を使います。X座標を検出したら、検出対象をY座標に切り替えて同様の方法で検出を続けます。下の図では、タッチパネルの2層あるうちのX座標用の層を押せば、下にあるY座標用の層と接触し、その地点での分圧された電圧の値をY座標用の層から読み込むことができます。
There are two layers of conductive films on a 4-wire touch panel, the one for x-coordinate and the other for y-coordinate. Each layer is connected to GND and 5V on the egdes. Pressing the x-coordinate layer with your finger, then the x-coordinate layer will contact to the other layer(y-coordinate layer) underneath. At this moment the voltage is devided at the point where the two layers are touching, and the devided valtage can be read from the edge of the y-coordinate layer(the y-coordinate layer is working as a conductive film for the x-coordinate film at this time).



Arduino基盤との接続は、以下のようにアナログ入力の0〜3番ピンに接続することにします(X座標用に0番ピンと1番ピン、Y座標用に2番ピンと3番ピンを使用)。通常タッチパネルなどの薄型の機器にはFPC(フレキシブルプリント基板)/FFC(フレキシブルフラットケーブル)の端子がついています。Arduino基盤からのワイヤーと接続するためにはFFC用コネクタを介して接続します(直接ハンダ付けできないので)。
X座標を計測中にはyLowの端子からX座標の分圧された電圧を読み取るので(Y座標を計測中にはxLowの端子で読み取る)、タッチパネル上に何も触れていない時は、xLow端子に0Vが接続されるようにするため、プルダウン抵抗を接続しておきます(xLowやxHighとyHighにもプルダウン抵抗をつけておきます)。


Needs a pull-down resistor for each wire from the touch panel.

処理の手順は、まずX座標(横方向)の検出を行う際には、アナログ入力の「0番ピンと1番ピン」を「14番ピンと15番ピン」としてデジタル出力に切り替え、14番ピンを0V(LOW)、15番ピンを5V(HIGH)で出力しておきます。そして、Y座標用のアナログ入力2番ピンを通してanalogRead()で値を読み込みます。読み込まれた値は、X座標の値になります。このとき、Y座標用のアナログ入力「2番ピン」がデジタル出力にならないように「16番ピン」として予めデジタル入力にしておきます。「3番ピン」も同様に「17番ピン」としてデジタル入力にしておきます。
Y座標(縦方向)の検出の際には、アナログ入力の「2番ピンと3番ピン」を「16番ピンと17番ピン」としてデジタル出力に切り替え、16番ピンを0V(LOW)、17番ピンを5V(HIGH)で出力しておきます。X座標用であった「14番ピンと15番ピン」をデジタル入力に切り替えておいてから、アナログ入力「0番ピン」を通してY座標の値をanalogRead()で読み込みます。このように1ループのなかで、X座標とY座標の検出処理を順番に行い、それぞれの座標値を得ます。
Basically using the analog pins 0,1,2,3 on an Arduino board, those pins can be both analogRead pins and digitalWrite pins(in this case:pin numbers are 14,15,16,17) depending on setting.
First, to read an x-coordinate value, set the pin14 as 0V(LOW) and the pin15 as 5V(HIGH), then the rest of the pins(either of 16 and 17) can be analogRead pins. Next, to read a y-coordinate value, set the pin16 as 0V(LOW) and the pin16 as 5V(HIGH), then read the value from either of the pin14 and the pin15. Reading the both value one after another then send them through a serial communication to Processing.

//デジタル出力用ピン番号の定義:the digital output pins
#define xLow  14
#define xHigh 15
#define yLow  16
#define yHigh 17

void setup(){
  //シリアル通信開始:start serial communication
  Serial.begin(9600);
}

void loop(){
  //X座標用端子をデジタル出力に設定し、それぞれをLOWとHIGHで出力しておく
  //set the both x-coordinate pins as digital output:one is Low the other is HIGH  
  pinMode(xLow,OUTPUT);
  pinMode(xHigh,OUTPUT);
  digitalWrite(xLow,LOW);
  digitalWrite(xHigh,HIGH);

  //Y座標用端子をLOWにしておく:the both y-coordinate pins are set to be LOW
  digitalWrite(yLow,LOW);
  digitalWrite(yHigh,LOW);

  //Y座標用端子をデジタル入力に設定:change the y-coordinate pins as digital input
  pinMode(yLow,INPUT);
  pinMode(yHigh,INPUT);
  delay(10);

  //アナログ入力2番ピン(yLowピン)で読み込み
  //read analog pin2(yLow pin) to get an x-coordinate value
  int x=analogRead(2);
  
  //Y座標用端子をデジタル出力に設定し、それぞれをLOWとHIGHで出力しておく
  //set the both y-coordinate pins as digital output:one is Low the other is HIGH 
  pinMode(yLow,OUTPUT);
  pinMode(yHigh,OUTPUT);
  digitalWrite(yLow,LOW);
  digitalWrite(yHigh,HIGH);

  //X座標用端子をLOWにしておく:the both x-coordinate pins are set to be LOW
  digitalWrite(xLow,LOW);
  digitalWrite(xHigh,LOW);

  //X座標用端子をデジタル入力に設定:change the x-coordinate pins as digital input
  pinMode(xLow,INPUT);
  pinMode(xHigh,INPUT);
  delay(10);

  //アナログ入力0番ピン(xLowピン)で読み込み
  //read analog pin0(xLow pin) to get an y-coordinate value
  int y=analogRead(0);

  if(Serial.available()>0){
    //文字列でシリアル通信:send the values as a DEC format with a delimiter
    Serial.print(x,DEC);   //X座標:x-coordinate
    Serial.print(",");     //デリミタ:delimiter
    Serial.println(y,DEC); //Y座標:y-coordinate

    //合図用信号読み込みでバッファを空にする
    //read a handshake signal from Processing and clear the buffer
    Serial.read();
  }
}

今回実験で用いたタッチパネルは、12.1インチのサイズ(横:縦=4:3)であり、指先で触れた位置が
パネル左端: 70 :a minimum value when touching the left edge of the touch panel
パネル右端:781 :a maxmum value when touching the right edge of the touch panel
パネル上端: 81 :a minimum value when touching the upper edge of the touch panel
パネル下端:822 :a maxmum value when touching the lower edge of the touch panel
の値として検出されました。パネルに触れないときには、プルダウン抵抗により0が出力されます(以下Processingのプログラムでは、XとYの読み取り値が10以上のときタッチしていることとして判別しています)。
In this example, I used a 12.1-inch(width:height=4:3) touch panel.
When touching each edge of the touch panel, the values are like the above.
When not touching the touch panel, you can read zero value from the analog pins because of the pull-down resistors.

それでは、座標値をProcessingへシリアル通信し、Processingの画面上に描画することにします。Processingへは、座標値を文字列として送信することにします(文字列のシリアル通信については「Arduino-Processing シリアル通信5」を参照して下さい。Processing側では、タッチパネル上の指先の動きに合わせて円が動くようにします。タッチパネルに触れている時といない時では円の色が変化するようにします。「s」キーを押してシリアル通信開始です。
The below Processing code is that:
a circle on the screen moves and follows where to touch on the touch panel,
the color of the circle changes depending on touching or not touching.
*Press 's' key to start the serial communication with the Arduino.

//シリアルライブラリを取り込む
import processing.serial.*;
//シリアル通信用インスタンスportを用意
Serial port;

//読み込み値の変数を用意:variables for data from the Arduino
int x,y;
//座標用変数を用意:variables for xy-coordinates to draw a circle on the screen
float xPos,yPos;

void setup(){
  //画面サイズ設定
  size(800,600);
  smooth();
  //シリアルポート設定
  port = new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
  //「10」(ラインフィード)が来る度に
  //serialEvent()を作動させる
  port.bufferUntil(10);
  background(0);
  stroke(255); 
}

void draw(){
  //背景描画(黒)
  background(0);

  if(x>10 && y>10){//タッチしている時:when touching
    //塗り色を白にする
    fill(255);
    //読み取り値を座標にマッピングする
    xPos=map(x,70,781,0,width);
    yPos=map(y,81,822,0,height);
  }else{//タッチしていない時:when not touching
    //塗り色を黒にする
    fill(0);
  }

  //マッピングした座標を代入し円を描写:draw a circle
  ellipse(xPos,yPos,20,20);
}

//シリアル通信:serial communication
void serialEvent(Serial p){
  //文字列の変数stringDataを用意し「10」(ラインフィード)が来るまで読み込む
  String stringData=port.readStringUntil(10);

  //文字列データが空ではないとき
  if(stringData!=null){
    //文字列データに含まれる改行記号を取り除く
    stringData=trim(stringData);

    //整数型の配列data[]を用意し、
    //コンマ記号をもとに文字列データを区切って
    //配列data[]に整数化して入れておく
    int data[]=int(split(stringData,','));

    //配列data[]内のデータが2つなら、
    if(data.length==2){
      //最初のデータをxに代入
      x=data[0];
      //次のデータをyに代入
      y=data[1];

      //合図用データ送信:send a handshake signal
      port.write(65);
    }
  }  
}

//キー「s」が押されたら通信開始
void keyPressed(){
  if(key=='s'){
    //開始用データ送信:send a first handshake signal
    port.write(65);
  }
}

Arduinoから送られてくるXY座標値をmap()をつかってProcessingの画面上のXY座標値に対応させます。今回の場合、読み取りの最小値(70,81)を画面上の(0,0)に、読み取りの最大値(781,822)を(width=800,height=600)に対応させます。

通常タッチパネルはモニターと一体型になっていますが、タッチパネルだけを用いて透明で平面的な入力デバイスとして利用することも考えられます。仕組みはそれほど複雑ではないので、中古やジャンクのタッチスクリーンなどを分解して手に入れてもいいかもしれません。

以下のサイトでは、タッチパネルの構造や仕組みについて説明してあります。Other sites explaining about touch panels/touch screens.
グンゼ/タッチパネル製造
DMC/タッチパネル製造

また、以下のようなタッチパネル製品/部品などもあります。Shops selling touchpanel parts.
aitendo/タッチパネル部品販売
ストロベリーリナックス/PSP用タッチパネル
スイッチサイエンス/NintendoDSタッチスクリーン
Liquidware/Arduino TouchShield
Sparkfun/OLED+Touchscreen
iPodパーツショップ/iPhone 3G タッチスクリーン(部品)



Logitec USB対応15型タッチパネル LTP-15U
ロジテック (2004-10-31)
売り上げランキング: 13452

8/23/2008

Arduino 距離センサ/超音波レンジファインダー

今回は、Parallax社の超音波レンジファインダー「PING)))」で距離の計測実験を行います。距離センサとして、以前に赤外線で検出する「GP2Y0A21YK」を扱いましたが、「PING)))」は超音波を使って検出するタイプです。測定距離は3cm〜3.35mとなっているので、「GP2Y0A21YK」(測定距離10cm〜80cm)より測定範囲の幅はあります。「GP2Y0A21YK」は、信号端子をPWM端子に接続しanalogRead()によって簡単に読み込みが可能ですが、「PING)))」では1本の信号端子(SIG)に対してパルスを用いて測定します。まず「PING)))」から超音波を発します(5マイクロ秒間)。その超音波が物体に当たれば反射して戻ってくるので、戻ってくるまでの時間を計測し、音速と時間の関係から距離を導き出します。
サンプルソースがArduinoサイトのPlaygroundLearning>Examples>Complex Sensorsの中にあります。前者はpulseIn()を使って反射波の時間を計測しており、後者は変数を用いて時間をカウントしています。今回は前者のpulseIn()を用いた方法を参考にします。pulseIn()の場合は、unsigned long(正の整数値:0〜4,294,967,295までの値)の変数タイプを用います。尚、接続方法は以下の通りです。




//反射波の変数を用意
unsigned long echo = 0;
//信号ピンを9番ピンに設定
int signalPin = 9;

void setup(){
//出力用にシリアル通信開始
Serial.begin(9600);
}

void loop(){
//まず信号ピンを出力に設定(発信用)
pinMode(signalPin, OUTPUT);
//2マイクロ秒間LOWで出力
digitalWrite(signalPin, LOW);
delayMicroseconds(2);
//5マイクロ秒間HIGHで出力
digitalWrite(signalPin, HIGH);
delayMicroseconds(5);//超音波発信
//信号ピンをLOWに戻す
digitalWrite(signalPin, LOW);

//信号ピンを入力に設定(受信用)
pinMode(signalPin, INPUT);
//信号ピンをHIGHに設定
digitalWrite(signalPin, HIGH);
//反射波が戻ってくるまでの時間を計測し
//片道の時間(半分の値)にする
echo = pulseIn(signalPin, HIGH)/2;
//ミリメートル単位に変換
int distance = echo*0.34442;

//距離を出力
Serial.println(distance);
delay(250);
}


PING)))」では、ひとつの信号端子(SIG)だけで超音波の発信と受信を切り替えて制御する必要があります。まず最初に信号端子をLOWに設定(2マイクロ秒程度)しておき、その後5マイクロ秒間HIGHにすると、超音波が発信します。その後、物体に反射して戻ってくる超音波を感知させるために、信号端子を「デジタル出力」から「デジタル入力」に切り替えます。「デジタル入力」に切り替えられた信号端子は、反射波を受信するとHIGHになります。反射波がなくなればLOWになるので、それまでの継続時間をpulseIn()でカウントします。pulseIn()の括弧内には、ピン番号とHIGHかLOWの値を入れます。HIGHを入れれば、そのピンがHIGHになるまで待機し、HIGHになったときに時間(マイクロ秒単位)の計測を開始し、次にLOWになるときに時間の計測を終了します。今回の場合は、発信した超音波が反射して戻ってくるまでの時間を計測してくれることになります。計測した時間は超音波が往復して戻ってくるまでの時間なので、計測時間の半分が実際の距離に要した時間となります。音速は344.42m/secなので、1マイクロ秒で0.34442mmの距離を進むことになります。従って、pulseIn()で求めた時間(往復)を片道分の時間にするため2で割り、0.34442をかければミリメートル単位で出力することになります。

8/20/2008

Arduino モータドライバ+モータ

以前「Arduino 小型DCモータ/TA7291P」で、モータドライバICのTA7291Pを用いてDCモータの制御を行いました。TA7291Pは、許容電流は1.0A(最大2.0A)なので小型のDCモータ向きでした。より大きなモータを制御するには、以下のようなモータドライバあるいはモータコントローラなどがあります。Arduino基盤に対して、電圧、電流ともに許容値を超えるため、モータ駆動用外部電源が必要になります。


東芝 TA7291P


東芝 TA8440HQ:PWM端子付きブリッジドライバIC


NEC UPD16805:モータドライブ用IC


東芝 TA8429HQ


STマイクロエレクトロニクス L6203


「モータドライバIC」
TA7291P :連続1.0A(最大2.0A):モータ用電源 0~20V
TA7279P :連続1.0A(最大3.0A):モータ用電源 0~16V(PWM端子なし)
TA8440HQ:連続1.5A(最大3.0A):モータ用電源 0~50V
UPD16805:連続1.0A(最大4.2A):モータ用電源 0.5~8.0V
TA8429HQ:連続3.0A(最大4.5A):モータ用電源 7~27V
L6203 :連続4.0A(最大5.0A):モータ用電源 12~48V
TB62300F:連続2.5A(最大8.0A):モータ用電源 18~40V

「モータドライバボード」
Poloru VNH3SP30:連続9A(最大30A) :モータ用電源最大30V
Poloru VNH2SP30:連続14A(最大30A):モータ用電源最大19V

「モータコントローラ」
SyRen10 :連続10A(最大15A) :モータ用電源 6~24V
SyRen25 :連続25A(最大45A) :モータ用電源 6~24V
Pololu SMC04 :最大30A  :モータ用電源 6~18V
KONDO UMD-540S:50A以下:モータ用電源 4.8~12V

「デュアルモータコントローラ(モータ2個制御)」
Sabertooth2x5 :連続5A(最大10A) :モータ用電源 6~18V
Sabertooth2x10 :連続10A(最大15A):モータ用電源 6~24V
Sabertooth2x25 :連続25A(最大50A):モータ用電源 6~24V

「ラジコンカー用スピードコントローラ/アンプ」
FUTABA MC230CR 連続45A(最大90A) :モータ用電源7.2V〜8.4V
FUTABA MC330CR 連続100A(最大200A):モータ用電源7.2V〜8.4V
KONDO VFS-FR 連続240A(最大960A):モータ用電源7.2V〜8.4V


連続アンペア数は、PWM制御など使わず直接連続運転したときの許容値になります。ArduinoのanalogWrite()やパルスを生成してPWM制御するならば、最大アンペア数近くまで出力可能になります。ただし、ドライバによって性質が異なるので、ゆとりを見ておいたほうがいいでしょう。あまりにも発熱しすぎる場合は、放熱器をドライバ本体に取付ける必要があります。

大抵のモータドライバやモータコントローラなどには、
・回路用電源端子(5V/Arduino基盤5V端子へ)
・モータ用端子(2端子/モータへ接続)
・外部電源端子(モータ駆動用電源/GNDは他と共有)
・GND共有端子(Arduino基盤GND端子と共有)
・正転/逆転/停止など切替用信号端子(2端子/Arduino基盤へ)
・PWM用端子(Arduino基盤PWM端子へ)
がついているはずなので、接続方法はほぼ共通しています。


ラジコン用アンプについて
ラジコンカー用スピードコントローラ(アンプ)の場合、レース用のものは「バックなし(前進のみ)」が多いため、「バック付き(前進/後進)」かどうかを確認する必要があります。


フタバ MC231CR アンプ【送料無料】


フタバ MC331CR アンプ【送料無料】


【ラジコン RCパーツ】KO PROPO【ESC(アンプ)】VFS-FRバック付きVFS

アンプ本体には、
・モータ用端子(2端子/モータへ接続)
・外部電源端子(モータ駆動用電源/ラジコン用7.2Vバッテリーなどへ接続)
・3本線
  回路用電源:赤(Arduino基盤5V端子へ)
  GND:黒(Arduino基盤GNDと共有)
  信号線:白(Arduino基盤PWM端子へ)
がついています。ArduinoのanalogWrite()で、PWMピンと信号線(白)を接続して(サーボをPWM制御するような方法)、スピード調整/正転/反転/停止できます。analogWrite()の括弧内の値(0〜255)を中点(127前後)にすると停止状態になるはずです。
あるいは、Arduinoのサーボライブラリのservo.write()を使って0~180の範囲(90が中点)で制御することができるはずです。

上記以外に、MOS-FETを用いてモータドライバを自作する方法もあります。


一般的な模型用モータ
モータについては、一般的なラジコンや模型用(マブチモータ)のものであれば、以下のようなものが挙げられます。

FA-130RA:1.5V時/最大効率時電流0.66A/瞬間最大電流2.20A
RE-140RA:1.5V時/最大効率時電流0.66A/瞬間最大電流2.10A
RE-260RA:3.0V時/最大効率時電流0.64A/瞬間最大電流2.70A
RE-280RA:3.0V時/最大効率時電流0.87A/瞬間最大電流4.70A
RS-380PH:7.2V時/最大効率時電流3.23A/瞬間最大電流14.9A
RS-540SH:7.2V時/最大効率時電流4.57A/瞬間最大電流22.2A

最大効率時電流は、通常時の電流とみなしてもいいでしょう。瞬間最大電流は、急停止や逆転時など様々な負荷がかかった時(負荷をかけて停止した時)の電流値です。一定の速度で回転しているようなものであればいいのですが、回転方向や速度を変化させるような場合は、瞬間最大電流を考慮にいれる必要があります。その場合は瞬間最大電流を許容できるモータドライバが必要になります。

上記のモータにギヤヘッドが取付けてある「タミヤ・ギヤードモータ」シリーズは、比較的高トルクが得られます。380モータであれば、7.2V電源で瞬間最大電流が14.9Aなので、上記モータドライバICでは許容アンペア数が低すぎるため、最低でも15Aまで耐えられる「SyRen10」などが必要になるでしょう。540モータであれば、7.2V電源使用時なら22.2A以上、12V電源使用時なら37A以上耐えられるモータコントローラ「SyRen25」などが必要になるでしょう。
小型のモータであれば、「タミヤ・テクニクラフト」シリーズのギヤボックスなどがあります。

尚、モータのデータシートには「性能線図」と呼ばれる表が掲載されている場合があります。この表を用いて、モータの特性を調べることができます。マブチモータのサイトには性能線図の分かりやすい説明があります。


大型のモータ(その他モータの種類についてはこちら
人を動かすことができる大型のモータであれば、以下のようなものがあり、専用のモータドライバユニットにArduinoを接続しPWM制御可能です。
オリエンタルモータ BLHシリーズ/100W/DC24V(このモータを使った説明
また、以下のようなロボット用/精密機械用のモータであれば、小型なものから大型のものまであります。
マクソンモータ(スイス製)
光進電気工業(ファールハーバー/ドイツ製)
ツカサ電工


松下電器産業 ギアーヘッド

8/18/2008

Arduino デジタルコンパス/HMC6352

デジタルコンパス(方位センサ)によって、物体の方位を磁気的に調べることができます。Arduinoで制御可能あるいは入手しやすいデジタルコンパスとして幾つか以下に挙げておきます。

デジタルコンパスの種類:
・「RDCM-802」(秋月電子:¥3400
・「HMC1052L」(Sparkfun:$24.95ストロベリーリナックス:¥3480
・「HMC6352」(Sparkfun:$59.95ストロベリーリナックス:¥7350スイッチサイエンス:¥6980
・「HM55B(日立製)」(Parallax:$29.99日本マイクロボット教育社:¥4450
・「CMPS03」(RoboticsConnection:$55)

それぞれの性能など:
「RDCM-802」は、3ビットの信号で8方位の解像度しかないので、大体の向きを調べるくらいしかできません。
「HMC1052L」は、ArduinoのanalogRead()で入力すれば10ビット(1024段階)の解像度が得られます。
「HMC6352」は、I2C通信で0.0〜359.9度(0.1度ずつ)の分解能があります。
「HM55B」は、シリアル通信で11ビット(2048段階)の解像度が得られます。
「CMPS03」は、I2C通信で3600の分解能。

サンプルソースについて:
「HMC6352」については、WiringのExamplesのサイトにサンプルソース(Standbyモード)があり、I2C通信のライブラリを用いればArduinoでも使用可能になります。ArduinoのPlaygroundにもサンプルがあります。
「HM55B」については、ArduinoのPlaygroundのサイトにサンプルソースがあります。
「CMPS03」については、Arduino用のライブラリがあります。

HMC6352の使い方:
今回は、「HMC6352」デジタルコンパスモジュールの実験をしてみます。
「HMC6352」は、I2C通信で方位データを得るので、I2C通信のライブラリを用います。ライブラリを取り入れるには、メニューバーのSketch>Import Library>Wireを選択することで「#include <Wire.h>」の一文が自動的にプログラムを書く欄に挿入されます。ArduinoでのI2C通信は、SDA端子(データ用端子)はアナログ入力の4番ピン、SCK端子(クロック用端子)はアナログ入力の5番ピンに限定されます。「HMC6352」は、5V電源でも許容範囲ですが今回は3.3Vを使用することにします。接続方法は以下の様になります。



アドレスについて:
I2C通信では、マスターデバイスとスレーブデバイスという関係性があります。マスターデバイスには複数のスレーブデバイスが接続できるメリットがあり、複数のスレーブデバイスはマスターデバイスから制御されることになります。今回の場合は、Arduino基盤がマスターデバイス、HMC6325がスレーブデバイスになります。複数のスレーブデバイスが接続可能であるため、スレーブデバイス側にはどのデバイスであるかを識別するためのアドレスが必要となります。データシートによれば、HMC6325のデフォルトのアドレスは16進数の「0x42」であり、今回はこのアドレスを使うことにします。ただし、識別されるアドレスは8ビット中の上位7ビットであり、「0x42」を1ビット右にシフト「>>」した値になります(16進数の「0x42」は、二進数の「01000010」であり、1ビット右にシフトするということは、二進数の「01000010」の桁を右に一桁ずらすことになるので、「00100001」(=0x21)になるということです/ビットシフトに関しては「Arduino-Processing シリアル通信3」を参照)。

モード切替について:
HMC6325には、マスターデバイスから「A」を送信することで方位を計測開始しデータを読み取る「Standbyモード」と、一旦「A」を送信後データを読み取るごとに自動的に計測/出力する「Queryモード」、そして設定した周期(1Hz、5Hz、10Hz、20Hz、)で連続的に計測/出力した結果を読み取る「Continuousモード」があります。
今回は「Continuousモード(20Hz)」を使うことにします。この設定をするためには、RAM書き込み用コマンド「G」、書き込み先(レジスタ)の「0x74」、そして設定内容の8ビット「0111010」あるいは16進数の「0x72」を送信します。ちなみに、設定内容については、

0x50:Standbyモード(デフォルト)
0x51:Queryモード
0x52:Continuousモード(10Hz,Periodic Set/Reset=ON)
0x72:Continuousモード(20Hz,Periodic Set/Reset=ON)

となります(詳細はデータシートを参照して下さい)。
シリアルモニターを使って読み取り値を出力することにします。


//I2C通信ライブラリを取り込む
#include <Wire.h>

//デジタルコンパスモジュールのアドレス設定
int compassAddress = 0x42 >> 1; //=0x21
//読み込み値(角度)の変数を用意
int reading = 0;

void setup() {
//I2C通信開始
Wire.begin();
//角度表示のためのシリアル通信開始
//Serial.begin(9600);

//Continuous Modeに設定する
Wire.beginTransmission(compassAddress);
//RAM書き込み用コマンド
Wire.send('G');
//書き込み先指定
Wire.send(0x74);
//モード設定
Wire.send(0x72);
//通信終了
Wire.endTransmission();
//処理時間
delayMicroseconds(70);
}

void loop() {
//デバイスに2バイト分のデータを要求する
Wire.requestFrom(compassAddress, 2);
//要求したデータが2バイト分来たら
if(Wire.available()>1){
//1バイト分のデータの読み込み
reading = Wire.receive();
//読み込んだデータを8ビット左シフトしておく
reading = reading << 8;
//次の1バイト分のデータを読み込み
//一つ目のデータと合成(2バイト)
reading += Wire.receive();
//2バイト分のデータを10で割る
reading /= 10;
Serial.println(reading);
}
//処理のために少し待つ(20Hz)
delay(50);
}


まずは、Wire.begin()でI2C通信を開始します。「HMC6325」の初期設定では、「Standbyモード」になっているため、setup(){...}内で「Continuousモード」に切り替えます。設定内容を「HMC6325」のRAMに書き込むために「G」というコマンド、書き込み先となる専用レジスタの「0x74」、設定内容となる8ビットの「0x72」をWire.send()を使って送信します。
方位角度の読み込みは、Wire.requestFrom()でアドレスと何バイト分のデータかを指定して要求します。計測結果は0〜3599までの値(360度を10倍した値)を2バイト(16ビット)で返してきます。Wire.available()によって2バイト分読み込み可能なデータをカウントしたら、Wire.receive()によって、2バイトの値を2回に分けて読み込みます。一度に読み込むことができる値は1バイト(8ビット、0〜255)までなので、最初の1バイトを読み込んだら、その値を8ビット左にビットシフト(8桁左にビットシフトするということは、256倍することと等しくなります)、その値に次の1バイト分のデータを加算します。値は10倍されているので、最終的に10で割って0〜359度の角度として取り込みます(float型を使えば0.0〜359.9で出力します)。20Hzごとに計測する「Continuousモード」に設定したので、delay(50)を挿入してループの周波数も20Hzにしておきました。

測定結果をProcessingへシリアル通信するのであれば、2回に分けて読み取った値(2バイト分)をそのまま1バイトずつ2回送信(合計2バイト)し、Processing側で受け取ってから二つの値を合成すればいいでしょう。値を合成するには、一個目の値を左に8ビットシフト(または256倍)してから二個目の値を足せば0〜3599の値として得ることができるはずです。BYTEフォーマットなら、そのまま2回で送信します。文字列でデリミタ(区切り記号)を挿入して送信する場合は、2回目の送信時にSerial.println()で改行記号を用いて送信します(Processing側での受信については、「Arduino-Processing シリアル通信3(複数の値を送信する場合)」あるいは「Arduino-Processing シリアル通信5(文字列で送信する場合)」を参照して下さい。

BYTEフォーマットで送信する場合:

void loop() {
Wire.requestFrom(compassAddress, 2);
if(Wire.available()>1){
//2回に分けて読み取った値を
//BYTEフォーマットで2個送信する
Serial.print(Wire.receive(),BYTE);//1個目
Serial.print(Wire.receive(),BYTE);//2個目
}
delay(50);
}

あるいは、文字列として送信する場合:

void loop() {
Wire.requestFrom(compassAddress, 2);
if(Wire.available()>1){
//2回に分けて読み取った値を
//DECフォーマットで2個送信する
Serial.print(Wire.receive(),DEC);
Serial.print(",");//デリミタを挿入送信
Serial.println(Wire.receive(),DEC);//改行記号つき
}
delay(50);
}


また、「C」を送信することでキャリブレーションモードに入り、周囲の磁気の影響によるモジュール内素子のゆがみをなくし正常な状態に調整することが出来ます。モジュールを平らな場所に置いてから6秒から3分以内に、ニ周程度回転させ、「E」を送信することで終了します。20秒以上かけて2回転させれば、正確なキャリブレーションになります。

//キャリブレーション開始コマンド送信
Wire.beginTransmission(compassAddress);
Wire.send('C');
Wire.endTransmission();

//30秒ほど待つ(6秒〜3分まで)
//この間に数回モジュールを回転させる
delay(30000);

//キャリブレーション終了コマンド送信
Wire.beginTransmission(compassAddress);
Wire.send('E');
Wire.endTransmission();


デジタルコンパスをモータなどの磁気を発するものの近くに設置すると磁気的影響を受けるので、少し離れた場所に設置したほうがいいでしょう。

8/11/2008

Arduino+Processing マトリクスLED+Webカメラ

今回は、Webカメラから取り込んだ映像をArduinoに接続した8×8マトリクスLEDに映す実験を行います。まず、ProcessingでWebカメラからの映像を8×8ピクセルで取り込み、合計64個のピクセルの明るさの値(0〜255)を調べてから、その個々の値をシリアル通信でArduinoに送ります。Arduino側では、受け取った64個分の値をマトリクスLEDの個々の明るさに反映させます。Arduino基盤とマトリクスLEDとは、ICを使わず直結することにします(接続方法は「Arduino マトリクスLED1」を参照)。
Processingの画面では、マトリクスLEDの表示シミュレーション(モニタリング)を同時に行うことにします(前回行ったモザイク処理のような方法で赤い円を64個映し出すことにします)。


「Processingの画面(モニタリング)」

Processingのプログラム:

//ビデオライブラリを取り込む
import processing.video.*;
//キャプチャ用オブジェクトを用意
Capture video;

//シリアル通信ライブラリを取り込む
import processing.serial.*;
//シリアル通信オブジェクトを用意
Serial port;

//64個分のピクセル色の配列を用意
int[] pixelValue=new int[64];

//シリアル通信開始用フラグ
boolean start=false;

void setup(){
//画面を160角に設定
size(160,160);
//描画を滑らかにする
smooth();

//映像キャプチャの設定(幅8,高さ8ピクセル,フレームレート30)
video = new Capture(this, 8, 8, 30);

//ポートの設定
port=new Serial(this,"/dev/tty.usbserial-A40014iU",9600);

//外形線なし
noStroke();
}

void draw(){
//背景を黒で塗る
background(0);

//64個分のピクセルの処理
for(int i=0;i<64;i++){
//映像の各ピクセルの色の値を
//明るさの値に変換する
pixelValue[i]=int(brightness(video.pixels[i]));

//円の塗色(赤の値)に代入
fill(pixelValue[i],0,0);
//円を描画
ellipse((i%8)*20+10,(i/8)*20+10,15,15);

//値を送信
if(start){
port.write(pixelValue[i]);
}
}
}

//キャプチャ映像読み込み
void captureEvent(Capture video) {
video.read();
}

クリックでシリアル通信開始
void mousePressed(){
start=true;
}


Processingの方では、VideoライブラリSerialライブラリの二つを取り込む必要があります。マトリクスLEDが8×8の解像度なので、Webカメラから取り込む映像の解像度も8×8にしておきます(カメラ映像の横縦比は4:3なので、少し縦長の映像になってしまいます)。
*Windowsの場合、そのままの設定ではこのVideoライブラリを使用することができません。WinVDIG 1.0.1をインストールする必要があります。
「pixelValue[i]=int(brightness(video.pixels[i]))」では、まず映像の各ピクセルの色をpixels[]で読み込みます。pixels[]は、RGBの三色の値(三つの値)を含んでおり、brightness()で括ると明るさの値(一つの値)に変換されます(0〜255)。変換された値はfloat(小数)なのでint()で括って整数に変換しておきます。この値を、fill()の赤の値に代入し(緑と青は0)、LEDのような赤い円をellipse()を使って64個描画します。8×8を160×160の画面で表示しているので、20×20ピクセルのグリッド状に配置されます。ellipse()の直径はとりあえず15にしておきました。ellipse()のXとY座標は、「%」と「/」を使って計算します(X座標となる「(i%8)*20+10」は、iを8(横幅)で割った余りに20ピクセル掛けて、さらに10ピクセル足すことでellipse()が20×20のグリッドの中心に来るように位置調整しています)。最後に「port.write(pixelValue[i])」で、Arduinoへ各ピクセルの明るさの値を送信します。今回は画面をクリックしたらシリアル通信が開始されるようにしています。

次にArduinoの方に移ります。冒頭で書いたように、今回はマトリクスLEDを、ICを使わず直結します。個々のLEDはダイナミック点灯しているので、点灯時間の長さによって明るさを調整することになります。点灯時間が短ければ暗くなり、長くなれば明るくなります。つまり、Processingから送られて来た明るさの値(0〜255)を、個々のLEDの点灯時間に反映させるプログラムになるということです。マトリクスLEDとの接続方法や詳細については「Arduino マトリクスLED1」を参照してください。

Arduinoのプログラム:

//8x8の二次元配列を用意
byte matrix[8][8];

void setup(){
//出力ピンの設定、すべてオフにする
for(int i=2;i<=17;i++){
pinMode(i,OUTPUT);
digitalWrite(i,LOW);
}
//シリアル通信開始
Serial.begin(9600);
}

void loop(){
//シリアル通信(64個分のデータ)
if(Serial.available()>63){
for(int k=0;k<8;k++){
for(int l=0;l<8;l++){
//読み込んだ値を配列に代入
matrix[k][l]=Serial.read();
}
}
}

//各LEDの点灯制御
for(int i=2;i<=9;i++){
//列の点灯
digitalWrite(i,HIGH);

for(int j=10;j<=17;j++){
//行の点灯
digitalWrite(j,LOW);
//行の点灯継続時間
delayMicroseconds(1+matrix[i-2][j-10]);
//行の消灯
digitalWrite(j,HIGH);
//行の消灯継続時間
delayMicroseconds(256-matrix[i-2][j-10]);
}
//列の消灯
digitalWrite(i,LOW);
}
}


以前の「Arduino マトリクスLED1」とほぼプログラム内容は同じです。異なる部分は、シリアル通信と各LEDを点灯/消灯させる継続時間の部分です。用意する二次元配列は、boolean型ではなくbyte型(0~255の値なので)にしています。
シリアル通信上で干渉しないようにするため、Arduino基盤の0番ピンと1番ピンには何も接続しないことにしています(2〜17番ピンを使用)。シリアル通信では、Processingから送られてくる64個のデータをSerial.available()でカウントして、それぞれの値を予め用意しておいた二次元配列matrixに保存しておきます。送られてくるデータは0〜255(明るさの値)となります。そしてダイナミック点灯していく際に、明るさの値をdelayMicroseconds()に代入して、点灯継続時間と消灯継続時間に割り当てます。delayMicroseconds(0)としてしまうと、0マイクロ秒としては扱ってくれないので、delayMicroseconds()の括弧内に入れられる最小値は1にしてあります。最小1マイクロ秒の点灯時間かつ最大256マイクロ秒の消灯時間のときが最も暗くなるときです。その逆で、最大256マイクロ秒の点灯時間かつ最小1マイクロ秒の消灯時間のときが最も明るくなります。個々のLEDは257マイクロ秒ごとにダイナミック点灯していることになります。個々のLEDの点滅を300マイクロ秒程度にすると点滅しているようには見えないので、今回の257マイクロ秒周期で点滅させれば、ほぼ問題ないでしょう。

関連:
・「シリアル通信1〜5
・「Arduino マトリクスLED1
・「Processing Video(Webカメラ)

ロジクールストア(ウェブカメラカテゴリ)

8/09/2008

Arduino マトリクスLED2/MAX7219

以前の「Arduino マトリクスLED1」では、8×8のマトリクスLEDをArduino基盤に直接接続して点灯させましたが、今回はMAX7219というLEDディスプレイドライバICを使って点灯してみます。Arduino基盤にマトリクスLEDを直結すると16個の端子が必要でしたが、MAX7219を使えば5V電源とGND以外に3本の端子で制御することが可能になります。原理的には、シリアルデータを送り、マトリクスLEDを表示させることになりますが、ArduinoにはマトリクスLED用のライブラリ(Wiringのライブラリ)があるので今回はそれを使ってプログラムしてみます。今回使用する8×8のマトリクスLEDは、以前同様、秋月電子で購入したものです。
以下のように、マトリクスLEDの各端子とMAX7219の端子が対応します(マトリクスLEDモジュールの側面に小さな凹凸部があり、MAX7219には端部に半円状の凹部や小さな丸印があるので、それらを手掛かりに向きを合わせてください)。



その他のMAX7219の端子については、
DIN:Arduinoからのシリアルデータ入力端子
DOUT:複数のMAX7219を接続する端子
VCC:5V電源
I_SET:接続する抵抗によって輝度を変えます
GND:AduinoのGNDと共有
LOAD:Arduinoからのデータ入力のロード用端子
CLK:Arduinoからのクロック信号入力端子

Arduino基盤との接続は以下のようになります。I_SETとLOAD端子には、10KΩの抵抗を接続して下さい(I_SETの抵抗値を上げれば暗くなり、下げれば明るくなります)。画像をクリックすれば大きくなります。



既存のサンプルを使って点灯実験してみます。Arduinoの画面からFile>Sketchbook>Examples>Library-Matrix>hello-matrixを選ぶと、プログラムが現れますが、ピン設定に少し変更を加えます。
既存サンプルでは、

pin 0: data (din)
pin 1: load (load)
pin 2: clock (clk)

となっていますが、0番ピンと1番ピンは使わず、

pin 2: data (din)
pin 3: load (load)
pin 4: clock (clk)

に変更することにします。こうすることで、プログラムのデータをアップロードする時やシリアル通信の際に信号が干渉されずにすみます。


//ライブラリを取り込む
#include <Sprite.h>
#include <Matrix.h>

//各ピンの設定
//pin 2: DIN
//pin 3: LOAD
//pin 4: CLK
Matrix myMatrix = Matrix(2, 4, 3);

void setup(){
}

void loop(){
myMatrix.clear(); //表示内容をクリアする

delay(1000);

//ピクセルを指定し表示する
myMatrix.write(1, 5, HIGH);
myMatrix.write(2, 2, HIGH);
myMatrix.write(2, 6, HIGH);
myMatrix.write(3, 6, HIGH);
myMatrix.write(4, 6, HIGH);
myMatrix.write(5, 2, HIGH);
myMatrix.write(5, 6, HIGH);
myMatrix.write(6, 5, HIGH);

delay(1000);
}


以下のように表示(1秒おきに点滅)されれば、配線などに問題ないことになります。write()で、X座標(0〜7)とY座標、ならびにHIGHかLOW(あるいは1か0)を代入し個々のLEDの点灯/消灯を指定します。



最初のArduino側のピンの設定(既存サンプルのコードに変更を加える)と、配線を間違わなければ、座標指定で点灯/消灯を制御できるので、それほど難しくはないと思います。今回のマトリクスLED用のライブラリを使うには、メニューバーのSketch>Import Library>MatrixとSpriteを選択すればインクルードされます。

次は、Spriteという基準となる絵柄を設定し、それが動く(横に流れる)表示方法を行います。Arduinoの画面からFile>Sketchbook>Examples>Library-Matrix>sprite-animationを選択し、このサンプルを使うことにします。前回同様、ピン設定は、

pin 0: data (din)
pin 1: load (load)
pin 2: clock (clk)

となっていますが、0番ピンと1番ピンは使わず、

pin 2: data (din)
pin 3: load (load)
pin 4: clock (clk)

に変更します。


//ライブラリを取り込む
#include <Sprite.h>
#include <Matrix.h>

//各ピン設定
//DIN:2
//LOAD:3
//CLK:4
Matrix myMatrix = Matrix(2, 4, 3);//DIN,CLK,LOAD

//スプライトの指定
//幅、高さ、絵柄を二進数で指定
Sprite wave = Sprite(
8, 4,
B00011000,
B00100100,
B01000010,
B10000001
);

void setup()
{
}

//X座標の変数を用意
int x = 0;

void loop(){
//スプライトの配置
myMatrix.write(x, 2, wave);
//もうひとつのスプライトの配置
myMatrix.write(x - 8, 2, wave);
//点灯時間設定
delay(75);
//画面をクリア
myMatrix.clear();

//アニメーション(カスケーディング)
if(x == 8){//8になったら0に戻す
x = 0;
}
x++;//X座標の変数を増加させる
}


Sprite()で、絵柄の幅、高さを指定し、二進数で点灯させる絵柄(0は消灯箇所、1は点灯箇所)をつくり設定します。write()で、絵柄の配置座標(左上の座標)と絵柄のオブジェクトを指定し表示させます。write()の括弧内のX座標やY座標は変数を用いて変化させることができるので、カスケーディング(絵柄が流れて表示される)の制御も簡単にできます。このサンプルでは、8コマ前にもうひとつの同じ絵柄を用意しておき、二つの絵柄を使用して途切れなく連続して絵柄が流れていくように表示しています。

また以下のようにSpriteを複数用意しておけば、入れ替わりで異なる絵柄を表示できます。


//ライブラリを取り込む
#include <Sprite.h>
#include <Matrix.h>

//各ピンの設定
//pin 2: DIN
//pin 3: LOAD
//pin 4: CLK
Matrix myMatrix = Matrix(2, 4, 3);

//スプライトの指定
//幅、高さ、絵柄を二進数で指定
Sprite pattern1 = Sprite(
8, 8,
B11111111,
B00000000,
B11111111,
B00000000,
B11111111,
B00000000,
B11111111,
B00000000
);

Sprite pattern2 = Sprite(
8, 8,
B10101010,
B10101010,
B10101010,
B10101010,
B10101010,
B10101010,
B10101010,
B10101010
);

Sprite pattern3 = Sprite(
8, 8,
B11110000,
B11110000,
B11110000,
B11110000,
B00001111,
B00001111,
B00001111,
B00001111
);

Sprite pattern3 = Sprite(
8, 8,
B00001111,
B00001111,
B00001111,
B00001111,
B11110000,
B11110000,
B11110000,
B11110000
);


void setup()
{
}

//X座標の変数を用意
int x = 0;

void loop(){
//スプライトの配置
myMatrix.write(0, 0, pattern1);
//点灯時間設定
delay(200);
//画面をクリア
myMatrix.clear();

myMatrix.write(0, 0, pattern2);
delay(200);
myMatrix.clear();

myMatrix.write(0, 0, pattern3);
delay(200);
myMatrix.clear();

myMatrix.write(0, 0, pattern4);
delay(200);
myMatrix.clear();
}


関連:
・「Arduino マトリクスLED1」(ICを使わずマトリクスLEDを制御する方法)
MAX7219データシート(MAXIM JAPAN)

7/05/2008

Arduino マトリクスLED1

今回はマトリクス LEDの表示実験をします。秋月電子で購入した8×8マトリクスLEDを使用します。8×8なので合計64個のLEDが搭載されています。それぞれのLEDを直接点灯させるためには、64個分の端子が必要であり、Arduinoの端子の数以上になってしまいますが、ダイナミック点灯(説明以下)という方法で可能になります。
ArduinoにはマトリクスLED用ライブラリWiringのライブラリを利用)を使う方法もありますが、MAX7219というLEDディスプレイドライバICを必要とします。このICを使えば、Arduinoからはシリアル通信を通して3本の線で制御することができます(MAX7219との接続サンプル)。また、74HC595というICを二つ使う方法(サンプル)もあります。
今回はICを使わずに、マトリクスLEDの16個の端子にArduinoを接続する方法で制御します。16個の端子のうち8個がアノード(プラス)で残り8個がカソード(マイナス)の端子になります。LEDの点灯箇所と端子の対応は以下のようになります。LEDモジュールの4辺(側面)には小さな凹凸があるので、それを手掛かりに向きを合わせて下さい。



Arduinoからはダイナミック点灯という方法で制御することになります。そうすることで、合計16本の端子で64個のLEDを個別に制御することができます。ダイナミック点灯は、順番に一列(8個のLED)ずつ高速点灯させ、人間の目には8列全部が同時に点灯しているように見せる方法です。列ごとに点灯させる順番やタイミングをdigitalWrite()のHIGH/LOWの組合わせで制御します。基本的には、横方向の端子にプラスを、縦方向の端子にマイナスを接続することで、その交差した部分のLEDが点灯する仕組みになっています。
Arduinoのデジタル出力ピンは、通常0から13番までしかないのですが、pinMode()で設定することで、アナログ入力の6個のピン(0から5番ピン)もデジタル出力用に切り替えることができます。その場合、順番にデジタル出力14から19番のピンとして扱うことができ、合計で20個のデジタル出力が可能になります。もともとデジタル入出力ピンの0番と1番はシリアル通信などで使うので(プログラムをアップロードするときにも干渉することがあるので)、できれば接続しないほうがいいでしょう。今回は2番ピンから17番ピンまでを使うことにします。
LEDのカソード側(マイナス側)には抵抗(1KΩ)を取付けます。Arduinoのデジタル出力13番ピンには既に1KΩの抵抗が内蔵されているので、それ以外の7端子に取付けることとします。抵抗をつけなくても多少負荷はかかりますが実験はできます。ただし、Arduinoの13番ピンを接続している列(4列目)だけが、暗くなってしまいます。
接続方法は以下のようになります(画像をクリックすれば大きくなります)。ブレッドボードで実験する場合、大きなもの(幅のあるもの)を用意するか、小さなブレッドボードを2枚用意して、2枚にまたがるようにLEDモジュールを差し込むと作業しやすいと思います。



それぞれのArduinoの出力ピンで行と列で表せば、以下のようになります。



まず、一行ずつ点灯させていきます。
PIN_2行目のPIN_10列目とPIN_12列目の二つを点灯させるためには、

PIN_2:HIGH
PIN_10:LOW
PIN_12:LOW

となりますが、同時に消灯させる列もあるので

PIN_11:HIGH
PIN_13:HIGH
PIN_14:HIGH
PIN_15:HIGH
PIN_16:HIGH
PIN_17:HIGH

とします。行(PIN_2〜PIN_9)、列(PIN_10〜PIN17)とすれば、
点灯させるには、

行:HIGH、列:LOW

という組合わせになり、
消灯させるには、

行:HIGH、列:HIGH
行:LOW、列:HIGH
行:LOW、列:LOW

という3つの組合わせがあります。列(縦)側の端子と行(横)の端子の両方をHIGH(5V)にすると消灯するということを覚えておいて下さい(電位差が0Vになるので)。それ以外の「LOW:HIGH」、「LOW:LOW」の組合わせでも消灯します。

次のPIN_3行目に移る前に(ある程度の時間点灯させた後に)PIN_2行目をLOWにすることで、次回PIN_3行目を制御するときにPIN_2行目が点灯しないように後処理しておきます。PIN_3行目についても同様の手順で行い、合計8回高速に繰り返すことで、全体が点灯しているように見えます。

まずは、以下のプログラムで、64個のLEDが順番に個別に点灯するか実験してみます。


void setup(){
//16本のピン(2~17)を出力に設定
for(int i=2;i<=17;i++){
pinMode(i,OUTPUT);
}
}
void loop(){
//行(横)の繰り返し処理
for(int i=2;i<=9;i++){ //行(2~9番ピン)
digitalWrite(i,HIGH); //HIGHで点灯

//列(縦)の繰り返し処理
for(int j=10;j<=17;j++){ //列(10~17番ピン)
digitalWrite(j,LOW); //LOWで点灯
delay(100); //点灯時間
digitalWrite(j,HIGH); //列をオフにする
}

digitalWrite(i,LOW); //行をオフにする
}
}


それぞれ一つずつ順番に点灯していけば、配線などに間違いがないということになります。プログラムの順番としては、1行目の中の1列目から8列目までを順番に点灯し、次に2行目の中の1列目から8列目までを順番に点灯し、同様に8行目まで繰り返します。delay(100)の部分が一つのLEDの点灯時間であり、0.1秒に設定されています。この点灯時間を短くしていくと、残像現象により一度に複数のLEDが点灯しているように見え始めます。次のサンプルでは、点灯時間を0.03秒に設定し、二次元配列を用いて、予め用意しておいた表に基づいて点灯させる方法を行います。


boolean matrix[8][8]={
{0,0,0,1,1,0,0,0},
{0,0,1,0,0,1,0,0},
{0,1,0,0,0,0,1,0},
{0,1,0,0,0,0,1,0},
{0,1,0,0,0,0,1,0},
{0,1,1,1,1,1,1,0},
{0,1,0,0,0,0,1,0},
{0,1,0,0,0,0,1,0}
};

void setup(){
for(int i=2;i<=17;i++){
pinMode(i,OUTPUT);
digitalWrite(i,LOW);
}
}
void loop(){
for(int i=2;i<=9;i++){
digitalWrite(i,HIGH); //行:HIGHで点灯
for(int j=10;j<=17;j++){
if(matrix[i-2][j-10]==1){//点灯条件
digitalWrite(j,LOW); //列:LOWで点灯
}
//上のif文のかわりに以下でも可
//digitalWrite(j,!matrix[i-2][j-10]);

delayMicroseconds(300);//0.03秒点灯
digitalWrite(j,HIGH);//オフにする
}
digitalWrite(i,LOW);//オフにする
}
}


8×8の二次元配列matrix(名前は任意)を用意して、その配列内に0か1で消灯/点灯の表をつくります。matrix[行][列]という対応になります。matrix[0][3]であれば、0行3列目の値となります。
このフォーマットをもとに、とりあえず「A」という文字をつくってみました。表の「1」のところを点灯させるために、if文で条件設定し、表座標の値が「1」なら、その箇所をLOWで出力します。今回接続しているピンの番号と配列の順番の数値のつじつまを合わせるために、「matrix[i-2][j-10]」としています(行:2番ピンが0番目の内部配列に対応するので[i-2]、列:10番ピンが内部配列内の0個目の値に対応するので[j-10]になります)。if文を使わずに、digitalWrite(j,!matrix[i-2][j-10])と書いても同じことになります。今回は、LOWで点灯するので、「!」を使って表座標の値が「0」のとき「1」(HIGH)になり、「1」のとき「0」(LOW)になるように反転します。
delayMicroseconds(300)は、一つずつ高速に点滅する時間です。高速なので点滅しているようには見えませんが、もし点滅しているように見えてしまう場合は数値を低くして、点滅のスピードを上げて下さい。
このように二次元配列matrixを使うことで、64個分のLEDの点灯/消灯状態を指定して表示可能になります。

次は表示文字をカスケーディング(文字が流れるように動く)してみたいと思います。左向きに文字が流れるようにするには、

matrix[k][l]=matrix[k][l+1];

というように、配列内の縦一列の値を右隣の値(+1の列の値)に移し替えればいいことになります。さらに、左側へ流れた文字が再び右側から出てくるように繰り返して表示されるようにするためには、画面右端の8列目の値(配列内7番目の値)が、1列目(配列内0番目の値)になるようにします。使用している二次元配列は8×8ですが、余白をもう一列つけたして8×9にしておきます。それで、

matrix[k][8]=matrix[k][0];

とすれば、余白である9列目(配列内の8番目の値)に1列目(配列内の0番目の値)が代入され、繰り返し表示されることになります。しかしこのままでは、横に流れるスピードが速すぎるので、while文を用いて表示される時間を引き延ばします。while文では、以下のように()内に条件を入れ、その条件が満たされている限り繰り返し処理を行います。

int count=5;
while(count>0){
//繰り返される内容をここに書く

count--; //カウント数を減らしていく
}

という書き方をすれば、while(){...}内の処理を5回繰り返すということになります。つまり、先ほどのLEDを順番に点灯させるプログラム全体をwhile(){...}で括ってしまうということになります。以下のプログラムでは、1ループの中で、LEDを表示させる処理を5回繰り返し、それからカスケーディングのための処理を1回行う内容になります。


//余白の列を付けたし配列を8x9にしておく
boolean matrix[8][9]={
{0,0,0,1,1,0,0,0,0},
{0,0,1,0,0,1,0,0,0},
{0,1,0,0,0,0,1,0,0},
{0,1,0,0,0,0,1,0,0},
{0,1,0,0,0,0,1,0,0},
{0,1,1,1,1,1,1,0,0},
{0,1,0,0,0,0,1,0,0},
{0,1,0,0,0,0,1,0,0}
};

void setup(){
for(int i=2;i<=17;i++){
pinMode(i,OUTPUT);
digitalWrite(i,LOW);
}
}
void loop(){
int count=5;//この値を大きくすればゆっくり流れる
while(count>0){
for(int i=2;i<=9;i++){
digitalWrite(i,HIGH); //行:HIGHで点灯
for(int j=10;j<=17;j++){
if(matrix[i-2][j-10]==1){//点灯条件
digitalWrite(j,LOW); //列:LOWで点灯
}
//上のif文のかわりに以下でも可
//digitalWrite(j,!matrix[i-2][j-10]);

delayMicroseconds(300);
digitalWrite(j,HIGH);//オフにする
}
digitalWrite(i,LOW);//オフにする
}
count--;//回数カウント1回減らす
}

//カスケーディング
for(int k=0;k<=7;k++){
//0列目から余白の8列目まで計算する
for(int l=0;l<=8;l++){
if(l==8){ //配列8列目は0列目の値を代入
matrix[k][8]=matrix[k][0];
}else{ //それ以外の列は+1列の値を代入
matrix[k][l]=matrix[k][l+1];
}
}
}
}


また、配列を大きくして以下のようにすれば、複数の文字を表示できます。


//8x25の配列にする(25列目は余白)
boolean matrix[8][25]={
{0,0,0,1,1,0,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0},
{0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0},
{0,1,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0},
{0,1,0,0,0,0,1,0,0,1,1,1,1,1,0,0,0,1,0,0,0,0,0,0,0},
{0,1,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0},
{0,1,1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0},
{0,1,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0},
{0,1,0,0,0,0,1,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0}
};

void setup(){
for(int i=2;i<=17;i++){
pinMode(i,OUTPUT);
digitalWrite(i,LOW);
}
}
void loop(){
//変更なし
int count=5;
while(count>0){
for(int i=2;i<=9;i++){
digitalWrite(i,HIGH);
for(int j=10;j<=17;j++){
digitalWrite(j,!matrix[i-2][j-10]);
delayMicroseconds(300);
digitalWrite(j,HIGH); //LED OFF
}
digitalWrite(i,LOW); //LED OFF
}
count--;
}

//カスケーディング
for(int k=0;k<8;k++){
//以下の配列数の値を変更しておく
for(int l=0;l<=24;l++){
if(l==24){
matrix[k][24]=matrix[k][0];
}else{
matrix[k][l]=matrix[k][l+1];
}
}
}
}


今回のプログラムでは、loop(){...}内に、while(){...}という小さなループがあり、その中に、for(){...}で横1行ずつの繰り返し処理を行い、さらにその中にもうひとつのfor(){...}で縦1列ずつの繰り返し処理を行うというように、何重にも繰り返しループの処理が組み込まれています。結果的なコードを見ると分かりにくいかもしれませんが、最初から順を追って考えていけば、その仕組みが見えてくると思います。

関連:MAX7219(LEDディスプレイドライバIC)を用いる方法


6/29/2008

Arduino ビデオ信号/バウンドするドット



前回のテレビ画面への出力実験では、縦縞や矩形という静止画を扱いましたが、今回はすこし応用して小さなドットがバウンドする映像をArduinoによって出力します。水平同期信号だけでなく、垂直同期信号も用います。今回はできるだけ細かく信号を扱うため、より精度を出す方法を用います。前回のようにdelayMicroseconds()のかわりに、_delay_us()を用いますが、_delay_us()の括弧内には変数ではなく固定した値である定数を入れます。変数を入れると演算に時間がかかってしまうために、画面は乱れてしまいます。そのかわり、ドットの動きを反映させる変数には、delayMicroseconds()を使ってもいいのですが、今回は_delay_loop_1()というコマンドを使います。これもまたArduinoのリファレンスには載っていないのですが、_delay_loop_1()を使えば、より細かなディレイを扱えます。垂直同期信号以外に、等価パルスという信号(走査線1〜3と7〜9本目に挿入)もあるのですが、今回は使わずにプログラムしました。必要な部品や配線については、前回の記事を参照してください。

バウンドするドットのサンプル:

#include <util/delay.h>

//それぞれの定数を定義する
//同期信号、3色の出力設定
#define SYNC (PORTB=0) //B00000000 //0.0V
#define BLACK (PORTB=1) //B00000001 //0.3V
#define GRAY (PORTB=2) //B00000010 //0.6V
#define WHITE (PORTB=3) //B00000011 //1.0V

//水平ブランキング期間の各時間設定
#define FRONTPORCH 1.5 //フロントポーチ
#define HSYNCPULSE 4.7 //水平同期信号
#define BACKPORCH 4.7 //バックポーチ

//走査線1本の周期
#define FULLLINE 63.5
//走査線半分の時間
#define HALFLINE 31.75
//垂直同期信号の時間
#define VSYNCPULSE (HALFLINE-FRONTPORCH-HSYNCPULSE)//25.55us
//水平ブランキング期間を除いた描画範囲の時間
#define IMAGEWIDTH (FULLLINE-FRONTPORCH-HSYNCPULSE-BACKPORCH)//52.6us
//走査線全体の数
#define ENDLINE 262

//水平同期信号(水平ブランキング期間)
void hsync(){//10.9us
BLACK;
_delay_us(FRONTPORCH);//1.5us
SYNC;
_delay_us(HSYNCPULSE);//4.7us
BLACK;
_delay_us(BACKPORCH);//4.7us
}

//垂直同期信号
void vsync(){//31.75us
BLACK;
_delay_us(FRONTPORCH);//1.5us
SYNC;
_delay_us(VSYNCPULSE);//25.55us
BLACK;
_delay_us(HSYNCPULSE);//4.7us
}

void setup(){
//出力ピン設定
//PIN8(with 1000 Ohm):0.3V
//PIN9(with 330 Ohm):0.6V
pinMode(8,OUTPUT);
pinMode(9,OUTPUT);
//割り込み禁止設定
noInterrupts();
}

//走査線の数の変数
int line=1;
//ドットの上下加速度の変数
int vAcc=2;
//ドットの上下動きの変数
int vSpeed=0;
//ドットの左右動きの変数(プラスだけの整数)
unsigned int hSpeed=10;
//ドットの左右方向の変数(右向き1、左向き-1)
int hDirection=1;

void loop(){
//4〜6本目の走査線で垂直同期させる
if(line>=4 && line<=6){
vsync();//垂直同期を2回送信
vsync();
}
else{ //描画するための走査線(4〜6本目以外)
hsync(); //水平同期信号
//ドットの上下背景を黒で塗りつぶす
//走査線75と76がドットの範囲(vSpeedが0の時)
if(line<=75+vSpeed || line>77+vSpeed){
//背景横幅の時間(52.6us)を調整のため
//二つの値に分割して設定
//48usは_delay_us()の最大値、
//3.6usは同期するように調整した値
//調整した合計時間は51.6usになる
BLACK;
_delay_us(48);
_delay_us(3.6);
}
else{ //ドットの描画
//余白調整用の黒背景
BLACK;
_delay_loop_1(15);
//ドットの左側背景部分を黒に塗る
BLACK;
_delay_loop_1(hSpeed);
//ドット本体を白に塗る
WHITE;
_delay_loop_1(1);
//ドットの右側背景部分を黒に塗る
BLACK;
_delay_loop_1(256-hSpeed);
}
}

//走査線のカウントアップ
line++;
//最後の走査線まで来たら
if(line>ENDLINE){
line=1; //最初に戻る

//ドットの上下運動の計算
vAcc+=1; //加速度(速度の加算)
vSpeed+=vAcc/8; //8で割って少し遅くする
if(vSpeed>180){//速度が180を超えたら減速
vSpeed=180;
vAcc*=-1; //加速度の向きを変える
}
//ドットの左右方向の計算
hSpeed+=hDirection; //加速
//画面両端まで行ったら向きを変える
if(hSpeed<=1 || hSpeed>=255){
hDirection*=-1;
}
}
}


追記:
プログラムの手順としては、
・同期信号、黒、グレー、白に対する出力ピンの設定(8番、9番ピン)
・水平同期信号(水平ブランキング期間)の設定
・垂直同期信号の設定(必要であれば、等価パルスの設定)
・それぞれに用いる時間の定数を#defineで定義
・1本ずつ走査線を出力(合計262回:262ループ)
となります。

_delay_us():
括弧内に小数点を含んだマイクロ秒の数値を入れることができます。
ただし、変数を入れると演算が遅れてしまうので、#defineなどで定義した定数を入れる方がいいとリファレンスなどには書いてあります。
最大値は48マイクロ秒(Arduinoクロック数が16MHzなので)。それ以上ディレイする場合は、二つに分けて書くなど工夫が必要となります。

_delay_loop_1():
括弧内に変数として256までの整数値をいれることができます。
_delay_loop_1(256)で48マイクロ秒(最大値)。
_delay_loop_1(1)で0.1875マイクロ秒(最小値)。
_delay_loop1()は、3クロック分が処理時間になります。Arduinoは16MHzに設定されているので、1クロックが0.0625マイクロ秒になります。3クロック分なので、最小0.1875マイクロ秒単位で設定できることになります。今回のドットの横幅は_delay_loop_1(1)に設定したので、0.1875マイクロ分の長さになっています。走査線1本で描画できる範囲は、水平ブランキング期間(10.9マイクロ秒)を除いて、残りの52.6マイクロ秒になるので、数値的には52.6/0.1875で約280個の点に分割(解像度)できますが、コマンドによる処理時間も加えると、解像度はそれ以下にならざるを得ません。

それぞれの走査線の周期が異なっても、ある程度はテレビのほうで信号を調整してくれるとは思います。ただし、プログラム上で合計時間が合っていても、コマンドの数が多ければそれだけ時間がずれていくので、多少の時間調整を画面を見ながら行う必要があります。AVRマイクロコントローラは基本的に一つのコマンドで1クロックの時間がかかりますが、小数点の計算や条件文の設定などによっては、1クロック数以上かかるときがあるので、プログラムの仕方によって処理速度が変わってきます。その点を踏まえて、多少の時間調整を走査線ごとに行う必要がでてきます。そのために、配列を使って画面をピクセル状に分割し、毎回の処理速度が一定になるように、プログラムすればいいのかもしれません。
簡単な計測を行ってみると、forループを使うと毎回のループで5クロック程度、ifを使うと4クロック前後、変数を使うと3クロック前後消費していました。プログラム上でこれらのコマンドを組み合わせて使おうとすると、少なくても一つの値に対して、11クロック位は消費してしまうので、改善の余地はまだあるかもしれませんが、解像度は精々70程度かその半分程度になってしまうかもしれません。同時にRAM容量の限界もあるので、それほどきめ細かい解像度はあまり期待できなさそうですが、シンプルな表現として使うのであれば、まだ工夫できそうです。

より複雑で完成度がある映像をつくるのであれば、Processingでプログラムしモニターやプロジェクターで投影した方がいいはずです。ただ、Arduinoによる映像出力は、わざわざコンピュータと接続する必要もなく、乾電池などの外部電源を用いればスタンドアロンの装置として、簡単に既存のテレビモニターに接続可能になります。さらに発展させれば、BlueToothやXbeeなどの無線モジュールと組み合わせて、遠隔的に映像を配信することも可能になるかもしれません。同時に、今後利用されなくなるであろうブラウン管テレビモニターの再利用方法にもつながるかもしれません。




[目次:Processing関係]  [HOMEへ戻る]  [目次:Arduino関係]