INDEX(各項目ごとの目次)

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

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

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

6/26/2008

Arduino ビデオ信号/テレビ画面に出力



大抵のテレビにはビデオ入力端子がついており、その端子に映像信号を送り込めば画面に映像を映すことができます。今回は、Arduinoによってパルスを生成し、そのパルスを映像信号としてテレビに送り込む簡単な実験をします。パルスについては、モータサーボなどでも用いたように、Arduinoのデジタル出力端子からHIGH(5V)とLOW(0V)をある一定の周期で交互に出力する方法です。今回用いる映像信号は、NTSC方式というテレビの規格(日本やアメリカの規格、ヨーロッパはPAL方式)にあわせた周期になります。

テレビの画面は、水平な一本の線(走査線)が525本縦に並んで構成されています。編み物でいう横糸だけが525本あるという感じです。さらに一本の線を細かく見ると、点が左端から始まり、右端へ流れていきます。右端まで行った点は、次の列(下方の列)の左端に移動します。この動きを525回繰り返し画面右下まで移動することで、ようやく一枚の画面ができあがります。一本の線における画面左端から右端までの移動時間は63.5マイクロ秒になります。それが525回繰り返されるので、一枚の面ができあがるまでは63.5×525=33337.5マイクロ秒(0.0333375秒)つまり一秒間に約30回画面が切り替わっていることになります(30フレーム/秒)。実際はインターレース方式といって、525本のうち奇数番目の線を最初に描画し、偶数番目をその後描画する仕組みとなっています。それに対しプログレッシグ方式というのがあり、それは上から順に一本ずつ描画していきます。パソコンなどのモニターはプログレッシブ方式になっています。

Arduinoでプログラムするには、上記のような規格に合わせてパルス出力のタイミングを設定する必要があります。パルスのタイミングがずれると、信号を送っているのにもかかわらず乱れた画面となってしまい、ほとんど認識できない映像になってしまいます。今回は、水平同期信号のパルスを送ることで縦縞の模様をテレビ画面に映してみようと思います。色は黒、グレー、白の3色とします。それぞれパルスの電圧は以下のようになります(抵抗をつなげて、5V出力から以下のような電圧になるように調整します)。

水平同期信号:0.0V
黒     :0.3V
グレー   :0.6V
白     :1.0V

まず、一本の走査線の周期である63.5マイクロ秒ごとに、水平同期信号を送ります。これが目印となり、毎回一定の長さの線をつくりだします。
水平同期をとるために、以下の信号を送ります。

黒     :1.5マイクロ秒(フロントポーチ)
水平同期信号:4.7マイクロ秒
黒     :4.7マイクロ秒(バックポーチ)

合計が10.9マイクロ秒となり、この部分を「水平ブランキング期間」といいます。その後、合計が一本の線の周期である63.5マイクロ秒になるように、黒、グレー、白の信号を適宜加えていきます。
例えば、

黒     :1.5マイクロ秒(フロントポーチ)
水平同期信号:4.7マイクロ秒
黒     :4.7マイクロ秒(バックポーチ)
白     :20マイクロ秒 :画面表示
黒     :20マイクロ秒 :画面表示
グレー   :12.6マイクロ秒:画面表示

となります。最後の白、黒、グレーの三色が画面に縦縞となって現れます。それぞれの継続時間は画面上の縦縞の幅に対応しています。水平同期信号の前後にある黒(フロントポーチ、バックポーチ)は、画面右端と左端の余白部分になります。合計が63.5マイクロ秒になっているのであれば、より細かく区切って色を配置していくこともできます。



このような手順でArduinoのプログラムをしていきます。ただ、信号の精度が低いと同期がとれなくなったり映像が乱れたりするので、普段のArduinoのプログラム方法ではやや難があります。通常Arduinoのプログラムは、一旦C言語に翻訳され、アセンブラを通してマイクロコントローラ(AVR:ATMEGA168)に書き込まれます。C言語を用いた方が翻訳の手間がかからず、より精度高くプログラムできるので、今回はC言語でプログラムする要素を少し取り入れます。
電子部品については以下が各一個ずつ必要となります。

RCAビデオ入力プラグ
抵抗:1KΩ
抵抗:330Ω



テレビのビデオ入力端子(黄色:映像用)にRCAビデオ入力プラグを差し込みます。音声は使わないので赤と白の端子には、今回は何もつなぎません。RCAビデオ入力プラグの差し込み部分は、中心の棒状の部分がプラスで、周囲の円筒状の部分がマイナスです。Arduinoの8番、9番ピンからの線は、プラス部分につながれています。マイナス部分は、ArduinoのGND端子と共有します。

Arduinoのプログラム:
(高性能なテレビだと映らないかもしれません。映らない場合は、もうひとつのサンプルがさらに下にあります。)



//ディレイのライブラリを取り込む
#include <util/delay.h>
//同期信号、三色の出力を設定、定義しておく
#define SYNC (PORTB=B00000000)
#define WHITE (PORTB=B00000011)
#define BLACK (PORTB=B00000001)
#define GRAY (PORTB=B00000010)

void setup(){
//8番と9番ピンをデジタル出力に設定
//DDRB=B00000011;と書くことも可能
pinMode(8,OUTPUT);
pinMode(9,OUTPUT);
//割り込み禁止設定
noInterrupts();
}
void loop(){
//同期、前後余白の設定
BLACK;
_delay_us(2);
SYNC;
_delay_us(5);
BLACK;
_delay_us(5);

//画面表示
WHITE;
_delay_us(17);
BLACK;
_delay_us(17);
GRAY;
_delay_us(17);
}



今回は、より正確な同期タイミングを必要とするために、部分的にC言語で直接Arduino基盤上のマイクロコントローラをプログラムするコマンドを使用しています。パルスをつくるために以前はdelayMicroseconds()を用いていましたが、精度が充分ではないので、#include <util/delay.h>でディレイのライブラリを取り込んで(このライブラリはArduinoに既に含まれているので、ダウンロードする必要はありません)、「_delay_us()」という、より正確に実行するディレイを使います。#defineを使って、水平同期信号、白、黒、グレーのデジタル出力も予め定義しておきます。「PORTB」は、Arduino基盤の8〜13番ピンまでのことを指します(ポートBというひとまとまりのピン)。「B00000000」は、二進数の0(ゼロ)のことですが、「PORTB」に対してすべてのピンを0にする(LOWで出力する)ということになります。「PORTB=B00000001」は、「PORTB」の1番目のピン(Arduino基板上の8番ピン)を1にする(HIGHで出力する)ということになります。「B00000010」ならば、その隣の2番目のピン(Arduino基板上の9番ピン)をHIGHで出力するとなり、「B00000011」ならば、「PORTB」の1番目と2番目のピン(Arduino基板上の8番、9番ピン)をHIGHにするということになります。


今回の回路では抵抗を用いているので、

8番ピンLOWかつ9番ピンLOW:0.0V(水平同期信号)
8番ピンHIGHかつ9番ピンLOW:0.3V(黒)
8番ピンLOWかつ9番ピンHIGH:0.6V(グレー)
8番ピンHIGHかつ9番ピンHIGH:1.0V(白)

という出力の組合わせになります。
void setup(){...}内の、pinMode()設定を、上記のように二進数を用いて表せば、「DDRB=B00000011」となります。これは、ポートBの1番目と2番目のピン(8番、9番ピン)をデジタル出力に設定するということと同じになります。「DDRx」のxの部分にポートのアルファベットを入れれば、そのポートの設定を二進数で表すことができます。各ポートは、以下のようになっています。

ポートB(PORTB):8から13番ピン(デジタル出力ピン)
ポートC(PORTC):0から6番ピン(アナログ入力ピン)
ポートD(PORTD):0から7番ピン(デジタル出力ピン)

noInterrupts()」は、割り込みのプログラムを禁止します。割り込みプログラムによってタイミングが乱されたりせず、より正確な時間を維持することができます。
void loop(){...}内では、#defineで定義しておいたそれぞれの内容を呼び出して、同期や画面表示を行っています。基本的には、1ループの合計時間が一本の走査線の63.5マイクロ秒になるように各色の時間を配分しなければなりません。多少ずれても映るのですが、同期がずれてしまったり、各走査線ごとに長さが違うと、全く見えなくなるときがあります。
厳密には、最初の黒が1.5マイクロ秒必要ですが、小数点以下の精度がでないかもしれないので、敢えて整数にしています(その他の時間についても同様です)。水平同期、画面左右余白の信号が最初にあります。それぞれ、2マイクロ秒、5マイクロ秒、5マイクロ秒あり、合計で12マイクロ秒必要になります。今回は走査線一本の長さを63マイクロ秒に設定したので、63マイクロ秒から最初の12マイクロ秒を差し引き、残り51マイクロ秒の部分に画面表示させる内容をプログラムします。上のプログラムでは、残り51マイクロ秒の部分に、白、黒、グレーをそれぞれ17マイクロ秒ずつ配分しました。

試しにプログラムをランさせて、テレビの画面が乱れていれば、多少数値を足したり引いたりして調整して下さい。プログラム上のそれぞれのコマンドで多少の時間のずれが生じることがあります。必ずしも計算通りに63.5マイクロ秒になるとは限らないので、画面の状態を見ながら調整する必要があるかもしれません。「Fablic Square」と関連して、電子的な織物という意味で今回の実験内容をとらえてみて下さい。
このサンプルでは水平同期だけを用いましたが、この他に垂直同期の方法も用いれば、縦縞だけでなく横方向の操作も可能になります。

参考サイト:
http://www.nahitech.com/nahitafu/mame/mame6/sync.html
http://javiervalcarce.es/wiki/TV_Video_Signal_Generator_with_Arduino
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1187659197


もうひとつのサンプル(矩形の描画):

水平同期信号だけでは、表示できないテレビモニターもあったので、垂直同期信号も加えたサンプルを以下に書いておきます。垂直同期信号は、一画面分の走査線数(全走査線525本中の半分262本)ごとに必要になります。以下のサンプルでは、262本中の、前半の3〜5本目の走査線に垂直同期信号を入れています。


#include <util/delay.h>
#define SYNC (PORTB=B00000000)
#define WHITE (PORTB=B00000011)
#define BLACK (PORTB=B00000001)
#define GRAY (PORTB=B00000010)

//水平同期信号
void hsync(){
SYNC;
_delay_us(5);
BLACK;
_delay_us(7);
}

//垂直同期信号
void vsync(){
SYNC;
_delay_us(25);
BLACK;
_delay_us(5);
}

void setup(){
//8,9番ピンをデジタル出力に設定
//DDRB=B00000011; //でも可
pinMode(8,OUTPUT);
pinMode(9,OUTPUT);

//割り込み禁止
//noInterrupts(); //でも可
cli();
}

int count=1;//走査線を数える変数

void loop(){
if(count>=3 && count<=5){
vsync();//垂直同期信号2回送信
vsync();
}else if(count>=5&&count<100){
hsync(); //水平同期信号
BLACK; //矩形の上部背景描画
_delay_us(48);
}else if(count>200){
hsync(); //水平同期信号
BLACK; //矩形の下部背景描画
_delay_us(48);
}else{
hsync(); //水平同期信号
BLACK; //矩形の左側背景描画
_delay_us(20);
WHITE; //矩形の描画
_delay_us(8);
BLACK; //矩形の右側背景描画
_delay_us(20);
}
count++; //走査線の数をカウントアップ
if(count>262){//走査線が画面下端へ行ったら
count=1; //一番目の走査線に戻る
}
}



水平同期信号以外にも、垂直同期信号を加えることで、縦縞だけではなく矩形のようなかたちを表示することができます(525本の走査線の半分である262本を用いています)。走査線1本分の時間は、60マイクロ秒に設定してあります(1ループ:60マイクロ秒)。countという変数を用意して、1本ずつ表示される走査線の数を数えていきます(1ループで1本のため、プログラムの最後の部分のcount++でカウントアップしています)。
変数countを用いて、何番目の走査線がどのような色になるかをif文で条件分岐し:

矩形の上部背景部分(走査線:5〜100番目)を黒で描画
矩形の左側背景部分(走査線:100〜200番目の前半の20マイクロ秒)を黒で描画
矩形の本体部分(走査線:100〜200番目の中間の8マイクロ秒)を白で描画
矩形の右側背景部分(走査線:100〜200番目の後半の20マイクロ秒)を黒で描画
矩形の下部背景部分(走査線:200番目以降)を黒で描画

というように図形と背景をブロック分けして描画しています。つまり、矩形の高さは走査線の本数で定義し、横幅は一本の走査線の中のパルス長によって定義されています。
262番目の走査線を描画したら(262ループしたら)ようやく1画面全部が描画されるので、再び1本目に戻り次の画面を描画し直します。


6/22/2008

素材実験/鉛筆の描画線



導電性素材実験として、紙の上に鉛筆で描いた線を可変抵抗器として使う実験をします。
まず、普通のコピー紙などにHBの鉛筆でやや濃いめに太さ5mm、長さ100mm前後の太い線を描きます。テスターを用いて、その線の両端での抵抗値を求めます。今回は結果として、約100KΩの抵抗値を確認できました。上図のように、ミノムシクリップで両端をはさみ、鉛筆で描いた線と同程度の抵抗(100KΩ)をマイナス側に直列つなぎし、Arduinoの5V端子とGND端子に接続します。もうひとつのミノムシクリップ(緑)で鉛筆の線の途中部分をはさみ、Arduinoのアナログ入力へ接続します。これで、一応鉛筆で描いた可変抵抗器ができあがります。抵抗値の変化をanalogRead()で読み取り(今回は読み取り値を4で割る)、analogWrite()でLEDに出力します。緑のミノムシクリップの位置を変えることで、LEDの明るさが変化します。

Arduinoのプログラム:

void setup(){
//特になし
}
void loop(){
//アナログ入力:0番ピンを読み取り4で割る
int val=analogRead(0)/4;
//アナログ出力:3番ピン
analogWrite(3,val);
}


鉛筆自体の濃さ、線の濃さ、太さ、面積などで抵抗値は変わります。接触不良になりがちなので、多少濃いめに描くといいでしょう。その他の素材/材料などでも同様の実験は可能です。最初にテスターで抵抗値や導電性を確かめてから実験を行ってください。テスターで抵抗値を計測するには、ダイヤルなどで機能を切替する箇所を「Ω」のマークがついている部分に合わせてください。


MonotaRO デジタルカードテスター

6/16/2008

Arduino 加速度センサ

今回は秋月電子で購入した「KXM52-1050」という3軸加速度センサモジュールを使い、重力方向に対する傾斜角を読み取ります。このセンサでは、XYZ軸の3軸ありますが、XとY軸だけでも三次元的な傾斜角を計測することができます。一応、センサのXYZの3つの出力端子をArduinoのアナログ入力端子にそれぞれ接続することにしますが、実際使うのはXとYの出力値とします。データシートをみながらセンサの端子を以下のように接続します。

1:5V(Arduino5V端子と共有)
2:5V(Arduino5V端子と共有)
3:GND(ArduinoGND端子と共有)
4:無接続
5:GND(ArduinoGND端子と共有)
6:X軸(Arduinoアナログ入力0番ピン)
7:Y軸(Arduinoアナログ入力1番ピン)
8:Z軸(Arduinoアナログ入力2番ピン)



加速度センサを水平なところにおけば、X軸とY軸は重力方向に対して直角なので0Gとなります。5V電源の場合、0Gは2.5Vとして出力されるとデータシートには書いてあります。ArduinoのanalogRead()の1024段階(10ビット)であれば511になるはずですが、さまざまな条件で多少の誤差を含みます。実際に使用する前に、念のためArduinoの「Serial Monitor」で加速度センサの出力値をモニタリングしてみます(Arduinoのモニタリング方法については「Arduino 圧電スピーカ」を参照」。

Arduino (Serial Monitor)のプログラム:

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

void loop(){
//3つの値をアナログ入力で読み込む
int x=analogRead(0);
int y=analogRead(1);
int z=analogRead(2);

//Xの値を出力(十進数)
Serial.print(x,DEC);
//値と値の間に区切りを入れる
Serial.print(",");
//Yの値を出力
Serial.print(y,DEC);
//値と値の間に区切りを入れる
Serial.print(",");
//Zの値を出力し改行する
Serial.println(z,DEC);
delay(100);
}
}

3つの値を一行で出力する際に、Arduinoの出力画面上で読みやすいようにそれぞれの値の間に「","」の区切りの記号(コンマ)をいれます。この区切り記号は何でもいいのですが、これがないとそれぞれの数値同士が隣り合わせになって読みにくくなります(また、3つの数値をそれぞれ改行して出力すると、どれがX軸の値でどれがY軸の値なのか分かりにくくなるので、3つ出力してから改行しています)。
固定した角度で計測しても数値が安定しないので、100個の値をサンプリングして平均値を求めたいと思います。平均値のプログラムを付け加えます。100個分の値の合計となると、数字も大きくなるので「int」型の整数ではなく、より大きい値が扱える「long」型の整数を変数として使います。


//加算用の変数
long x_sum, ysum, z_sum;
//回数の変数
int count=0;

void setup(){
Serial.begin(9600);
}

void loop(){
int x=analogRead(0);
int y=analogRead(1);
int z=analogRead(2);

//それぞれに値を足していく(合計数)
x_sum+=x;
y_sum+=y;
z_sum+=z;

//回数を+1する(カウントアップ)
count++;

//100回カウントしたら
if(count>99){
//合計数を100で割って平均値を出す
Serial.print(x_sum/100,DEC);
Serial.print(",");
Serial.print(y_sum/100,DEC);
Serial.print(",");
Serial.println(z_sum/100,DEC);
//カウントを0に戻す
count=0;
//合計数を0に戻す
x_sum=0;
y_sum=0;
z_sum=0;
}
}

まずは、X軸について計測することにします。水平状態(0G)に対して定規などを用いて−90度傾けて−1Gの値、90度傾けて+1Gの値を上記プログラムを用いて計測することにします。
プログラム上では、xの値をx_sumに足していき、変数countで何回足したかを数えておきます(1ループで一回足されます)。countが100になったら、100回分の合計数であるx_sumを100で割り、その値を出力します(yについても同様に計測します)。
0Gの値については、水平に置いて計測してもいいのですが、今回は−1Gの時の値と+1Gの時の値の中点を用いることにします。よって、以下のような計測結果になります。

  角度:   重力:X軸平均値:Y軸平均値
-90度:  -1G:  316:  271
中点0度:   0G:  536:  491
+90度:  +1G:  756:  711 

これらの値は、今回使用した加速度センサと計測状況において求められた値なので、各自で似たような方法で計測してください。

それでは、この計測結果をもとに、Processingにセンサからの出力値をシリアル通信し、Processing上の3D立体を動かしてみたいと思います。センサを傾ければ、同様に3D立体も同じ角度で傾くようにします。シリアル通信は、1024段階の値を文字列で送ることにします(「Arduino-Processing シリアル通信5」を参照)。このプログラムでは、X軸とY軸だけを読み取ることにします。

Arduinoのプログラム:

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

void loop(){
//2つの値をアナログ入力で読み込む
int x=analogRead(0);
int y=analogRead(1);

if(Serial.available()>0){
//Xの値を出力
Serial.print(x,DEC);
//値と値の間に区切りを入れる
Serial.print(",");
//Yの値を改行して出力
Serial.println(y,DEC);
//合図用データを読み込みバッファを空にする
Serial.read();
}
}

センサから読み取ったXとY軸の値をそのままArduinoから送信します。
Processingでは、受け取った値を角度に変換する計算が必要になります。まず0Gを基準にして、水平時の値が0になるようにオフセット値(X軸の場合:536、Y軸の場合:491)を設けて差し引いておきます。そうすれば、

  角度:   重力:   X軸:   Y軸
-90度:  -1G: -220: -220
  中点:   0G:    0:    0
+90度:  +1G: +220: +220

となります。振り幅は0Gを基準にプラスマイナス220となります。
次に角度の計算ですが単位はラジアンを用います。−90度から+90度までの範囲なので、ラジアンでいうと−PI/2から+PI/2になります(PIは円周率のπです)。X軸の値が110であれば、振り幅である220(1G)の半分なので0.5Gになります。角度については90度の半分なので45度になりそうですが、実際は30度になります。−45度の場合は、以下の図のように約−156になります。



この計算方法は以下のようにして求められます。

acos()、asin()を用いる場合:
まず、Arduinoから送られて来たX軸の値をx、オフセット値をx_offset(今回のオフセット値は536)、オフセット調整した値をx0とすると、

x0=x-x_offset;

になり、角度をradX(ラジアン)とすると

sin(radX)=x0/220;

という関係になります。例えば、x0=110を代入すればsin(radX)=1/2なので、radXは30度となります。
Processingにはasin()acos()の関数があるので、それを利用すると

radX=asin(sin(radX));

という関係になり、sin(radX)にx0/220を代入し

radX=asin(x0/220);

となることで角度radXが求まります。
Y軸についてはacos()で求めると、

radY=acos(y0/220);

になります。

atan2()を用いる場合:
また、この関係をタンジェントで表せば、

tan(radX)=x0/sqrt(220*220-x0*x0)

となります。sqrt()は平方根(ルート)を求める関数です。
角度を求めるには、atan2()という関数を用いて、

radX=atan2(x0,sqrt(220*220-x0*x0));

とします。そうすると角度radXが求められます。

加速度センサのX軸プラス方向をProcessingの3D空間のX軸マイナス方向に対応させるために-radXに変換します。加速度センサのY軸方向を3D空間のZ軸方向に対応させて、

rotateX(-radX)
rotateZ(radY)

となります。
もし、加速度センサの回転方向と、3D立体の回転方向が逆になってしまうときは、値にマイナスを掛けます。また、90度ずれているときはPI/2を足します。実際にセンサを動かして、同じように3D立体が動くか確かめて下さい。

Processingのプログラム:

import processing.serial.*;
Serial port;

//読み取り値の変数
int x,y;

//X軸-1G時316、+1G時756であることから
//X軸のオフセット値
int x_offset=536;
//X軸の振り幅(-1G〜0G又は0G〜+1G)
int x_range=220;

//Y軸-1G時271、+1G時711であることから
//Y軸のオフセット値
int y_offset=496;
//Y軸の振り幅(-1G〜0G又は0G〜+1G)
int y_range=220;

//角度(ラジアン)の変数
float radX,radY;

void setup(){
//3D画面サイズ400×400
size(400,400,P3D);
//シリアルポート設定
port = new Serial(this,"/dev/tty.usbserial-A50019vD",9600);
//念のためバッファを空にする
port.clear();
//「10」(ラインフィード)が来る度にserialEvent()作動
port.bufferUntil(10);
//図形塗り面なし(ワイヤフレーム描画)
noFill();
}

void draw(){
//背景色を白
background(255);

//3D立体の座標を画面中央、-100奥に配置
translate(width/2,height/2,-100);

//オフセット調整(最小値-220、最大値220)
int x0=constrain(x-x_offset,-220,220);
int y0=constrain(y-y_offset,-220,220);

//角度の計算(ラジアン)
radX=asin(x0/x_range);//asin()で求める
radY=acos(y0/y_range);//acos()で求める
//radX=atan2( x0,sqrt(x_range*x_range-x0*x0) );//atan2()で求める場合
//radY=atan2( y0,sqrt(y_range*y_range-y0*y0) );

//センサX軸の角度は3D立体のX軸の角度に対応
//センサY軸の角度は3D立体のZ軸の角度に対応
//角度をそれぞれ代入
rotateX(-radX);
rotateZ(radY);

//直方体を描画
box(200,30,100);
}

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

//データが空でないとき
if(stringData!=null){
//改行記号を取り除く
stringData=trim(stringData);
//コンマで区切ってデータを分解、整数化
int data[]=int(split(stringData,','));

//データ数が2個のとき
if(data.length==2){
//データの値を代入
x=data[0];
y=data[1];
//合図用データ送信
port.write(65);
}
}
}

//マウスボタンを押して通信開始
void mousePressed(){
//合図用データ送信
port.write(65);
}

void draw(){...}内の「オフセット調整」箇所の

int x0=constrain(x-x_offset,-220,220)

は、constrain()を用いて、読み取った値xからオフセット値であるx_offsetを差引き、最小値−220から最大値220までの値になるように制限しています。

ノイズのせいか、動きがぎこちない場合はフィルターのプログラムを挿入し滑らかにします。そのためには、radX、radYと同様にプログラムの冒頭でフィルター用の変数:
float filterX,filterY;

を用意しておき、void draw(){...}内の最後の角度を求める箇所を以下のように変更してください。
radX=asin(x0/x_range);//変更なし
radY=acos(y0/y_range);//変更なし

//フィルターの式
filterX+=(radX-filterX)*0.3;//新たに挿入
filterY+=(radY-filterY)*0.3;//新たに挿入

rotateX(-filterX);//変更
rotateZ(filterY); //変更

フィルターの式の「0.3」は係数であり、1.0に近づくほどフィルターの効果はなくなります。逆に0.1のように係数の値を小さくすれば、滑らかになりつつ反応が鈍く動くようになります。適度に調整してみてください。


尚、もっと簡単に加速度センサを扱いたい場合は(あまり正確な角度にこだわらないのであれば)、
//オフセット調整(最小値-220、最大値220)
int x0=constrain(x-x_offset,-220,220);
int y0=constrain(y-y_offset,-220,220);
//角度の計算(ラジアン)
radX=asin(x0/x_range);
radY=acos(y0/y_range);

の部分を、
radX=2.0*x*PI/1023;
radY=2.0*y*PI/1023;

に置き換えてもセンサを傾けた方向に3D立体が傾きます。この計算では、読み取った直接の値に比例して角度も変わります(比率が多少ずれてしまいます)。この場合は、モニタリングで調べた最小値/最大値/オフセット値などの設定もする必要はありません。式の中の「2.0」というのは係数であり、大きくすれば傾きも大きくなるので画面で確認しながら調整してください。

−90度や+90度付近では、出力値の変化が微妙になるので、きちんとした角度が出ない場合があります。出力値補正のためにZ軸の出力も利用すれば、計算は少し複雑になるかもしれませんが、±90付近まで計測可能になります。

6/12/2008

Arduino-Processing シリアル通信5


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


以前行ったシリアル通信では、ArduinoのanalogRead()で読み取った値(0〜1023までの値)を0〜255(8ビット)にスケールダウン(4で割る)して送信するか、256以上の大きな値を、二つの0〜255(8ビット)の数値に分解して送っていました。今回は、たとえば「1023」という255より大きい値を、そのままの「1023」という値で送信したいと思います。そのためには、読み取った整数値(int型)を文字列として送信します。
今回は3つの可変抵抗器を読み取って(接続方法は「Arduino-Processing シリアル通信2」を参照)、Arduinoから3つの値をまとめて送信します。複数の値を送る際には数値と数値の間にデリミターという区切りの記号(今回の場合「,」コンマ)を挿入して送信します。そうすることによって、Processingでデータを受け取る際に、データ内容を混同せず読み取ることができます。最初の二つの読み取り値は、Serial.print()を使ってDEC(十進数文字列)のフォーマットで送信し、区切り記号のコンマもSerial.print()で文字列として送信します。最後の読み取り値を送る時に、DECフォーマットでSerial.println()を用い「改行」して送信します。改行することで、Processing側でデータを受け取る際に、送られて来たデータのどの部分が最後であるのかを確認することが可能になります。それでは、Arduinoのプログラムから始めます。

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

void loop(){
  //3つのセンサの値を読み取り、変数に代入
  int x=analogRead(0);
  int y=analogRead(1);
  int z=analogRead(2);

  //合図用データが一個きたら
  if(Serial.available()>0){
    //xの値を十進数文字列で送信
    Serial.print(x,DEC);
    //区切り記号コンマを送信
    Serial.print(",");
    //yの値を十進数文字列で送信
    Serial.print(y,DEC);
    //区切り記号コンマを送信
    Serial.print(",");
    //zの値を十進数文字列かつ改行して送信
    Serial.println(z,DEC);
    //合図用データを読み込みバッファを空にする
    Serial.read();
  }
}


「xの値 コンマ yの値 コンマ zの値 改行」というデータが一度に送られることになります。Serial.print(value,DEC)の「DECフォーマット」の数値は文字列であり、以前使ったSerial.print(value,BYTE)の「BYTEフォーマット」の数値と異なる値になります。文字列の「1」は、BYTEフォーマットでは、「49」に相当します。十進数文字列とバイトの数値の対応は以下のようになります(ちなみに、BYTEフォーマットの「65」は文字列の「A」になります)。

DEC: BYTE:
 0  48
 1  49
 2  50
 3  51
 4  52
 5  53
 6  54
 7  55
 8  56
 9  57

アスキーコード表」にこれらの対応関係が掲載されています。
たとえば、「120」という値の場合、BYTEフォーマットならそのまま「120」となりますが、DECフォーマットでは「49 50 48」というように「1」「2」「0」という3つの文字を送ることになります。DECフォーマットでは、1桁の数値なら1バイト分のデータであり、2桁なら文字二つを送るので2バイト、3桁なら3バイト必要になります。BYTEフォーマットは、255までの数値であれば1バイトで済みますが、それ以上の数値は「Arduino-Processing シリアル通信3」で行ったように、分解して送るなどの工夫が必要となります。
DECフォーマットで、そのままの値を文字列として送信した方が分かりやすいのですが、その分バイト数が増えてしまうことにもつながります。BYTEフォーマットであれば少ないバイト数で送ることができますが、大きな数値を分解して計算し直さなければいけないので、十進数の数値として扱いづらくなります。状況に応じて使い分けるのがいいと思います。

次に、Processing側のプログラムに入る前に、どのようなかたちでデータを受け取るかということについて説明します。

例えば、3つの可変抵抗器から読み取られる値が、

x=120
y=284
z=1015

の場合、Arduinoからは、

「120 コンマ 284 コンマ 1015 改行」

というデータが送られてきます。
「コンマ」は「アスキーコード表」では「44」であり、「改行」記号は「アスキーコード表」の「13」と「10」がデータの最後に付け加えられることになります。
「13」は「キャリッジリターン(行頭に戻る)」ということであり、文字列では「\r」になります。
「10」は 「ラインフィード(次の行へ移る)」ということであり、文字列では「\n」になります。
Windowsでは、キャリッジリターンとラインフィードで改行となり、Macintoshでは、キャリッジリターンのみで改行されるので、この二つがあることで、いずれにせよ改行されることになります。

先ほどの、

「120 コンマ 284 コンマ 1015 改行」

というデータは、

"120" + "," + "284" + "," + "1015" + "\r" + "\n" 

という文字列データになります。
コンマや改行記号を手掛かりにすれば、データの順番や終わりの部分をProcessing側で判別して読み込むことができます。それでは、このようなことを踏まえてProcessingのプログラムをしてみたいと思います。PFontを用いて、数値を文字で画面に表示することにします。マウスを押したら通信開始することにします(プログラムが開始して数秒たってからマウスを押さないと反応しないときがあります)。

Processingのプログラム:
//シリアルライブラリを取り込む
import processing.serial.*;
//シリアル通信用変数portを用意
Serial port;

//フォント用変数fontを用意
PFont font;

//読み込み値の変数を用意
int x,y,z;

void setup(){
  //画面サイズ設定
  size(400,200);

  //フォントをロードする
  font = loadFont("Monaco-10.vlw");
  //フォント使用開始:サイズ10
  textFont(font, 10);
  //文字を右寄りに配置する
  textAlign(RIGHT);

  //シリアルポート設定
  port = new Serial(this,"/dev/tty.usbserial-A50019vD",9600);
  //念のためバッファを空にする
  port.clear();
  //「10」(ラインフィード)が来る度に
  //serialEvent()を発動させる
  port.bufferUntil(10);  
}

void draw(){
  //背景を白で塗りつぶす
  background(255);
  //3つの値を文字で表示する
  text(x,100,50);
  text(y,200,50);
  text(z,300,50);
}

//シリアル通信
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[]内のデータが3つなら、
    if(data.length==3){
      //最初のデータをxに代入
      x=data[0];
      //次のデータをyに代入
      y=data[1];
      //その次のデータをzに代入
      z=data[2];

      //合図用データ送信
      port.write(65);
    }
  }  
}

//マウスが押されたら通信開始
void mousePressed(){
  //開始用データ送信
  port.write(65);
}

Processing上のシリアル通信では、まず初期設定setup(){...}内で、bufferUntil()を使って、指定した文字がArduinoから送られて来るたびにserialEvent()が作動するように設定しておきます。Arduinoから最後に送られてくるSerial.println()によって、データの末尾が改行記号の「\n」であることから、今回はbufferUntil()の括弧内には「10」を入れておきます。整数値「10」は文字列の改行記号の「\n」(ラインフィード)に相当します。
そして、serialEvent(){...}内では、readStringUntil()を用いて、同様に「10」つまり「\n」が来るまでデータを読み込む設定にします。読み込まれたデータは、3つの値以外にも「コンマ」や「改行」記号が含まれた連続した文字列なので、その文字列の内容を整理し直す必要があります。
「stringData!=null」は、読み込まれたデータが空(null)ではないとき、つまり何かしらのデータがあるときという条件です。データがあれば、その文字列データに含まれている余分な空白記号や改行記号をtrim()によって取り除きます。
その後、それぞれの値の区切り記号(デリミター)として用いた「,」コンマをもとに、連続したひとつのデータをsplit()で分解します。split()によって分解されたデータは、複数のデータを内包する配列に変換されます。さらに、分解されたデータは、まだ文字列なので、int()を用いて整数値へ変換します。そのためにdata[]という配列を用意し、「int data[]=int(split(stringData,','))」の中で、この一連の変換作業を行っています。配列については、「Arduino 7セグLEDの点灯」の後半でも触れていますので、参照してください。
if(data.length==3){...}は、配列data[]内のデータ数が3つあるときにという条件です。length配列の大きさ(データを何個含んでいるか)を数えます。データ数が3つあることを確認してから、配列data[]に含まれる一つ目の値「data[0]」をxに代入します(配列では、最初のデータは0番目となります)。同様にyとzについても代入します。最後に合図用データを一つ送信します(65以外の数値でも大丈夫です)。合図用データをArduinoへ送信すれば、Arduinoは再び新たなデータを送り返してきます。

連続した文字列データを個別の数値に変換する手続きを以下にもう一度書きます。
Arduinoで読み取った3つの値を、

x=120
y=284
z=1015

とすれば、
Arduinoからは、

"120" + "," + "284" + "," + "1015" + "\r" + "\n" 

という順番で文字列として送信されます。
Processingでは、port.readStringUntil(10)で括弧内の「10」つまり「\n」までを、

"120,284,1015\r\n" 

という連続したデータとして読み込みます(「\r\n」は改行記号)。合計14個の文字があるので14バイトになります。「\r」と「\n」はそれぞれ1バイトずつとなります。 
trim()で「改行」記号を削除すると、

"120,284,1015"

になります。
split()で「,」をもとに分解すると、

{"120","284","1015"}

という、3つの文字列を含んだ配列のデータに変換されます。
さらに、これら3つの文字列をint()で整数の数値に変換すると、

{120,284,1015}

になり、予め用意しておいた整数型の配列data[]に入れます。

int data[]={120,284,1015}

そして、「data.length」によって配列data[]のデータ数が3個であるかを確認し、これらの値(整数値)を順にx、y、zへ入れます。

x=data[0]
y=data[1]
z=data[2]

この手順を踏んで、連続した文字データを個別の数値として扱うことができます。


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

6/08/2008

Arduino サーボ制御



サーボモータは、信号を送ると指定した角度まで回転するので、ロボットの間接部分にもよく用いられています。通常のアナログサーボであれば、回転角の範囲は0〜180度程度です。種類によっては、回転範囲が180度以下のものや、360度回転(連続回転サーボ、あるいは0〜360度の範囲で回転するサーボ180度のサーボを改造する例、もうひとつの改造例)するものもありますが、今回は180度の回転が可能な一般的なサーボを制御します。サーボもDCモータ同様、Arduino基盤に対しては過電流となる恐れがあるので、別電源を用意したほうが無難ですが、一つくらいであれば直接つないでもそれほど問題でないでしょう。
サーボには大抵5V線(赤)、GND線(黒)、信号線(白)の3つの線があります。そのままArduino基盤につなぐ場合、5V線、GND線をそれぞれ基盤の5V端子、GND端子へ接続し、信号線をPWM端子へつないでanalogWrite()で制御することができます。



analogWrite()で動かす方法:(動くけど多少不安定)
今回はPWM端子である3番ピンにサーボの信号線を接続し、可変抵抗器で操作します。可変抵抗器からのanalogRead()による読み取り値0〜1023を4で割って、0〜255の範囲にスケールダウンします。analogWrite()は0〜255の値を出力しますが、サーボの動作角度においてはおよそ0〜180度に対応します。例えば、出力値を127としてanalogWrite(3,127)であれば、約90度のところで停止します。パルス信号の性質上、1ループを約20ミリ秒にすると動きが安定します。そのために、delay(20)を最後に加えておきます。

Arduinoのプログラム:

void setup(){
//特になし
}

void loop(){
//可変抵抗器の値を読み込み4で割る
int val=analogRead(0)/4;
//アナログ出力
analogWrite(3,val); //0~255
//0.05秒ループにする
delay(20);
}


analogWrite()が0〜255なので、180度の回転範囲を256段階の分解能で出力できるということになります。ただし、この方法だとガタガタと不安定な動きになるかもしれませんので、以下の方法をおすすめします。


ライブラリを利用する方法(Arduino0018):
Arduino0016まではPWMの9番ピンと10番ピンだけにサーボを接続可能でしたが、Arduino0017以降では、ソフトに含まれているライブラリを使うことで最大12個まで接続可能です(Megaの場合最大48個)。
ただし、このライブラリを使用すると9~10番ピンのPWM機能は使えなくなります(Megaの場合は12~23ピンをサーボに使用すると11~12ピンのPWMが機能しなくなります)。つまり、サーボとanalogWrite()を同時に使うプログラムの場合は、analogWrite()に使用するピンとして9~10のピンを避ける必要があります。
「メニューバー>Sketch>Import Library...>Servo」を選択すると「#include <Servo.h>」が自動的に挿入されライブラリを使用可能にします。以下はArduino0018に内包されているServoライブラリの使用例です。attach()でサーボを接続するピンを指定し、write()で0~180の角度の値(整数値)をいれます。プログラム内でmap()を使っていますが、センサから読み込まれる値val(変数)の範囲0~1023をサーボの角度出力値0~180度に変換する方法です。


#include <Servo.h>

Servo servo;//サーボのインスタンス

void setup(){
//サーボの信号線を3番ピンに接続
//(PWMピン以外のピンにも接続可)
servo.attach(3);
}

void loop(){
//センサの読み取り値
int val=analogRead(0);
//map()を使って0~1023のセンサ読取り値を0~180の角度に変換
int deg=map(val,0,1023,0,180);
//サーボ出力
servo.write(deg);//0~180まで
}



精度をあげて制御する方法:writeMicroseconds()を使う
write()を使う方法は0~180度を180段階の分解能でしか角度設定できませんが、新たに加わった機能writeMicroseconds()によって、より細かく角度を設定することが可能です。この場合サーボにパルス(パルス幅)の値を送って角度を決定します。
例えば:

writeMicroseconds(500)で0度
writeMicroseconds(1500)で90度(中間位置)
writeMicroseconds(2500)で180度

という感じになります。つまり0~180度の範囲を500~2500の範囲に変換すればいいことになります。
しかし、サーボによって受け入れるパルス幅が異なるので仕様書などで確認してください。
各サーボの最小値や最大値を超えたパルスを送ってもそれ以上回転しないか、負荷を与えることにもなるので注意して下さい。
ちなみに、
servo.attach(ピン番号,最小パルス幅,最大パルス幅);
というように()内に3つの値をいれることができます。最小パルス幅はデフォルトでは544、最大パルス幅は2400に設定されているようです。これらの値で動く範囲を調節できます。


#include <Servo.h>

Servo servo;//サーボのインスタンス

void setup(){
//サーボの信号線を3番ピンに接続
//(PWMピン以外のピンにも接続可)
servo.attach(3);
}

void loop(){
//センサからの読取り値
int val=analogRead(0);//0~1023
//map()を使って0~1023を544~2400に変換
int pulseWidth=map(val,0,1023,544,2400);
//サーボ出力
servo.writeMicroseconds(pulseWidth);//変換したパルス幅値を代入:544~2400
}


上記プログラムでは、writeMicroseconds()を使うことで、0~180度の範囲を544~2400の範囲の分解能で動かすことができます(180度の範囲を2400-544=1856分解能、約0.1度単位で角度を調節可能)。上の場合はanalogRead()を使っているので、その精度に左右されます(つまり1024分解能)。


以下は2008までの内容です。参考までに。

その他のライブラリの使用例:
ArduinoのPlaygroundというサイトには、サーボのライブラリがあります。このライブラリをダウンロードして、Arduinoのフォルダ内にあるlibraries(Arduino-0011>hardware>libraries)に入れれば、Arduinoの画面のメニューバー>sketch>Import Library>Servoを選んで利用することができます(#include <Servo.h>という一文が自動的に書き込まれます)。このライブラリを利用すれば、PWM端子以外のピンにもサーボを接続することができます。servo.write()の()内に0〜180の整数値を入れることで角度を制御するので、分解能は180(1度ずつ)となります。analogRead()の0〜1023の値に0.176を掛けて出力値を0〜180にスケールダウンします。小数点の計算なのでfloat型の変数valにしてから、servo.write()の()内にint()を用いて整数化しています。最後のServo::refresh()を、周期が20ミリ秒以下にならないように、少なくとも50ミリ秒に一回は呼び出す必要があります。

#include <Servo.h>

Servo servo;

void setup(){
//サーボの信号線を3番ピンに接続
//(PWMピン以外のピンにも接続可)
servo.attach(3);
}

void loop(){
//読み取り値をスケールダウン
float val=analogRead(0)*0.176;
//サーボ出力
servo.write(int(val));
//周期を更新
Servo::refresh();
}




パルスをつくって制御する方法:
analogRead()の読み取り値である0〜1023の1024段階で制御する方法があります。この場合は、プログラム上でdigitalWrite()のHIGHとLOWを交互に出力するパルスを生成して制御します。HIGHの継続時間とLOWの継続時間の合計が、パルスの周期である約20ミリ秒(20000マイクロ秒)になります。delayMicroseconds()で、HIGHとLOWの継続時間を変化させるプログラムで、サーボの動作角度を制御します。一般的なサーボにおいては、HIGHの継続時間は500〜2500マイクロ秒程度になります。500マイクロ秒で0度、2500マイクロ秒で180度という計算になります。約2000マイクロ秒の振り幅があるので、analogRead()で読み取った値を2倍するとほぼ0〜180度を1024段階の分解能で表現できます。digitalWrite()を使うのでPWM以外のピンにもサーボを接続することができます。

void setup(){
//パルス出力ピンの設定
//(デジタル出力なのでどのピンでも可)
pinMode(3,OUTPUT);
}
void loop(){
//可変抵抗器の読み込み値を2倍にする(振幅値:約2000)
int val=analogRead(0)*2;
//パルス:HIGHを出力
digitalWrite(3,HIGH);
//パルス最小値を500としvalを代入
delayMicroseconds(val+500);
//パルス:LOWを出力
digitalWrite(3,LOW);
//HIGHの継続時間を差引いて周期を20000usに調整
delayMicroseconds(10000-(val+500));
delayMicroseconds(10000);
}

パルスの周期は20000マイクロ秒なので、LOWの継続時間は20000マイクロ秒からHIGHの継続時間を差し引いた時間となります。尚、delayMicroseconds()の()内に入れられる最大値は16383なので(精度を保つことができる最大値)、20000-(val+500)とは書かずに、10000-(val+500)と10000に分けて書いてあります。


サーボによっては、パルスの最小値や最大値あるいは振幅値が多少異なるので、正確に制御したい場合はデータシートを参照するか、サーボごとにテストしてみる必要があります。


絵とき「サーボ制御」基礎のきそ (Mechatronics Series)
塩田 泰仁
日刊工業新聞社
売り上げランキング: 248872

Arduino 小型DCモータ/TA7291P

小型のDCモータ「FA-130」を制御する方法についてです。このモータは、車のプラモデルなどに使われるDCモータです。最大で500mAの電流が流れます。大抵DCモータを扱う場合、Arduino基盤にとっては過電流となるので別電源(乾電池など)が必要となります。
DCモータには二本の線がついており、一方をプラスに、もう一方をマイナスにつなげば回転し、プラスとマイナスを入れ替えれば逆回転します。スピードは、電圧が低いと遅く、高いと速く回転しますが、今回の場合はPWM(パルス)で調節します。パルスは、一定の電圧でONとOFFを高速に繰り返して出力する方法で、ONの継続時間が長いほど速く回転し、OFFの時間が長いほど遅く回転します。このONとOFFの時間の比率を「デューティ比」と言います(実際には、回転速度というよりトルクに反映されます)。



上図と下図を比較すると、「ONの継続時間」については、下図より上図のほうが長いので、上図の方がモータは速く回転します(トルクが高くなります)。



PWMでは、このONとOFFの時間の比率を変えることでスピード調節を行っています。以前行ったLEDの照度調整も同様の仕組みです。LEDの場合、実際は点滅しているのに過ぎないのですが、繰り返されるON/OFFがあまりにも速いので、明るさが変わったように見えています。

また下図のように、モータを正転/逆転させる場合は、電源へつないでいる二本の線の途中にスイッチを設けて入れ替え可能にします。切り離せば静止状態になります。このような切替を可能にする回路を「Hブリッジ回路」といいます。



そこで、いままで説明したことを容易にしてくれるのが、今回用いるモータドライバIC「TA7291P」です。「TA7291P」には、10本の端子があります。
東芝 TA7291P

東芝 TA7291P

価格:189円(税込、送料別)





 1:GND(Arduino/GND端子)と共有
 2:モータの端子へ接続
 3:非接続
 4:PWM端子(Arduino/アナログ出力端子)へ接続
 5:信号用端子(Arduino/デジタル出力端子)へ接続
 6:信号用端子(Arduino/デジタル出力端子)へ接続
 7:5V電源(Arduino/5V端子)と共有
 8:外部電源のプラス端子へ接続(乾電池など)
 9:非接続
10:モータの端子へ接続

今回は、可変抵抗器で正転/逆転/静止/スピード調節しようと思います。以下のようにそれぞれを接続することとします。複雑な配線に見えるかもしれませんが、間違わずにそれぞれを接続して下さい。


TA7291Pの5番ピンと6番ピンは、静止/正転/逆転を決めるための信号用の端子であり(それぞれArduinoのデジタル出力の1番ピン、2番ピンに接続)、
5番ピンが「LOW」、6番ピンが「LOW」の場合は静止
5番ピンが「HIGH」、6番ピンが「LOW」の場合は正転
5番ピンが「LOW」、6番ピンが「HIGH」の場合は逆転
となります。ArduinoのdigitalWrite()から出力されるLOW/HIGHの組合わせによって決められます。つまり、Arduinoのデジタル出力1番ピンと2番ピンのHIGH/LOWの出力の組合わせをプログラムによって操作することになります。
スピード調節については、TA7291Pの4番ピンがArduinoのアナログ出力の3番ピン(PWMピン)と接続され、ArduinoからPWM出力によって制御されます。

今回は、可変抵抗器を右に回すと正転、左に回すと逆転、中間だと静止するプログラムにします。可変抵抗器からanalogRead()によって読み取られる値は0〜1023なので、それを2で割って0〜511にスケールダウンし、
  0〜254:逆転(0で高速、254で低速)
255〜256:静止
257〜511:正転(257で低速、511で高速)
となるようにします。逆転時では数値が0に近いほど速く、254に近いほど遅くなり、正転時では数値が大きいほど速くなるので、調整した値をanalogWrite()の出力値に入れます。よって、以下のようなプログラムになります。

Arduinoのプログラム:

void setup(){
pinMode(1,OUTPUT); //信号用ピン
pinMode(2,OUTPUT); //信号用ピン
}

void loop(){
//アナログ入力:0番ピンの値を2で割る
int val=analogRead(0)/2; //0~511の値にする

//静止/正転/逆転の状態に分けてプログラムする
if(val>=255 && val<=256){ //静止:255~256
//LOW,LOWでデジタル出力
digitalWrite(1,LOW);
digitalWrite(2,LOW);
}else if(val>256){ //正転:257~511
//HIGH,LOWでデジタル出力
digitalWrite(1,HIGH);
digitalWrite(2,LOW);
//valが大きいほど出力値も大きくなる
analogWrite(3,val-256); //出力値:1~255
}else{ //逆転:0~254
//LOW,HIGHでデジタル出力
digitalWrite(1,LOW);
digitalWrite(2,HIGH);
//valが小さいほど出力値は大きくなる
analogWrite(3,255-val); //出力値:1~255
}
}

外部電源には、1.5Vの乾電池が4本入る電池ボックスなどを使うといいでしょう。
TA7291Pのデータシートには、外部電源のプラス端子をつなぐための8番ピンの電圧は、PWMに使うための4番ピンの電圧以上なければいけないと書いてあります。これに従えば、今回の場合の外部電源は、5V以上必要となります。

関連:「Arduino モータドライバ+モータ」(その他のモータドライバ等について)


MonotaRO (モノタロウ)

6/01/2008

センサについて

センサには、複雑なものもありますが、ArduinoのdigitalRead()analogRead()ですぐに使うことができるものも多くあります。いままで習った範囲でも制御できるセンサを挙げておきます。「四谷工作研究所(2007)」にも各種センサについて使い方を記載しているので参考にして下さい。
ツマミ回転式可変抵抗器:ツマミの回転量で出力値を変化
スライド式可変抵抗器:スライダの移動量で出力値を変化
ジョイスティック式可変抵抗器:スティックの動きで出力値を変化
圧電スピーカ:マイクとして使うことで衝撃音を計測
CDSセル(光センサ):光量を計測し出力値を変化
曲げセンサ:センサ自体を曲げることで出力値を変化
圧力センサ:押された圧力を計測
距離センサ:物体との距離を計測
加速度センサ:物体の重力方向に対する傾き角度/加速度を計測
人体感知センサ:人間の動きを感知しHIGH/LOWを出力


その他、スイッチ類を利用してセンサのように使うこともできます。


センサやスイッチ類をそのまま単体で使うだけでは、すぐには面白い表現にたどりつかないかもしれないので、複数のセンサやスイッチを組み合わせることを考えてみてください。例えば、単純なタクトスイッチを「Wooden Stick」に多数配置すれば、棒を握った箇所を判別するセンサになるように、応用的な使い方や配置の仕方を試してみるといいでしょう。


センサとArduinoの接続について:
可変抵抗器のように段階的に出力が可能なセンサは、大抵5V線、GND線、出力線の3つの端子があり、以下のようにつなぎます。

距離センサGP2Y0A21YK」の場合、電源(5V)とグランド(0V)にそれぞれつなぎ、出力線となる端子をArduinoのアナログ入力(ANALOG IN)のピンにつなぎ、出力線からの値をanalogRead()を使って読み込みます(プログラムについては、「Arduino シリアル通信1」または「Arduino アナログ入出力」を参照)。距離センサGP2Y0A21YKからanalogRead()で読み取った値を距離に置き換えるには、距離=220000/(読取値*5-200)にするといいそうです。
以下は距離センサGP2Y0A21YKの読取り電圧と距離(cm)の関係のグラフです。


このように3線(3端子)あるセンサの場合はいいのですが、2線(2端子)しかない場合は、必要な抵抗をつなぎ以下のようになります。

CDSセル(光センサ)」や「曲げセンサ」などは、2線(2端子)のまま売られている場合が多いので、適宜抵抗を取付けて、分岐する3つ目の線をのばし(出力線)、アナログ入力のピンへつないで下さい。つまり、2線(2端子)を3線(3端子)にするということになります。この際必要となる抵抗は、「プルアップ抵抗」と呼ばれます(プラス側に取付けた抵抗)。例えば、「曲げセンサ」の場合、もともと10KΩ〜30KΩ程度の抵抗があるので、10KΩの抵抗をつなぐとちょうどいいでしょう。「CDSセル(光センサ)」の場合も、同様のつなぎ方をします。取付ける抵抗(上図の場合10KΩ)の目安は、センサ自体の抵抗値と同程度のものとなります。取付ける抵抗を変えれば、出力される電圧や出力幅も変わるので、幾つかの抵抗を試してみて、読み取る際に丁度いいものを選んでください(プログラムについては、「Arduino シリアル通信1」または「Arduino アナログ入出力」を参照)。

また、スイッチなども2端子しかついていないので、抵抗をつなぎ、分岐させた3つ目の線(出力線)を取付けて、デジタル入力ピンへつなぎます。

タクトスイッチ」(あるいは2端子しかないスイッチ類)の場合、抵抗はマイナス(GND)側にとりつけてあります。これを「プルダウン抵抗」といいます。スイッチを押すと5V、放すと0Vの状態になります。5V/0Vのスイッチなので、デジタル入力によって読み取ります。
以下は、タクトスイッチを押すと13番ピンのLEDが光るプログラムです。

void setup(){
//12番ピンをデジタル入力に設定
pinMode(12,INPUT);
//13番ピンをデジタル出力に設定
pinMode(13,OUTPUT);
}

void loop(){
 //デジタル入力(12番ピン)がHIGH(5V)なら
if(digitalRead(12)==HIGH){
//13番ピンをHIGH(5V)で出力
digitalWrite(13,HIGH);
}else{ //デジタル入力がLOW(0V)なら
//13番ピンをLOW(0V)で出力
digitalWrite(13,LOW);
}
}

プルダウン抵抗/プルアップ抵抗:
もし、タクトスイッチから抵抗を通してGND端子へ線を接続していなければ、スイッチを押したときだけ5Vが出力されてデジタル入力の12番ピンがHIGHとして読み取りますが、スイッチを押していない時は12番ピンがどこにも接続されていない状態になってしまい、12番ピンにおいて不安定な値が読み取られます。読み取り値が不安定な値にならないためにも、抵抗を通してGNDピンに接続しておく必要があります。そうすれば、スイッチを押していない時は、GNDピンから抵抗を通して0Vが出力され、12番ピンはLOWとして読み取ることができます。つまり、スイッチを押したときの状態(5V出力)だけでなく、押していない時の状態(0V)もきちんと定義しなければならないということになります。
また、抵抗をプラス(5V)側に取付けて線を分岐させれば、「プルアップ抵抗」となります。プルアップにした場合は、押すと0V、放すと5Vになります。スイッチを押さない時の状態、押した時の状態の設定に応じて使い分けます。


Processing-Arduino シリアル通信4


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


前回までは、Arduino基盤に接続したセンサなどからProcessingの画像を動かしていましたが、今回はその逆で、Processingで制作した画像からArduinoを制御したいと思います。前回同様合図用のデータを送り、確認し合いながら通信します。Processingでマウスで動かすことができるスライダをつくり、256段階の値をArduino側に送り、PWM出力を用いてLEDの明るさを調節できる内容とします。Processing側から操作するので、まずProcessing側のプログラムから書きます。

Processingのプログラム:
//シリアルライブラリの取り込み
import processing.serial.*;
Serial port;

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

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

void draw(){
  background(100);
  line(x,0,x,height);
}

void serialEvent(Serial p){
  //データ数が0個より多いとき
  if(port.available()>0){
    //X座標を送信
    port.write(x);
    //Arduinoからの合図用データを
    //読み込んでバッファを空にする
    port.read();
  }
}

void mouseDragged(){
  //ドラッグ中のX座標にマウスX座標を
  //最小値0,最大値255で入れる
  x=constrain(mouseX,0,255);
}

void keyPressed(){
  //sキーを押したら
  if(key=='s'){
    //通信開始用データ送信
    port.write(0);
  }
}


今回は、キーボードの「s」キーを押すことでシリアル通信を開始することにしました。「s」キーを押さなければ、Processing、Arduinoの両方のプログラムは、どちらも待機中となります。そのため、Processing側から「s」キーを押して、通信開始のきっかけとなるデータを1個(1バイト分)送ります。
Arduino側では、11番ピン(PWMピン)にLEDのプラス側を、GNDにマイナス側をつなぎます。必要に応じてその間に抵抗(220Ω)を直列つなぎします。

Arduinoのプログラム:
//読み取り値の変数
int val=0;

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

void loop(){
  //データが0個より多いときの時
  if(Serial.available()>0){
    //データの読み込み
    val=Serial.read();
    //合図用データ送信(1バイト)
    Serial.write(65);
  }
  //アナログ出力(11番ピン)に読み込み値を入れる
  analogWrite(11,val);
}

手順としては、
(1)両方のプログラムが開始される。
(2)合図用データの待機中。
(3)Processingから「s」キーで通信開始用データを送信。
(4)Arduinoのバッファ内データを数える。
(5)Arduinoバッファ内データが1個以上ならデータ読み込み。
(6)その結果、Arduinoバッファ内データが空になる。
(7)Arduinoから合図用データ送信。
(8)Proccessingバッファ内データを数える。
(9)Processingバッファ内データが1個以上ならX座標データ送信。
(10)合図用データを読み込み、バッファを空にする。
 *以後は、(4)へ戻り通信を繰り返す。

追記:
上記プログラムで通信が途切れてしまう場合、ArduinoよりProcessingの処理速度が速すぎるのかもしれません。Processingのvoid setup(){...}内でframeRate(30)程度にするか、draw(){...}内にdelay(20)程度を挿入し少しスピードダウンすると安定するかもしれません。
あるいは、ProcessingからArduinoへ一方向的にデータを送る内容なので非同期通信にしてもいいかもしれません。その場合もProcessingのvoid setup(){...}内でframeRate(30)程度にスピードダウンし、void draw(){...}内にport.write(x)を入れて(serialEvent()は使わず)30fpsで定期的に送信し、Arduino側でそのデータを受け取るようにします(合図用データは送る必要はありません)。


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

5/28/2008

Arduino-Processing シリアル通信3


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


前回までのサンプルでは、ArduinoのSerial.write()を用いて、0〜255までの256段階の値を扱っていました。analogRead()からは、0〜1023までの1024段階の値を読み取ることができるのに、わざわざ4で割って256段階にスケーリングして解像度を落としていました。
今回はanalogRead()から読み取った1024段階の値を解像度を落とさずProcessingへ送信したいと思います。1024段階の値を二つの値に分解して送信する方法を用いることにします。大きな画面上で図形などを滑らかに動かすときや、センサなどから読み取った値を精度高く計算したいときは、解像度を低くできない場合があります。そのような時に、今回の方法を用いるといいでしょう。
今回もまた3個の可変抵抗器を基盤につなぎ(配線については前回ブログ参照)、それぞれをanalogRead()で1024段階で読み取り、Processing上でも1024段階で表現したいと思います。

Arduinoのプログラム:
//3つの変数を用意
int x, y, z;

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

void loop(){
  //x,y,zの変数へ読み込んだ値を入れる
  x = analogRead(0);
  y = analogRead(1);
  z = analogRead(2);

  //Processingからの合図用データが
  //バッファ領域内に一つ以上になったら
  if(Serial.available() > 0){
    //それぞれの値を2つの値に
    //分解するための変数を用意し
    //計算結果を入れる
    int x_high = x / 256;
    int x_low = x % 256;
    int y_high = y / 256;
    int y_low = y % 256;
    int z_high = z / 256;
    int z_low= z % 256;
    //分解した値を送る
    Serial.write(x_high);
    Serial.write(x_low);
    Serial.write(y_high);
    Serial.write(y_low);
    Serial.write(z_high);
    Serial.write(z_low);
    
    //合図用データを読み込んで、
    //バッファ領域を空にする
    Serial.read(); 
  }
}

以上のプログラムで、x,y,zは整数型の変数です。整数型の計算では、例えば10/3=3、3/10=0であり、小数点以下の部分は無視されます(四捨五入もされません)。ちなみに、小数点まで求める場合は、float型の変数を用います。
それから「%」(Modulo)という割り算の余りを求める計算式があります。10%3=1、3/10=3、10%4=2、10%5=0となります。
今回はこの二つの計算方法を使って、0〜1023の1024段階の値をx_highとx_lowという二つの値に分解して、二つのデータとしてArduinoから送ります。送られたデータはProcessing上で再び合成されて、一つの値として扱われるようにします。
例えばArduinoのanalogRead()から950という読み取り値があったら、
x_high = 950 / 256;
x_low = 950 % 256;

として、
x_high = 3;
x_low = 182;

となります。
この二つの値3と182をそれぞれProcessingへ送信し、Processing上で、
x_high * 256 + x_low = 950

つまり、
3 * 256 + 182 = 950

と計算し直して、もとの値950を得ることになります。実は、分解された値は前回同様最大値が255(256段階)であり、大きな値に対して256が幾つ分あり、そしてその余りが幾つかということを計算して送信しています。
同様にyとzについても分解して送信し、Processing上で合成します。
今回は3つの値があり、それぞれを二つに分解して送信するので、合計6回送信します。

Processingの方では、円のXY座標と大きさをそれぞれ調節可能にするプログラムとします。解像度が1024段階あるので、画面サイズは大きめにしておきます。前回同様、マウスで画面をクリックしたら、通信開始するプログラムにします。

Processingのプログラム:
//シリアルライブラリを取り込む
import processing.serial.*;

Serial myPort;
int x,y,z;

void setup(){
  //大きめの画面サイズに設定
  size(1000,700);
  //滑らかな描画にする
  smooth();
  //シリアルポート設定
  myPort=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
  //バッファ領域を空にしておく
  myPort.clear();
  //図形の塗りを200に設定
  fill(200);
}

void draw(){
  //背景を白に設定
  background(255);
  //円の描画
  //XY座標と幅、高さに変数を入れる
  ellipse(x,y,z,z);
}

void serialEvent(Serial p) {
  //バッファ領域に6個のデータがあるとき
  if(myPort.available()>5){
    //分解された値を読み込む変数を用意し
    //各変数に読み込み値を入れる
    int x_high = myPort.read();
    int x_low = myPort.read();
    int y_high = myPort.read();
    int y_low = myPort.read();
    int z_high = myPort.read();
    int z_low = myPort.read();
    
    //読み込んだ値をそれぞれ合成し、
    //1024段階の値としてx,y,zに代入
    x = x_high * 256 + x_low;
    y = y_high * 256 + y_low;
    z = z_high * 256 + z_low;
    
    //合図用のデータを送る
    myPort.write(65);
  } 
}

//マウスが押されたら通信開始
void mousePressed(){
  myPort.write(65);
}

0〜255というのは、1バイト分のデータであり、二進数の
00000000〜11111111

の値ということになります。00000000は0であり、11111111は255です。0か1が8桁あり8ビット(=1バイト)となります。
16ビットなら16桁あり
0000000000000000〜1111111111111111

となり、0〜65535となります。16ビットなので2バイトあります。
今回値を二つに分けて送信した方法というのは、16ビットを
00000000|00000000

というように上位8桁と下位8桁に分けて送信したことと同じです。
もし、256という値なら、
00000001|00000000

となり、上位8桁だけをみれば00000001なので、8ビットの1と等しいことになります。下位8桁は00000000なので、8ビットの0になります。つまり、256の場合は、1(上位)と0(下位)が送信されるということになります。
950を16ビットの二進数であらわすと、
0000001110110110

であり、同様に上位8ビット、下位8ビットで分けると、
00000011|10110110

になり、
上位8ビット00000011は10進数の3であり、下位10110110は182になり、3と182を送信することになります。
ArduinoやProcessingにも二進数/ビット演算の数式や関数があります。
>>」という記号を用いて、
950 >> 8

と書けば、この値は3となります。つまり、950/256=3と同じ結果が得られます。
>>」というのは「右にシフトする」という意味で、「950 >> 8」においては「950を8桁右にシフトする」ということになります。二進数で言えば、
0000001110110110

という16桁を8桁右にずらす(シフト)ので、
0000000000000011

となり、十進数の3になります。
950 >> 1

なら、1桁右にずらすので、
0000000111011011

となります。ずらされて右端からあふれてしまった0や1は消えてしまいます。
そのほか「&」というのもあります。
950 & 255

と書けば、
182となり、950 % 256と同じ結果が得られます。以下のようにそれぞれを上下に重ね合わせて、950を255でマスキングするような感じです(ビットマスク)。
00000011|10110110  950
00000000|11111111  255
00000000|10110110  182

255の0のある桁(上位8桁)の真上の950の桁00000011はすべて消されて0となり、255の1のある桁(下位8桁)の真上の950の桁10110110だけ残ります。255は16桁のうち上位8桁はすべて0が並んでおり、下位8桁はすべて1が並んでいるので、255というマスキングを950に施すということは、950の上位8桁を消して(0にする)、下位8桁だけを取り出すということになります。つまり、
950 >> 8 = 3
950 & 255 = 182

という計算でも、950/256=3、950%256=182と同じ値が得られるということになるので、Arduino上のxの値に関しては、
x = analogRead(0);
byte mask = B11111111;
Serial.write(x >> 8);
Serial.write(x & mask);

と書くことができます。「byte mask = B11111111」というのは、maskというバイト型の変数を用意し、その値が11111111であるということで、xの値の下位8桁だけを取り出すために使っています。Arduinoにおいてバイトの場合は、8桁の二進数の頭の部分に「B」(二進数/バイナリーのフォーマット)を付けます。Processing上では、
(3 << 8) + 182 = 950

というように、逆に「3を左に8桁シフト」してから「182を足す」と「950」になるので、Processingでは、
x = (x_high << 8) + x_low;

と書けば同じ値を得ることができます。

二進数の表記やビット演算はサンプルプログラムやデータシートの中にも出てくることがあります。ビットやバイトを使わなくてもプログラムできますが、覚えておくと便利なときもあり、考え方や計算もよりシンプルになる場合があります。


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

5/27/2008

Arduino-Processing シリアル通信2


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


前回のシリアル通信では、ひとつの値をArduinoからProcessingへ一方的に送り続ける内容でした。今回は、Arduino側から三つの可変抵抗器を使って、三つの異なる値をProcessingへ送りたいと思います。

例えば、Processing上の3DモデルのX座標、Y座標、Z座標の値を送り、それぞれのツマミで三次元的に立体を動かすことができるプログラムになるということです。
Arduinoから3つの値を送るには、
Serial.write(x);
Serial.write(y);
Serial.write(z);

となります。
3つの値がXYZの順番で送られる場合、XYZ XYZ XYZ・・・と繰り返されますが、Processing側の読み取りを開始するタイミングがずれると、最初の幾つかの値をスキップしてしまい、YZX YZX YZX・・・、あるいはZXY ZXY ZXY・・・となってしまいます。このように、順番がずれないようにするためには通信上の工夫が必要となります。今回の方法では、3つの値を一方的に送るのではなく、受取確認をしながらお互いに通信します。以下にプログラムを書きます。

Arduino側のプログラム:
//x,y,zの3つの変数を用意し、初期値を0とする
int x=0;
int y=0;
int z=0;

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

void loop(){
  //アナログ入力ピン0,1,2を
  //それぞれx,y,zに対応させる
  //値を4で割って最大値255にする
  x=analogRead(0)/4;
  y=analogRead(1)/4;
  z=analogRead(2)/4;

  //Processingから合図のデータが
  //一つ送られてきたらという条件
  if(Serial.available()>0){
    //x,y,zの順番で値を送る
    Serial.write(x);
    Serial.write(y);
    Serial.write(z);
    //先ほどのProcessingからの
    //合図のデータを読み込む
    Serial.read();
  }
}

Arduinoプログラム上のSerial.available()は、外部(この場合Processing)から1バイト分のデータが何個送られてきているかを数えてくれます。送られてくるデータは、Arduinoであれ、Processingであれ、それぞれのメモリ上(バッファ領域)に一旦貯められます。その後、Serial.read()によって、貯められているデータから順番にひとつずつ読み込む処理をします。もし、読み込む処理をしなければ、送られたデータは次々とバッファ領域に溜まり続けます(設定した限界をこえるとそれ以上貯めることはできなくなります)。if(Serial.available()>0){...}という条件は、データが1個以上貯まったら(0個より多い場合)、{...}内の処理をするという意味です。

このプログラムの場合の手順は以下のようになります。

(1)まず、Processingから合図用のデータが送られてくる。
(2)合図用のデータが、一旦Arduinoのバッファ領域に貯められる。
(3)Serial.available()で、現在何個データが貯まっているか数える。
(4)合図用のデータが1個以上あれば、(5)以下を実行する。
(5)Serial.write(x)で、新たなxの値を送信する。
(6)Serial.write(y)で、新たなyの値を送信する。
(7)Serial.write(z)で、新たなzの値を送信する。
(8)Serial.read()で、バッファ領域内の合図用のデータを読み込む。
(9)結果、バッファ領域が空になる(データの個数が0個になる)。
(10)Processingからの合図用のデータを待つ。

その後は(1)に戻り、同様の処理を繰り返します。要するに、Processingからの合図のデータが毎回1個送られて、それを確認後、Arduinoはx、y、zの値を送り返すという手順を踏みます。

次にProcessing側のプログラムを書きます。Arduinoのプログラムで書いたように、Processingからは、合図用のデータを1個送る必要があります。そうすれば、Arduinoから3個のデータが送られてくるので、Processingのバッファ領域にデータが3個貯まったときに、読み込む処理をさせればいいということになります。今回はx、y、zの3個の値なので3Dの図形を動かすプログラムにします。

Processing側のプログラム:
//シリアルライブラリを取り込む
import processing.serial.*;
//シリアルのオブジェクトmyPortを用意
Serial myPort;

//x,y,zの3個の変数を用意
int x=0;
int y=0;
int z=0;

void setup(){
  //3D用の画面サイズとして設定
  size(255,255,P3D);
  //シリアルポートの設定(「A4001Kjl」は基盤により異なる)
  //Windowsの場合は、"COM5"などとなる(前回ブログを参照)
  myPort=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
  //3D図形の塗りは無し(ワイヤーフレーム描画)
  noFill();
}

void draw(){
  //背景色を白に設定
  background(255);
  //3Dの位置座標にx,y,z入れる
  translate(x,y,z);
  //一辺50のボックス(立方体)を描画
  box(50);
}

//シリアル通信処理
void serialEvent(Serial p){
  //Arduinoから送られてきたデータが
  //3個(2より多い)の場合
  if(myPort.available()>2){
    //x,y,zの順番でデータを読み込む
    x=myPort.read();
    y=myPort.read();
    z=myPort.read();
    //読み込み後、合図データ送信
    myPort.write(65);
  }
}

//マウスが押されたら通信開始とする
void mousePressed(){
  //念のためバッファ領域を空にする
  myPort.clear();
  //とりあえず65という合図用データを送る
  myPort.write(65);
}

以上が、Processing側のプログラムです。3Dの場合は、size()の括弧内に画面幅、高さ以外に「P3D」を書き足します。ワイヤーフレームで描画した方が、今回の場合分かりやすいと思うので、noFill()をつかって塗り面を無しにしました。
void draw(){...}内のtranslate()は、その後に描画される3D図形の座標値をいれます。translate()に含まれる値が変化することで、3D図形は移動します。box()は、3Dの直方体を描画します。box(50,100,80)というように3つ値を入れれば、幅、高さ、奥行きをそれぞれ定義できます。box(50)の場合は、各辺が50の立方体になります。
シリアル通信の部分は、まずmousePressed()でマウスを押したときに、myPort.clear()でProcessingのバッファ領域内に貯まっているデータをとりあえず空にします(初期化)。そして通信を開始するきっかけとなる合図のデータをmyPort.write(65)で送ります。65という値を送っていますが、0〜255の値であれば何でも構いません。Arduino側は送られてくるデータの個数を数えるのであって、データの中身の値については、何でもいいことになります。つまり、合図用に何らかのデータを1個送ればいいということです。一旦合図用のデータがProcessingから送られれば、次にArduinoがそのデータを受取り、そして3個のデータを送り返してきます。void serialEvent(Serial p){...}内のif(myPort.available()>2){...}内では、Arduinoから送られて来たデータがProcessingのバッファ領域に3個貯まったら(2個より多くなったら)myPort.read()で3回読み込み、x,y,zの3個の変数にそれぞれ値を入れていき、それらのデータは、box()の3D座標になるtranslate()に代入され、box()が動きます。



もう一度、手順をはじめから書くと、

(1)Arduinoの電源がオンになり、Arduinoのプログラムが開始。
(2)Processingのプログラムを立ち上げる。
(3)Arduinoは、Processingからの合図用データを待つ。
(4)Processing側でマウスを押す(通信開始)。
(5)Processingのバッファ領域を一旦空にする(初期化)。
(6)Processingから合図用データが1個送られる。
 *以下、Arduino上での処理
(7)Arduinoのバッファ領域に合図用データが一旦貯められる。
(8)Arduinoのバッファ領域内のデータの個数を数える。
(9)データの個数が1個以上のとき、以下の処理を実行。
(10)Arduinoから、新たなxの値を送信する。
(11)Arduinoから、新たなyの値を送信する。
(12)Arduinoから、新たなzの値を送信する。
(13)バッファ領域内の合図用のデータを読み込む。
(14)読み込んだ結果、Arduinoのバッファ領域が空になる。
(15)次の合図用データがバッファ領域に貯まるまで待機。
 *以下、Processing上での処理
(16)Processingのバッファ領域にデータが貯められる。
(17)Processingのバッファ領域内のデータの個数を数える。
(18)データの個数が3個になったら、以下の処理を実行。
(19)xの値として1番目のデータを読み込む。
(20)yの値として2番目のデータを読み込む。
(21)zの値として3番目のデータを読み込む。
(22)その結果、Processingのバッファ領域が空になる。
(23)Processingから合図用データが1個送られる。
(24)次の3個のデータがバッファ領域に貯まるまで待機。
 *その後は(7)へ戻り処理を繰り返します。

Serial.available()
myPort.available()を使うことで、バッファ領域内に貯められているデータの個数を数え、その個数をもとにデータを送受信するタイミングを制御することができます。これ以外にも、送られる複数のデータの先頭部分や最後の部分に「.」(ピリオド)などの特定のデータを付け加えて送ることで、読み込み開始地点や読み込み終了地点を知らせる方法もあります。
シリアル通信を使うことで、コンピュータの内側と外側の世界をつなぐことができます。今後も様々な表現に応じて「シリアル通信」の技術は、繰り返し登場してきます。今回一気に理解できなくても、徐々に使いこなしていくことで、身についていくと思います。


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

5/26/2008

Arduino-Processing シリアル通信1

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

今回はシリアル通信を用いて、Arduino基盤に接続した入力装置(可変抵抗器)で、Processingで描かれた図形を動かしてみます。
Arduino側のシリアル通信は、前回のモニタリングで使用したときのような感じです。Processingにおいても、シリアル通信の設定が必要になります。

通信の流れとして:
Arduino基盤に接続した入力装置からの値をanalogRead()で読み取る。
その値をSerial.print()でProcessing側に送る。
Processing側で、その値をSerial.read()で読み取る。
Processing上の図形の座標値に入れる。
という感じです。

まずは、Arduino側からプログラムしていきます。今回入力装置となる可変抵抗器を基盤に接続します。


Arduino側のプログラム:
int val; //読み取り値の変数を用意

void setup(){
  Serial.begin(9600);
}
void loop(){
  //アナログ入力0番ピンの値を読み取り(0~1023)
  //4で割った値を変数valに入れる(0~255)
  val=analogRead(0)/4;
  //シリアルでvalを送信
  Serial.write(val);
  //1秒間に20回ループ(0.05sec)とする
  delay(50);
}

上記プログラムの説明:
Processingと通信する場合、Serial.write()で一度に送ることができる値は、0〜255までの数値になるので、analogRead()で読み取った値(0〜1023)を4で割って、最大値が255になるようにスケーリングした値をvalに代入し送信します。delay(50)程度にし、一秒間に20回送信することにします。Arduino側から一方的にデータを送り続けるので、あまりにも多くのデータを送りすぎると、Processing側での受取処理が追いつかなくなり、反応に時差がでることがあります。

Processingのプログラムについて:
Processing側のプログラムでは、シリアル通信(Serial)はライブラリに含まれており、必要に応じてその機能を取り込む(import)必要があります。
Processingのsketchを開いたら、メニューバーのSketch>Import Library>serialを選択します。そうすると、プログラムを書く欄に自動的に「import processing.serial.*;」という一文が追加されます。これによって、シリアル機能が導入されます。そして以下のようにmyPort(名前は任意)というシリアルのためのインスタンスを用意し、シリアルポート、通信速度を設定します。Processingのシリアル通信についての説明は、Processing画面のメニューバーでHelp>Referenceへ行き、そのページ内の上部のLibrariesをクリックし、さらにページ内のCore Librariesの欄にSerialという項目があるので、そこをクリックします(Processingのサイトにも同じSerialのページがあります)。

Processing側のプログラム:
//シリアルライブラリを取り入れる
import processing.serial.*;
//myPort(任意名)というインスタンスを用意
Serial myPort;

int x; //図形のX座標の変数を用意

void setup(){
  //画面サイズ
  size(256,256);
  //シリアルポートの設定
  myPort=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
}

void draw(){
  //背景色を白に設定
  background(255);
  //XY座標を(x,100)に設定し、
  //幅50、高さ50の円を描画
  ellipse(x,100,50,50);
}

void serialEvent(Serial p){
  //変数xにシリアル通信で読み込んだ値を代入
  x=myPort.read();
}

上記プログラムの説明:
初期設定のsetup()内の「myPort=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);」の設定において、MacOSXの場合は「/dev/tty.usbserial-********」の箇所の********の部分は使用しているArduino基盤によって異なります。Arduino画面のメニューバーのTools>Serial Portのなかから使用している基盤のシリアルポートと同じものを書いて、両端を「"」マークで括ってください。Windowsの場合は、「COM*」(*は番号)のようにポートが表示されるので、同様に「COM*」を「"」マークで括ってください。
通信速度の「9600」は、通常この設定で構いません。

シリアル通信が行われるたびに、serialEvent()内のx=myPort.read()によってArduino基盤から送られて来た値を読み込み、変数xに代入され、最終的にellipse()のX座標に代入されます。Arduinoから送られてくる値は0〜255であるため、Processing上のellipse()のX座標の移動範囲も0〜255(256段階)となります。それに合わせて、Processingの画面幅を256に設定しました。つまり、ellipse()を1ピクセルずつ左右に動かすことができます。


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

Arduino 圧電スピーカ

圧電スピーカは、ブザー(音が鳴る)として機能する一種のスピーカです。圧電スピーカについている2本の線に、そのまま電気を流しても音は鳴りません。音を出すためには、高速でオンとオフを繰り返し(パルス)、内部の金属板を振動させます。プログラム上では、digitalWrite()を用いてHIGHとLOWの切替を行い、delay()によってオン/オフの時間の間隔(周波数)をつくりだすことで制御することができます。
以下では、前回使用した可変抵抗器を用いて、可変的に周波数をつくりだし、圧電スピーカを鳴らす実験をしてみます。

Arduinoのプログラム:
int val=0;

void setup(){
pinMode(13,OUTPUT);
}

void loop(){
val=analogRead(0);
digitalWrite(13,HIGH);
delay(val);
digitalWrite(13,LOW);
delay(val);
}

音はでますが、あまりいい音ではないので、周波数を細かくするために、delay()のかわりにdelayMicroseconds()を用いて同様にテストしてみます。delayMicroseconds()は、delay()の1/1000の時間、つまり1マイクロ秒(1/1000000秒)が単位となります。高音領域が高周波になりすぎないように、valに予め+500のオフセットを設け、500〜1523までの値がdelayMicroseconds()に入ることにします。ちなみに、Arduinoサイトの説明によると、delayMicroseconds()の()内に入れられる数値は、最大で「16383」であり、delayMicroseconds(0)というように()内に「0」を入れると0秒ではなく、それよりも長い時間(~1020マイクロ秒)ディレイしてしまうと書いてあります。設定するときは注意して下さい。

int val=0;

void setup(){
pinMode(13,OUTPUT);
}

void loop(){
val=analogRead(0)+500;
digitalWrite(13,HIGH);
delayMicroseconds(val);
digitalWrite(13,LOW);
delayMicroseconds(val);
}

圧電スピーカは、音を鳴らす以外にセンサとして使うこともできます。圧電スピーカとLEDを直結し、圧電スピーカを指先でたたいて衝撃を与えると電源がなくてもLEDが一瞬発光します。

この発電原理を利用して、圧電スピーカをマイクのような衝撃センサとして用いることが可能となります。圧電スピーカからの電圧をanalogRead()で読み取って、どの程度の値が得られるかテストしてみます。
読み取り値などを画面に出力するには、シリアル通信機能を用いて以下のようなプログラムを付け足し、プログラムが開始したら、Arduinoの画面上のSerial Monitorボタンを押します。

初期設定のSerial.begin(9600)は、通信速度を9600に設定し、シリアル通信を開始するという意味です。Serial.println(val)は、モニタリングするためにvalの値をシリアル通信を用いて出力します。Serial.println()は、データを毎回改行しながら出力します。もうひとつSerial.print()という、改行せずにそのままデータを送り出すものもあります。今回はモニタリングするために、改行して出力したほうが見やすいので、Serial.println()の方を使います。

圧電スピーカに衝撃を与えると値が変化することが確認できます。出力される値が10以上であれば、衝撃を加えたことに反応しているとみなすこととします。以下に、圧電スピーカからの衝撃によってオン/オフするLEDのプログラムを書きます。boolean型の変数を用いて、以前Processingで用いたトグルスイッチのプログラムを付け足します。boolean型の変数checkがfalseの時はLEDがオフの状態、checkがtrueの時はオンの状態とします。
int val=0;
boolean check=false;

void setup(){
//13番ピンをLEDの出力に設定
pinMode(13,OUTPUT);
}

void loop(){
//圧電スピーカ0番ピンの読み取り値
val=analogRead(0);
//読み取り値が10以上の場合
if(val>10){
if(check==false){ //LEDオフ状態の場合
digitalWrite(13,HIGH); //オンに切替
check=true; //オンの状態として記憶
}else{ //LEDオン状態の場合
digitalWrite(13,LOW); //オフに切替
check=false; //オフの状態として記憶
}
}
delay(100);
}


Arduinoのサイト内のLearning/Examples/SoundページにもPlay Melodies with a Piezo Speakerという名前で、圧電スピーカのサンプルが掲載されています。

Arduino アナログ入出力

前回までは、LEDをオン/オフ(5V/0V)するプログラムでした。今回は、オン/オフの制御ではなく、外部からの入力によってLEDの明暗を変化させるプログラムをします。
analogWrite()を用いれば、0〜255の256段階でLEDの明るさが調節できます。モータの出力に使えば、スピード調節が可能となります。PWM(パルス幅変調)という方法で256段階を調整しますが、パルスについては後で説明したいと思います。まずはanalogWrite()の使い方からマスターしていくこととします。
analogWrite(ピン番号,出力値)というように二つの値を設定する必要があります。「ピン番号」は出力したいピンの番号を入れますが、Arduino基盤の0〜13番ピンのうちの3、5、6、9、10、11の6つのピン(基盤上のピン番号下にPWMと書かれている番号)のどれかになります。「出力値」は、0〜255(0V〜5Vに対応)の値をいれます。
analogWrite(3,0);

と書けば、3番ピンを0(0V)で出力となるので、LEDであれば消灯します。
analogWrite(3,255);

であれば、一番明るい状態となり、
analogWrite(3,127);

であれば、約半分の明るさとなります。

次に、外部からの入力(センサ入力)の際に使用するanalogRead()について説明します。analogRead()は、0〜1023の1024段階で値を読み取ることができ、0〜1023が0V〜5Vに対応しています。Arduino基盤の右下に「ANALOG IN」と書かれた0〜5番ピンを使用します。
analogRead(0);

と書けば、「ANALOG IN」の0番ピンに接続したワイヤからの電圧を読み取って0〜1023の値が得られることになります。
入力用に使われるセンサは様々なものがありますが、今回は「可変抵抗器(ボリューム)」を使用することにします。一般的な可変抵抗器には3つの端子があり、両端の二つの端子を0V(GND)と5Vにつなぎ、ツマミを回すと中央の端子から任意の電圧(0V〜5V)が出力されます。つまり、中央の端子からの可変電圧をanalogRead()で読み取って、その入力値をanalogWrite()の出力値に入れれば、LEDの明るさをツマミをひねることで調整できるようになります。
ひとつ注意しなければいけないことは、analogRead()によって得られる値は0〜1023に対して、analogWrite()の出力値が0〜255までなので、入力値(読み取り値)を4で割った値を出力値に入れないといけません。
尚、analogWrite()analogRead()の場合は、初期設定でpinMode()の入出力を設定せずに直接使うことが出来ます。

以下に、analogWrite()analogRead()を用いて、外部からの入力(可変抵抗器)によってLEDの明るさを変えるプログラムを書きます。
int val=0; //入力値の変数を用意し、0に設定

void setup(){
//pinMode()の設定は不要
}

void loop(){
//ANALOG INの0番ピンを読み取りvalに代入
val=analogRead(0);
//アナログ出力(PWM)の3番ピンを出力とし
//valを4で割った値を入れる
analogWrite(3,val/4);
//0.1秒ループにする
delay(100);
}


可変抵抗器のツマミの回し方(時計回り/半時計回り)と出力値の増減の向きを変えたい場合は、可変抵抗器の両端の端子(0V端子/5V端子)を入れ替えてください。


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