INDEX(各項目ごとの目次)

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

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

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

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などの無線モジュールと組み合わせて、遠隔的に映像を配信することも可能になるかもしれません。同時に、今後利用されなくなるであろうブラウン管テレビモニターの再利用方法にもつながるかもしれません。


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/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 (モノタロウ)


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