非常灯付き充電器
電子工作コンテスト2011に応募したものです。
| 固定リンク | コメント (0) | トラックバック (0)
11月19日、20日に行われたマイクロマウス2011・ロボトレース競技に参加しました。本大会は初めての参加でしたが運良く決勝まで進むことができました。
決勝は、本大会決勝の難しさを思い知らされた形となりました。後から聞いた話では、コースが長く持ち時間が短いので、3回の走行を全部完走するのすら難しいということでした。まさにその通りでした。上位入賞者に比べればモーターをはじめ機体の性能が不足しているのは明らかですが、それ以上に調整不足のため第二走行の速度を抑えすぎていたのが敗因です。今のハードウェアでもまだまだタイムを縮められる余地はありました。
決勝の走行の様子です。
今回出走させたロボットは風兎2011LTという名前で、2年前に作った風兎2009LTのセンサー部分を改良したものです。急なカーブでも確実にマーカーを読み取れるよう、写真のようにマーカー用センサーの位置を車軸に近付けました。
また、プログラムの面でも改良を行いました。直線で速度を上げて走り、コーナー前で確実に減速できるよう等加速・定速・等減速の速度制御(いわゆる台形加速制御)を採用しました。上の映像の2回目の走行を見ると直線では加速した後コーナーの手前で最低速まで減速しているのが分かるのではないかと思います。
速度を制御したり、またロボトレースで必須とされるコースの形状を解析したりといったことをするためには、車輪の回転数を計測するエンコーダーを使うのが普通です。しかし風兎2011LTにはエンコーダーを搭載していません。VSTONEのビュートチェイサーという学習用ロボットキットを改造した機体であり、モーターはマブチのFA-130、ギアボックスはタミヤの楽しい工作シリーズという玩具用のごく一般的な部品で作られています。このようなハードウェアでも工夫次第で課題(線に沿って走る、コース形状を解析・記憶して速度を上げて走る)を達成できるということを示せました。こういったことを評価されて特別賞を受賞しました。
工夫といっても、まったく新しい発明をしたわけではありません。DCモーターのモデルとしてよく使われる、
という性質を考慮して擬似的な回転数の計測とフィードフォワードによる回転数(前進速度)の制御を行っているのです。詳細は後日風兎2009LTの解説に書いていきたいと思います。
最近は高校や中学校でもロボットのプログラミング学習が行われるところもあるようです。こういった学習ではフローチャート、条件分岐、繰り返し、あるいはC言語といった道具立ての部分が遊離して教えられているケースが多いように思います。それでも一定の効果はあるのでしょうが、このように物理や数学と結びつくところまで進められればより有意義なものになるのではないかと思います。
| 固定リンク | コメント (0) | トラックバック (0)
2010年後半の活動です。
機械研究会の大学祭企画に協力しました。吹き抜けの空間を利用した巨大なクレーンゲームです。 私は電子回路設計の助言、制御アルゴリズムの考案ならびに映像の撮影・編集を行いました。
電子工作コンテスト2010に出品し、インフロー賞を受賞しました。スケールの大きさに高い評価を貰いました。
| 固定リンク | コメント (0) | トラックバック (0)
本講座のサンプルプログラムの利用方法を手順に沿って説明します。
1. VSTONEのVS-WRC003サポートページ内にあるC言語サンプルソースの中から「LEDの点滅」サンプルソース一式をダウンロードし、適当な作業用フォルダに展開します。
2. Beauto講座の記事内にあるソースファイルへのリンクを右クリックしてダウンロードします。ここでは6_pid.cを例にとって説明します。
3. 作業用フォルダに保存した「LED点滅」のプロジェクトをHEWで開きます。ウィンドウ左側にはプロジェクトに入っているファイルリストが表示されています。このうちled.cにmain関数が書かれていて、LEDを点滅させる処理を行っています。これを6_pid.cと入れ替えるために、
4. まずHEWのメニューから[プロジェクト]-[ファイルの削除...]を選びます。
5. 次に6_pid.cをプロジェクトに追加します。メニューから[プロジェクト]-[ファイルの追加...]を選びます。新しく開いたウィンドウで6_pid.cを指定します。
プロジェクトにファイルが追加されるとこのように表示されます。
6. VSTONEのサンプルプログラムと同じようにビルド、書き込み等を行います。
| 固定リンク | コメント (0) | トラックバック (0)
第4回以降のサンプルプログラムに誤りがありましたので訂正(アップロードしたファイルの更新)するとともにお詫びします。以前のバージョンでは関数hgetcがないというリンカエラーが出ます。古いバージョンをダウンロードされた方は再度ダウンロードしてください。
| 固定リンク | コメント (0) | トラックバック (0)
本講座のサンプルがVS-WRC003LVやBeauto Roverでも動作することを確認しました。若干の修正が必要です。
本講座のサンプルをVS-WRC003LVで使う最も簡単な方法は、 VSTONEのサポートページから「無線コントローラVS-C1での操縦 」等のサンプルをダウンロードしてこれを利用することです。 プロジェクトをHEWで開いてmain.cをプロジェクトから削除、本講座のサンプルソース(たとえば6_pid.c)を代わりに追加します。
次に、6_pid.cの
#include "vs-wrc003.h"
を
#include "vs-wrc003lv.h"
に直してください。
VSTONEのサポートページにVS-WRC003LV シリアルコンバータが公開されました。これを使えば本講座のようにシリアル通信を用いた調整およびデバッグができます。
ただし、少なくとも私の環境では通信速度を19200bpsまで下げた上でパリティなしの設定にする必要がありました。ロボット側で通信速度とパリティの設定を変えるには本講座のサンプルソースの
InitSci3(CBR_115200, even, 1);
を
InitSci3(CBR_19200, non, 1);
に変更します。また、PCのターミナルソフトでもあわせて設定を行ってください。
なお、現時点においてvs-wrc003lv.cのmtr_run関数には依然としてバグがあります。 前回書いた注意点のようにvs-wrc003lv.cを修正するか、本講座サンプルソースのrun関数から下記2行を消すかのいずれかを行ってください。
void run(int speed, int turn_speed)
{
// 前進速度+回転速度から左右のモーターのduty比を決める
int d1 = speed - turn_speed;
int d2 = speed + turn_speed;
// 絶対値が127(100%)を超えないようにする
// duty比が0のとき、Mtr_Runに渡す数は0x80にしてブレーキ動作にする
if(d1 > 127) d1 = 127;
else if(d1 < -127) d1 = -127;
else if(d1 == 0) d1 = 0x80; // brake ← この行を削除
if(d2 > 127) d2 = 127;
else if(d2 < -127) d2 = -127;
else if(d2 == 0) d2 = 0x80; // brake ← この行を削除
Mtr_Run(-d1, d2, 0, 0);
}
| 固定リンク | コメント (0) | トラックバック (0)
2010年9月18日現在、VSTONEのサポートページで公開されているVS-WRC003LVのサンプルプログラム(「モータ制御サンプル」等、すべて)に含まれるMtr_run関数にはバグがあります。
この関数はBeauto Chaser用に書いたプログラムをVS-WRC003LVでも変わらず使えるようにするため(互換性のため)のものです。ブレーキ動作をあらわす0x80を入れた場合の動作が間違っていて、モーターが100%に近い速度で回りだします。本講座のプログラムのようにブレーキをよく使う場合には致命的ですので注意してください。
この関数を使用する場合はvs-wrc003lv.cの56行目から62行目を次のように修正してください。
void Mtr_Run(BYTE mt1,BYTE mt2,BYTE mt3,BYTE mt4){
if(mt1 == 0x80)
! mt1 == 0;
if(mt2 == 0x80)
! mt2 == 0;
if(mt3 == 0x80)
! mt3 == 0;
if(mt4 == 0x80)
! mt4 == 0;
void Mtr_Run(BYTE mt1,BYTE mt2,BYTE mt3,BYTE mt4){
if(mt1 == 0x80)
! mt1 = 0;
if(mt2 == 0x80)
! mt2 = 0;
if(mt3 == 0x80)
! mt3 = 0;
if(mt4 == 0x80)
! mt4 = 0;
なおVS-WRC003LVのモータードライバはフリーの動作ができないようになっており、Mtr_runやMtr_run_lvに0を指定してもフリーではなくブレーキになります。本講座のサンプルプログラムをVS-WRC003LVに応用する際、第5回、第6回のrun関数にある「0の場合0x80にする特別な場合わけ」は必要なくなります。
もうひとつの重要なことですが、VS-WRC003LVはPCからシリアルポートとして認識させることができません。そのため本講座で説明したような対話的デバッグや調整ができません。理論上、回路構成としてはシリアルポートとして使うこともできるようになっていますので、公式サポートが出るかどうか様子を見ることにしましょう。
| 固定リンク | コメント (0) | トラックバック (0)
第3回では、Beauto Builderのようなソフトを使ったフローチャート式プログラミングと本講座での考え方の違いについて述べました。
今回はこのうち2.についてです。
Beauto Builderのようなフローチャート式プログラミングツールでは「右センサーが一定以上の値で反応したら」等、YesかNoで表せる条件に従って分岐するのが制御プログラムの主要な構造となります。前回まではこれと同じ発想で、センサーから得た情報を3種類に場合分けして行動を選択していました。そのため、直線の線上を走り続けているときでも、ずれがある値より大きくなった瞬間に今までと大きく異なる動きをしようとします。このためロボットの動きがギクシャクしたものになってしまいます。微妙なずれを微妙に調整しながら滑らかに走行するのには向いていません。より多くの場合分けをすれば多少改善されるかもしれませんが、本質的には変わりません。
今回から扱うのは、連続な量を読み取り、そのまま連続的なモーター指令値に変換するという考え方です。用いる基本的な道具は条件判断ではなく、四則演算、特に乗算です。
void line_trace(int speed, float Kp, float Kd)
{
float prev_line_pos = 0.0; //前回のラインの位置
while(1) {
// 正規化したセンサ値の計算、ライン位置の計算などは省略
// ・・・(中略)・・・
line_pos = ・・・;
run(speed, Kp * line_pos + Kd * (line_pos - prev_line_pos)); // (1)
// ・・・(中略)・・・
Sync();
prev_line_pos = line_pos; // 前回ライン位置として記憶する (2)
}
(1)がライントレースの制御をおこなっている部分です。場合分けはありません。順番に見ていきましょう。
runへの第1引数、前進速度は一定でspeedにしています。これによりロボットは常に前へ進みます。
第2引数の第1項Kp * line_posは、ラインからのずれに比例したちからで旋回させるためのものです。ラインから大きくずれればずれるほど速く回転して中央に戻ろうとします。なお、ライン位置に掛かる比例定数Kpは適当に調整する必要があります。本講座では、ラインが左に見えるときline_posは負、runの第2引数は左回転(反時計回り)させたいとき正と決めました。そのためKpは負の数でなければなりません。正にするとラインから逃げるロボットになります(是非一度試してみてください)。
第2項についてですが、まずprev_line_posはこのwhileループについて1回前の繰り返しのときのline_posを記憶しています。(2)がそれを行っています。このため(line_pos - prev_line_pos)は、「前回からラインの位置がどれだけ変化したか」、つまりロボットから見てラインが移動する速度になります。これを用いて第2項のKd * (line_pos - prev_line_pos)はラインが移動する速度に比例したちからでロボットを旋回させようとします。ロボットが右回転している時に左回転の力を加えることになり、左右に振れるのを抑えるように働きます。また、ラインに対して平行でないと平行にする方向の力を発生しますので、折れ線や急なコーナーでロボットが線に対して斜めになったときにも効果を発揮します。このKdも同様に負でなければなりません。
最後に、2つの項の効果が両方とも出るように足し算します。
今回説明した制御方式だけでは急すぎるカーブや直角コーナーには対応できません。すべてのセンサーが線から外れ、線を見失ってしまったときにはライン位置が正しく計算できないためです。急なカーブに対してはKpを大きくするか、あるいはセンサー数を増やして検知範囲を広くすることで線を見失いにくくするという対処が考えられます。しかし直角コーナーは無理です。その他、何かの拍子に線から外れた場合も考慮して何らかの対策を別途記述しておく必要があります。
一例として、線から外れたら線のあると思われる方向にもっと強く旋回するという方法があります。これを実現するには、線がどちらにあるか、もっと正確には「もし次の瞬間に線から外れたとしたらどちらに外れた可能性が高いか」を常に判断し、記憶しておく必要があります。センサーが3個以上あれば比較的簡単に実現できます。センサーが2個だと検知範囲が狭いので、確実性の面で少し難しくなります。
基本的な使い方は前回までと同じです。パラメータがspeed、Kp、Kdの3つに変更されています。deadband、turn_speedは今回の制御方式では使わないのでなくしました。
すべてのセンサーが線から外れた時を考慮した処理の一例として、停止して走行を終了するようにしました。センサー数を変更する場合はこの部分も変更する必要があります。このままではちょっと急なカーブでもすぐコースアウトして止まってしまいます。このような部分は「条件判断して処理を選ぶ」という点でBeauto Builderを使うフローチャート式プログラミングと同じですので、適宜工夫してみてください。
(line_trace関数内)
if(sval[0] < 0.3 && sval[1] < 0.3) { //どのセンサーも白い床の上にあるとき
break; //ライントレース終了
}
また、USBケーブルを外してからスタートできるように、基板上のスイッチをスタートボタンにしました。設定画面で何も入力せず[Enter]を押すとReady.と表示されます。この後スイッチを押すと走り出します。
(main関数内)
//何も入力せずEnterを押すと設定モード終了
SciStrTx("Ready,\r\n", 8);
//ボタンを押すとスタート
while(!getSW());
USBケーブルを抜き差しした場合は、再度通信できるようにするためにターミナルソフトで切断と接続の操作を行ってください。
電源4.8V、Aギア(減速比12.7:1)という条件で試したところではspeed=80くらいまではコースアウトせず走れました。直線ではあまり揺れずにまっすぐ走り、カーブも止まらず滑らかに回れているということで、第1回で掲げた目標は達成できたのではないかと思います。
今回説明した制御方式は一般にPID制御と呼ばれるものの仲間に入ります。PID制御からIをなくしたものなので特にPD制御と呼ぶこともあります。次回、このPID制御と本講座の内容の関係について解説します。なぜこのような制御方式をするのか、またなぜこの制御方式でうまくいくのかについて解説したいと思っています。
なおPID制御に関する日本語版Wikipediaの記事はこの記事執筆時点でいくつか曖昧で不適切な点がありそのせいで分かりにくいものになっています。もし見るなら英語版をお勧めします。ついでですが、英語版PID Controllerの冒頭にある図は制御理論で使われるブロック線図というものです。見た目が似ているかも知れませんが、フローチャートとは違います。本講座の意図はこの図に集約されているので、この図を使って次回説明する予定です。
| 固定リンク | コメント (0) | トラックバック (0)
前回まででON-OFF制御器を作成し、その挙動をパラメータによって調整できるようにしました。ラインを捉えることはできるものの、前に進んでいませんでした。今回から前に進むようにします。
前進しながら左右方向に旋回もするという動作をわかりやすく記述するため、今回からMtr_Run関数に左右の車輪の速度(モーターのduty比)を明示的に書くのではなく、「前進速度」と「回転速度」とに分けて指定するようにします。サンプルプログラムではrunという関数がこれを行います。
void run(int speed, int turn_speed) { int d1 = speed - turn_speed; // (1)
int d2 = speed + turn_speed;
// 絶対値が127(100%)を超えないようにする (2)
// duty比が0のとき、Mtr_Runに渡す数は0x80にしてブレーキ動作にする
if(d1 > 127) d1 = 127;
else if(d1 < -127) d1 = -127;
else if(d1 == 0) d1 = 0x80; // brake(3)
if(d2 > 127) d2 = 127;
else if(d2 < -127) d2 = -127;
else if(d2 == 0) d2 = 0x80; // brake
Mtr_Run(-d1, d2, 0, 0); // (4)
}
(1)の部分で左右の車輪の速度を計算します。
例えば前進速度80、回転速度30の時は左右の車輪の速度は上の計算式よりそれぞれ50、110となります。こうすると左に旋回しながら前進することになります。
(4)では実際にMtr_Run関数を呼び出してモーターの出力を変えます。左右の車輪でモーターは逆向きになるので1番目の引数は-d1として回転方向を逆、つまりd1が正のとき左モーターがロボットを前進させる方向に回るようにしています。このあたりはロボットの機械設計と電気配線に合わせて変更する必要があります。
※とはいえ実は旋回速度や前進速度に負の値を設定することでつじつまあわせができてしまいますが。
run(80, 30)とすると最終的に、Mtr_Run(-50, 110, 0, 0)を実行することになります。
(2)は、speed=100、turn_speed=100のような大きな値を指定した時にd1やd2の値が127を超えてしまう場合、127で頭打ちにするための処理です。マイナス方向も同様です。頭打ちにした場合、厳密には回転速度、前進速度ともに希望した値になりません。ゆっくり走る場合は影響しませんが、留意しておいてください。
(3)はMtr_Runの仕様上の特別処理です。第3回 の余談として書いたことですが、Mtr_Runの仕様では0はフリー、0x80はブレーキとなっているのでd1やd2の計算結果が0になったときは0x80に直します。
このrun関数を使うとON-OFF制御によるライントレース部分はこのように書けます。なお、speedという変数を追加してあります。旋回も直進も第1引数が共通になっていることに注目してください。
if(line_pos < -deadband) { // ラインが左側にあるとき
run(speed, turn_speed); // 左回転(反時計回り)しながら進む
} else if(deadband < line_pos) { // ラインが右側にあるとき
run(speed, -turn_speed); // 右回転(時計回り)しながら進む
} else { // ラインが大体真ん中にあるとき
run(speed, 0); // まっすぐ進む
}
使い方は前回とほぼ同じです。パラメータ調整画面に前進速度speedを追加し、また上の説明を反映してline_trace関数の仕様を変更しました。第3引数に前進速度speedを指定するようになっています。
パラメータを変えて挙動の違いを見てください。Aギア(減速比12.7:1)、電源4.8Vの条件で試したところではspeedが30くらいまではうまく走ることができました。
ラインから外れた場合の処理は入っていません。コースからある程度以上外れると大抵は直進して暴走しますので気をつけてください。本来は線から外れたら停止するか、線に復帰するようにすべきです。本講座でも余裕があれば後からいくつかの方法を説明します。
次回はPID制御の考え方を取り入れたライントレースについて説明します。
| 固定リンク | コメント (0) | トラックバック (0)
パラメータといえば数学では「媒介変数」というものですが、ロボットのプログラミングではロボットの挙動を間接的に決めるための数値、という意味合いです。たとえば前回の「許容するライン中央からのずれ幅」や「ロボットを回転させる速度」もパラメータといえます。
最適なパラメータはロボットの設計、更には部品の個体差、コースの状況によって違います。今回は対話形式でのパラメータ調整を行う方法を紹介します。
今回はロボットのプログラミングというよりは次回以降の準備ですので、先にプログラムを示した上で必要な部分を説明します。
ビルド方法は第1回を参考にしてください。
まず前回同様、センサー調整を行います。センサー全部を黒い線の上に載せてスペースキーを押し、続いてセンサー全部を白い床の上に移動させてスペースキーを押します。
するとパラメータ調整画面が表示されます。
1. deadband=0.200000 2. turn speed=127 >
これは変更するパラメータを選ぶ画面です。現在のパラメータの値を表示しています。deadbandは中央からどのくらいずれたら復帰しようとするか、turn speedは復帰動作で車体を回転させる速度です。たとえば線から外れた時の回転速度を変えたい場合は2[Enter]と操作します。すると
value=
と表示されますので変更後の値を入力してください。たとえば60[enter]と入力します。パラメータ一覧に戻ります。変更されたのを確認してください。
パラメータを選ぶ画面、「>」に対して[Enter]のみを入力すると調整モードを終了します。
調整モード終了後、スペースキーを押すとライン追従動作を開始します。ロボットをラインの上に置いて動作を確認してください。[Esc]キーを押すとライン追従動作を終了し、パラメータ調整画面に戻ります。
パラメータによってロボットの動きが変わるのを確認してください。
ライン追従のプログラムはline_traceという名前の関数にまとめました。中のプログラムは前回とほとんど同じです。違いはturn_speed=回転速度とdeadband不感帯幅を引数として受け取り、それに応じた動作をする点です。例えば車体を回転させるようにモーターを駆動する部分は次のようになっています。
Mtr_Run(turn_speed, turn_speed, 0, 0); // 車体を左回転させる
パラメータ設定のメニューに相当する部分です。
while(1) {
int i;
char buf[80];
// 設定メニューを表示する
sprintf(buf, "1. deadband=%f\r\n2. turn speed=%d\r\n>",
deadband, turn_speed);
SciStrTx(buf, strlen(buf));
// どの値を調整するか入力する
if(inputDecInt(&i) == 0) {
//何も入力せずEnterを押した場合
break;
} else {
//何かが選択された場合
SciStrTx("value=", 6);
switch(i) {
case 1:
inputDecFloat(&deadband);
break;
case 2:
inputDecInt(&turn_speed);
break;
}
}
}
シリアル通信から数値を入力できるようにするため、inputDecInt、inputDecFloatという関数を用意しました。
inputDecInt
シリアル通信から整数を入力する
引数:
int *dst 結果を格納する変数のアドレス
戻り値:
正常に1つの整数を読み取れたら1、
そうでないとき(たとえば何も入れずEnter)は0。
inputDecFloat(&deadband);のように使います。呼び出されると(シリアル通信の)入力待ちになります。Enterを押すと変数deadbandに入力した数が入ります。1文字も数字がなかった場合、例えばabc[Enter]と入力した場合や[Enter]のみを入力した場合はdeadbandの値は変わりません。
&はアドレス演算子というものです。詳しくは大抵のC言語の入門書の関数呼び出しおよびポインタに関する項に載っていますので割愛します。
| 固定リンク | コメント (0) | トラックバック (0)
最近のコメント