ハードウェアが出来上がったら、 ソフトウェアを書かなくてはなりません。 でも、本当にロボットの自律制御プログラムなんて書けるの?
まず、CodeWarriorでプロジェクトを作成します。 このプロジェクトは、 簡単にコーディングをするためにProcessor Expertを利用し、 センサ&モータ・ドライバ基板は、 Processor Expertから使用します。
この文書では、CodeWarrior V5.0を使用しています。 現在の最新版は、CodeWarrior V5.1です。 ところが、V5.1に搭載されているプロセッサ・エキスパートに 既知のバグが発見されえいるため、 この説明通りにプログラムを作成してもうまく動きません。
このバグに対応するパッチは、非公式にリリースされていますので、 V5.1を使う場合には、このパッチが必要です。
まず、CodeWarriorを起動します。 スタートメニューからCodeWarriorを 選択するとCodeWarriorのIDEが起動し、 下のようなStartupダイアログが出てきます。
もし、出てこない場合には、 メニューバーからFile → Startup Dialog...を 選択するとStartupダイアログが出てきます。
新規プロジェクトを作成するために、 Create New Projectボタンをクリックします。
まず、プログラム開発の基礎になる、プロジェクトを作成します。
まず、使用する言語とプロジェクトの作成場所を決めます。
上のダイアログで、言語に"C"を プロジェクト名に"Udetate"を ディレクトリ欄に"C:\Projects\CW\Udetate"を 指定します。 ディレクトリ欄は、お使いのPCのお好きな場所を指定してください。
設定が終わったら、「次へ」ボタンをクリックします。
次は、使用するデバイスとデバッガの接続方法を設定します。
ここでは、デバイスとしてMC9S08QG8を選択します。 MC9S08QG8は、HCS08→HCS08QG Family→MC9S08QG8とたどると 見つかります。
デバッガには、DEMO9S08QG8評価ボードに搭載されている Multilinkを使用しますから、 接続方法には"P&E Multilink/Cyclone Pro"を選択します。
設定が終わったら、「次へ」ボタンをクリックします。
次にプログラムで使用するライブラリなどを指定する 「追加ファイルの選択」ダイアログが開きます。
このプロジェクトでは、使用するファイルがありませんので、 そのまま、「次へ」ボタンをクリックします。
次のダイアログでは、プログラム開発を助けるツールを選択します。
ここでは、Processor Expertを使いたいので、 "Processor Expert"をチェックします。
設定が終わったら、「次へ」ボタンをクリックします。
次のダイアログでは、Cコンパイラで使用するオプションを設定します。
ここでは、以下のようにすべてデフォルトのままにしておきます。
startup code | ANSI startup code |
---|---|
memory model | Small |
floating point format | None |
設定が終わったら、「次へ」ボタンをクリックします。
PC-Lintは、プログラムの書き方を検査してくれます。 今回は、使いませんので、"No"を選択します。
設定が終わったら、「完了」ボタンをクリックすると 新しいプロジェクトが作成されます。
プロジェクトの作成には、もうちょっと設定が必要です。 次は、使用するパッケージの選択ダイアログが現れます。
今回は、16ピンのDIPパッケージを使用するので、 "MC9S08QG8_16"以外のチェックをはずします。
設定が終わったら、「OK」ボタンをクリックします。
プロジェクト作成のための設定も最後になりました。 最後に選ぶのは、デバッグとリリースの選択です。
プログラムをコンパイルするときに、 デバッガをつなぐことを想定するか、製品に組み込むことを想定するかで コンパイル方法が変わってくる場合があります。 そんな時に使用するのが、ここで指定する"Configuration"です。 デバッガをつなぐときには、"Debug"を 製品向けのマイコンにプログラムを書き込むときには"Release"を それぞれ選択してコンパイルします。
今回は、評価ボードをそのまま使うので、"Debug"設定しか使いません。 そこで、"Release"のチェックをはずします。
設定が終わったら、「OK」ボタンをクリックします。
Processor Expertは、 ビーンと呼ばれる部品を組み合わせることによって、 比較的簡単にプログラミングを行うことができます。 以下は、このプロジェクトで使用するビーンの一覧です。
ビーンクラス | ビーンインスタンス | ポート | 用途 |
---|---|---|---|
ADC | AD1 | PTA1 | 加速度センサX軸 |
PTB2 | 加速度センサY軸 | ||
PTB3 | 加速度センサZ軸 | ||
BitIO | Motor1A | PTB4 | モータ1制御 A |
BitIO | Motor1B | PTA3 | モータ1制御 B |
BitIO | Motor2A | PTB6 | モータ2制御 A |
BitIO | Motor2B | PTB7 | モータ2制御 B |
BitIO | PWM1 | PTA0 | モータ1制御 PWM |
BitIO | PWM2 | PTB5 | モータ2制御 PWM |
このセンサ&モータ・ドライバ基板は、PWM制御対応になっているのですが、 今回は、PWMを使わずにプログラムを書いてみました。
3軸加速度センサの出すアナログ信号は、A/Dコンバータビーンで受信します。
以下の図のように ビーン・セレクタからADCビーンを選んでダブルクリックします。
すると、プロジェクト・パネルに"AD1"というビーンが追加され、 ビーン・インスペクタに"AD1"のプロパティが表示されます。
プロパティには、BASIC, ADVANCED, EXPERTの三つのレベルがあり、 BASICの場合は表示されないプロパティが EXPERTの場合には表示されるようになります。
このプロジェクトでは、EXPERTレベルの表示で 説明を行っていますので、 もし、並んでいるプロパティの数が違っている場合には、 ビーン・インスペクタの下の方にある EXPERTボタンをクリックしてください。
これから、プロパティを設定していきます。
センサ&モータ・ドライバ基板に搭載されているのは3軸の加速度センサなので、 三つのアナログ信号として以下のポートに伝達されます。
ポート | ADCチャンネル | 用途 |
---|---|---|
PTA1 | ADP1 | 加速度センサX軸 |
PTB2 | ADP6 | 加速度センサY軸 |
PTB3 | ADP7 | 加速度センサZ軸 |
そこで、ビーン・インスペクタの"A/D channels"(A/Dのチャネル数)の アイコンをクリックして チャネル数を3にします。 そして、チャネル0から2までの端子名を以下のように変更します。
項目名 | 設定する値 | ||
---|---|---|---|
A/D channels | A/Dのチャネル数 | 3 | |
→ Channel0 → A/D channel (pin) | チャネル0の端子 | PTA1_KBIP1_ADP1_ACMPMINUS | |
→ Channel1 → A/D channel (pin) | チャネル1の端子 | PTB2_KBiP6_SPSCK_ADP6 | |
→ Channel2 → A/D channel (pin) | チャネル2の端子 | PTB3_KBIP7_MOSI_ADP7 |
次に設定するのは、A/D変換の時間の設定です。 ビーン・インスペクタの"Conversion time"(A/D変換時間)の アイコンを クリックすると変換時間を設定するダイアログが現れます。
このダイアログの下半分にある"Possible settings"(可能な設定選択肢)の 中にある"46オs"を左クリックするとプルダウンメニューが現れます。 プルダウンメニューの中から"46オs"を選択し、 最後に「OK」ボタンをクリックすれば設定完了です。
この"オ"の部分はいわゆる「半角カタカナ」フォントの文字で、 英語などで使用される"Western"フォントでは、"µ"が表示されます。 つまり、これは「マイクロ秒」を示しているのです。 CodeWarriorは日本語フォントに対応していないので、 このような表示になってしまいます。 せめて、"us"で代用してくれれば、こんな事にはならなかったのに。
以上で、"ADC"ビーンの設定は終了です。 最終的にプロパティは以下のようになったはずです。
このプログラムでは、モータの動作を汎用出力端子で制御しています。 この制御に使用されるのが"BitIO"と呼ばれるビーン群です。 ここでは全部で六つのビーンを定義します。
以下の図のように ビーン・セレクタからBitIOビーンを選んでダブルクリックします。
すると、プロジェクト・パネルに"Bit1"というビーンが追加され、 ビーン・インスペクタに"Bit1"のプロパティが表示されます。
ここまでの手順は、以下の六つのビーンに共通です。 以下、それぞれのビーンのカスタマイズをしていきます。
カスタマイズする項目は、以下の三つです。
デフォルトのビーン名は、"Bit1"のような適当な名前になっています。 このままの名前でも使えることは使えるのですが、 プログラムを書いていると他のビーンとの区別が難しくなります。 そこで、ビーンにはわかりやすい名前を付けます。 最初のビーンは、第一モータのA制御信号につながるので、 ビーン・インスペクタの"Bean name"(ビーンの名前)に "Motor1A"と入力します。
次は、このビーンがどの端子に関連付けられるかを指定します。 ビーン・インスペクタの "Pin for I/O"(I/Oに使用される端子)のリストから "PTB4_MISO"を選択します。
最後にこのビーンが出力専用であることを明示します。 このビーンは、入出力ビーンとして定義して実行時に方向を指定するような 使い方もできるのですが、 出力にしか使わないのであれば出力専用と指定したほうが プログラムがコンパクトになります。 ビーン・インスペクタの "Direction"(入出力方向)のリストから "Output"を選択します。
以上でカスタマイズは終了です。 最終的にプロパティは以下のようになったはずです。
Motor1Aと同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。
項目名 | 設定する値 | |
---|---|---|
Bean name | ビーンの名前 | Motor1B |
Pin for I/O | I/Oに使用される端子 | PTA3_KBIP3_SCL_ADP3 |
Direction | 入出力方向 | Output |
最終的にプロパティは以下のようになったはずです。
Motor1AおよびMotor1Bと同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。
項目名 | 設定する値 | |
---|---|---|
Bean name | ビーンの名前 | Motor2A |
Pin for I/O | I/Oに使用される端子 | PTB6_SDA_XTAL |
Direction | 入出力方向 | Output |
最終的にプロパティは以下のようになったはずです。
Motor1A, Motor1BおよびMotor2Aと同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。
項目名 | 設定する値 | |
---|---|---|
Bean name | ビーンの名前 | Motor2B |
Pin for I/O | I/Oに使用される端子 | PTB7_SCL_EXTAL |
Direction | 入出力方向 | Output |
最終的にプロパティは以下のようになったはずです。
これも上記と同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。
項目名 | 設定する値 | |
---|---|---|
Bean name | ビーンの名前 | PWM1 |
Pin for I/O | I/Oに使用される端子 | PTA0_KBIP0_TPMCH0_ADP0_ACMPPLUS |
Direction | 入出力方向 | Output |
最終的にプロパティは以下のようになったはずです。
このビーンには、"PWM1"という名前をつけましたが、 モータ・ドライバをON/OFFさせるのにしか使用していません。
これも上記と同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。
項目名 | 設定する値 | |
---|---|---|
Bean name | ビーンの名前 | PWM2 |
Pin for I/O | I/Oに使用される端子 | PTB5_TPMCH1_SS |
Direction | 入出力方向 | Output |
最終的にプロパティは以下のようになったはずです。
このビーンには、"PWM2"という名前をつけましたが、 "PWM1"ビーンと同様に モータ・ドライバをON/OFFさせるのにしか使用していません。
以上ですべてのビーンの定義が終わりました。 このプログラムでは、割り込みを一切使わずに機能を実現しています。 その代わり、時間待ちをする"Delay100US"というメソッドを使います。 このメソッドは、プロジェクト・パネルから"Cpu:MC9S08QG8CPB"項目を開くと 現れます。
ご覧のようにこのメソッドには、 デフォルトでは「使用不可」であることを示す マークが 付いていますので、使用することができません。 そこで、このメソッド表示のところで、「右クリック」でメニューを出し、 "Enable"(有効)を選択します。
するとメソッドが使用可能である事を示す マークに 変わります。 これで、時間待ちメソッドを使うことができます。
以上でプログラムで使用する材料はそろいました。 しかし、プロセッサエキスパートの設定を行っただけでは ソースコードは生成されません。 ソースコードを生成させるには、 プロジェクト・パネルの ボタンを クリックします。
すると、コード生成、コンパイル、リンクが行われます。 生成されたソースコードは、 プロジェクト・パネルから見える "Generated Modules"(生成されたモジュール)フォルダの中に 収納されます。
出来上がったプロジェクトにプログラムを書いて、 3軸加速度センサの動作を見てみましょう。
まず、必要になるのは、A/D変換プログラムです。 ここでは、加速度センサからの値を3軸同時に取得するプログラムを作ります。 プロジェクト・パネルから見える"Udetate.c:main"をダブルクリックして テキストエディタを開きます。
そして、 以下のプログラムを"main(void)"関数の直前に書きます。
byte acc_errcode; // Error code returned by AD1 word acc_value[3]; // Value returned by sensor. /*================================================================ processAcc : Convert acceleration value into an array. The converted value is in the range 0 to 1023. ================================================================*/ void processAcc(void) { byte i; // Local loop counter. word adc_value[3]; // Value returned by AD1. // Measure all channels acc_errcode = AD1_Measure(TRUE); if (acc_errcode != ERR_OK) return; // Get value of all channels. acc_errcode = AD1_GetValue16(adc_value); if (acc_errcode != ERR_OK) return; // Revert to 10-bit value. for (i = 0; i < 3; i++) { acc_value[i] = adc_value[i] >> 6; } }
この"processAcc(void)"関数では、加速度センサのアナログ入力を 10ビットの値としてacc_value[]という配列に入れます。
上の関数を無限ループの中から呼び出すと、 配列acc_value[]を見ると いつでも各軸の加速度が得られることになります。 以下のプログラムを"main(void)"関数の"Write your code here"の下に 書き込みます。
/* Write your code here */ /* For example: for(;;) { } */ APCTL1 = 0xC2; // Work around for ADC bean bug. for (;;) { processAcc(); }
無限ループの前にAPCTL1レジスタに値を書き込む 文があります。 これは、プロセッサ・エキスパートのあるバグを回避するために入れた 文です。
このバグは、A/Dコンバータが使用するチャネルを有効にする際に APCTL1レジスタに誤った値を書き込んでしまうという もので、これを回避しないとモータ制御用端子が出力になりません。
このバグについては、Freescaleでバグとして認識され、 パッチも開発されたのですが、この原稿を書いている時には、 まだ一般には公開されていない模様です。 そこで、不本意ですが、バグを回避する一文をここに書くことにしました。
さて、いよいよマイコンにプログラムを書き込みます。 使用する開発ツールは、 DEMO9S08QG8評価ボードにすでに搭載されています。
このアプリケーションでは、 PTA0をモータ制御出力として、また、PTA1をX軸アナログ入力として 使用します。 このため、評価ボード上の半固定抵抗と光センサの出力が これらの信号と衝突しないように、 "USER_EN"ジャンパのRV1およびRZ1をはずしておきます。
ジャンパを設定したら、腕立て君に搭載した DEMO9S08QG8評価ボードをUSBケーブルで接続します。 この時、モータ駆動用の電池ははずしておいてください。
最初に評価ボードを接続したときには、 「新しいハードウェアの検索ウィザードの開始」 というダイアログが表示されます。 この時には、 「ソフトウェアを自動的にインストールする」を選択して "USB Multilink"のデバイスドライバをインストールします。
CodeWarriorのプログラム書き込み機能は、 デバッガやシミュレータと兼用のツールから利用します。
ドロップダウンリストが"P&E Multilink/Cyclone Pro"になっているのを 確認して、 アイコンをクリックすると コード生成、コンパイル、リンクの後、 デバッガが立ち上がります。
デバッガの初期化が終わると "ICD - Connection Manager"(In-Circuit Debuggerの接続マネージャ) ウィンドウが現れます。 ここでは、USBインターフェースの設定を行います。
"Interface:"と"Port:"の項目は、 自動的に認識されて正しい値が入っているはずです。 "Port:"に"DEMO9S08QG8"が表示されているのを確認して、 "Connect"をクリックします。
ここで、ツール・バーにある ボタンをクリックするとプログラムは自律走行を始めます。
加速度センサの値は、acc_value[]配列に代入され、 データ1・パネルの表示が時々刻々と変わるはずです。
データ1・パネルの"acc_value"の左にある を クリックすると、配列の中身が見えるようになります。
ところが、腕立て君を傾けても値が変わってくれません。 これは、配列表示の更新周期が「自動」になっているためです。 この値を周期的に更新するには、設定を変更しなくてはなりません。 設定のためのメニューが、 データ1・パネルで「右クリック」すると出てきます。
"Mode → Periodical..."とたどると "Update Ratio"(更新周期)ダイアログが現れます。
このダイアログで、更新周期を設定すると、 データ1・パネルの値がその周期に従って更新されるようになります。 デフォルトの"10"のまま、「OK」ボタンをクリックします。
すると、変数の値の表示が周期的に変更されるので、 現在の値を簡単に確認することができます。
確かに腕立て君を傾けると表示が変化する様子がわかります。 でも、この数値は、何を意味しているのでしょうか。
データシートによると、加速度センサの出力は、 加速度ゼロの時に電源電圧の50%の値を出して、1gあたり 800mVの変化を示すと書いてあります。
このデータ・シートの情報を基に、 上のデータ1・パネルの値から加速度を計算してみます。
軸 | A/D変換値 | 電圧 | 加速度 |
---|---|---|---|
X | 496 | 1.598 | -0.064g |
Y | 496 | 1.598 | -0.064g |
Z | 759 | 2.446 | +0.995g |
この表から、XとYは、ほぼゼロ、Zはほぼ1を示していることがわかります。 この値は腕立て君が静止した状態の値なのですが、 なぜ、Z軸の加速度がゼロにならないのでしょうか。
実は、腕立て君は、加速はしていませんが、 重力をを受けて下向きに加速度を感じています。 これが、Z軸に現れているのです。
このプログラムで腕立て君が加速度センサの値を読み取ろうとしているのは、 モータを停止したときです。 そのため、加速度センサは、腕立て君の受けている重力の方向を示し、 結果として腕立て君の姿勢が検出されるという仕組みになっています。
このプログラムでは、体の傾きの検出しか必要としません。 そのため、X軸とY軸だけを使った単純なルールにより傾きを検出しています。
X軸 | ||||
---|---|---|---|---|
小さい | 適当 | 大きい | ||
Y軸 | 大きい | 上向きで左に傾いている | 上向き | 上向きで右に傾いている |
適当 | 左に傾いている | 目標を向いている | 右に傾いている | |
小さい | 下向きで左に傾いている | 下向き | 下向きで右に傾いている |
以上の9種類の場合分けを行って現在の姿勢を検出します。
センサ&モータ・ドライバ基板には、PWMに対応する機能があるのですが、 今回はその機能を使わずにソフトウェアによってモータ駆動を行います。
このプログラムでは、まったくダイナミックな制御を行わずに 静止した状態で姿勢を検知し、 その結果によって決められた一定のモータ駆動パルスを発生して モータを動かします。 このプログラムで扱うモータの動作は、以下の4通りです。
コマンド | コード | 動作 |
---|---|---|
左ロール | $20 (32) | 左肩を下げ、右肩を上げます。 |
上昇 | $10 (16) | 両肩を上げます。 |
下降 | $08 (8) | 両肩を下げます。 |
右ロール | $04 (4) | 左肩を上げ、右肩を下げます。 |
モータ駆動部分は、この4種類のコマンドを姿勢検知部分から受け、 姿勢をある目標値に向けて動かします。
コマンドを受けたモータ駆動部は、 コマンドの種類によってモータに通電します。 以下のタイミングチャートは、モータ駆動信号のパターンを描いたものです。
モータ駆動タイミングを制御するために、 三つのパラメータが用意されています。
上のタイミングチャートでは、 二つのモータが双方とも動作する様子が描かれていますが、 コマンドによっては、片方のモータだけが動作することもあります。
以上の方針を元にモータ駆動プログラムを作成します。 以下のプログラムを"processAcc(void)"関数の直後に追加します。
byte command; // Command code for motor drive. byte nPulse = 20; // Number of pulse in an action. byte tDrive = 20; // Time period to drive motor. byte tIdle = 0; // Time period not to drive motor. /*================================================================ waitDrive : Wait for driving period. ================================================================*/ void waitDrive(void) { if (tDrive > 0) { Cpu_Delay100US(tDrive); } } /*================================================================ waitIdle : Wait for non-driving period. ================================================================*/ void waitIdle(void) { if (tIdle > 0) { Cpu_Delay100US(tIdle); } } /*================================================================ processMot : Drive motors regarding the COMMAND code. 0x20 : Role Left 0x10 : Head Up 0x08 : Head Down 0x04 : Role Right ================================================================*/ void processMot(void) { byte i; // Local loop counter. if (command & 0x20) { // Role left for (i = 0; i < nPulse; i++) { Motor1A_SetVal(); Motor1B_ClrVal(); waitDrive(); Motor1A_ClrVal(); Motor1B_ClrVal(); waitIdle(); Motor2A_SetVal(); Motor2B_ClrVal(); waitDrive(); Motor2A_ClrVal(); Motor2B_ClrVal(); waitIdle(); } command &= ~0x20; // Action done } if (command & 0x04) { // Role right for (i = 0; i < nPulse; i++) { Motor1A_ClrVal(); Motor1B_SetVal(); waitDrive(); Motor1A_ClrVal(); Motor1B_ClrVal(); waitIdle(); Motor2A_ClrVal(); Motor2B_SetVal(); waitDrive(); Motor2A_ClrVal(); Motor2B_ClrVal(); waitIdle(); } command &= ~0x04; // Action done } if (command & 0x10) { // Head up for (i = 0; i < nPulse; i++) { Motor1A_SetVal(); Motor1B_ClrVal(); waitDrive(); Motor1A_ClrVal(); Motor1B_ClrVal(); waitIdle(); Motor2A_ClrVal(); Motor2B_SetVal(); waitDrive(); Motor2A_ClrVal(); Motor2B_ClrVal(); waitIdle(); } command &= ~0x10; // Action done } if (command & 0x08) { // Head down for (i = 0; i < nPulse; i++) { Motor1A_ClrVal(); Motor1B_SetVal(); waitDrive(); Motor1A_ClrVal(); Motor1B_ClrVal(); waitIdle(); Motor2A_SetVal(); Motor2B_ClrVal(); waitDrive(); Motor2A_ClrVal(); Motor2B_ClrVal(); waitIdle(); } command &= ~0x08; // Action done } }
更にメインプログラムは、以下のように変更します。
APCTL1 = 0xC2; // Work around for ADC bean bug. for (;;) { processAcc(); processMot(); }
これで、コマンドに従ってモータが動くようになります。 プログラムを書き込んで、デバッガから実行させてもモータは動きません。 ここで、データ1・パネルのcommand変数の数値のところを ダブルクリックして、たとえば"8"に書き換えると モータが少しだけ動きます。 これが、下降コマンドの動作です。
他のコマンド(4,16,32)を送ると別の動作をします。
状況判断部分と駆動部分ができたので、 これらをつなぐ制御戦略を決めます。
まずは、パラメータで指示された方角に向くプログラムです。 この場合の方角は、地理上の方角ではなく上下左右の方角です。 センサの値によって以下のような動作を行わせます。
X軸 | ||||
---|---|---|---|---|
小さい | 適当 | 大きい | ||
Y軸 | 大きい | 右にロールして下降 | 下降 | 左にロールして下降 |
適当 | 右にロール | 静止 | 左にロール | |
小さい | 右にロールして上昇 | 上昇 | 左にロールして上昇 |
このような戦略にしました。 以下のプログラムを"processMot(void)"関数の直後に追加します。
word targetX = 512; // target value for X-acc word targetY = 512; // target value for Y-acc word toleranceX = 2; // tolerance of X-acc word toleranceY = 2; // tolerance of Y-acc /*================================================================ specifyStrategy : Specify command by acc_value[] ================================================================*/ void specifyStrategy(void) { if (acc_value[0] < targetX - toleranceX) { command |= 0x04; // Role right } else if (acc_value[0] > targetX + toleranceX) { command |= 0x20; // Role left } if (acc_value[1] < targetY - toleranceY) { command |= 0x10; // Head up } else if (acc_value[1] > targetY + toleranceY) { command |= 0x08; // Head down } }
A/D変換の値を調べたときにわかったように、 中心の値は理想的には512になります。 このため、まずは、X=512, Y=512を目標の値に設定します。 また、X,Yの許容誤差を2にしてあります。 この値は、私が見つけた経験値ですので、 デバッガから変更して適当な値を見つけてください。 また、モータ駆動部分のパラメータも細かい動きができるように 以下のような値に設定してみました。 これも私の経験値ですので色々な値を試してみてください。 このパラメータは、使用する電池の消耗度にも影響されるようです。、
byte nPulse = 3; // Number of pulse in an action. byte tDrive = 7; // Time period to drive motor. byte tIdle = 0; // Time period not to drive motor.
メインプログラムで戦略プログラムを使うように 以下のように変更します。
APCTL1 = 0xC2; // Work around for ADC bean bug. for (;;) { processAcc(); specifyStrategy(); processMot(); }
これで、指示された方角に向くと静止するようになるはずです。 ふらふらして落ち着きが無いようであれば、 toleranceXとtoleranceYの 値を大きくしてみてください。
また、targetYの値を変えると頭の高さが、 targetXの値を変えると左右の傾きが変わります。
toleranceが小さいと最適な状態が見つからずに 右左に揺れたまま落ち着きません。 これは、加速度センサが純粋に重力方向を検出しているのではなく、 左右の揺れも検出してしまっているためです。 この揺れの誤差を吸収するためにtoleranceという パラメータを取り入れました。
同様に一動作当たりの移動量が大きいと最適な姿勢を通り過ぎてしまうため、 やはり左右に揺れてしまいます。 これを解決するために、nPulseとtDriveの パラメータを小さくして、一動作当たりの移動量を減らしています。 ただ、本当に落ち着いた動作をさせたいのであれば、 目標値からの誤差にしたがって、一動作当たりの移動量を決定する 本来の意味の「制御」を行う必要があります。 ここでは、そこまでの厳密な制御は扱いません。
最後に腕立て動作をする戦略プログラムを導入します。 以下のプログラムを"specifyStrategy(void)"関数の直後に追加します。
bool up; // direction of current action. word maxY = 580; // Maximum Y-acc value word minY = 510; // Minimum Y-acc value word stepY = 4; // Step of Y adjustment /*================================================================ adjustTarget : Adjust the target value. ================================================================*/ void adjustTarget(void) { if (up) { if (acc_value[1] >= targetY) { if (targetY >= maxY) { up = FALSE; } else { targetY += stepY; } } } else { if (acc_value[1] <= targetY) { if (targetY <= minY) { up = TRUE; } else { targetY -= stepY; } } } }
プログラムは、上昇中と下降中で処理が大きく二つに分かれています。 上昇中は、 目標値targetYが最大値maxYに達するまで、 下降中は、最小値minYに達するまで、 目標値を更新していきます。 これらのパラメータもデバッガで変更できますので、 ロボットの動きを確かめながら変更することができます。
メインプログラムで目標値更新プログラムを使うように 以下のように変更します。
APCTL1 = 0xC2; // Work around for ADC bean bug. for (;;) { processAcc(); adjustTarget(); specifyStrategy(); processMot(); }
これで、腕立て動作を行うロボットの完成です。
このロボットは、加速度センサで姿勢を検知していますので、 片方の腕が段差に乗り上げた状態でも 正しく左右の傾きを水平に保とうとします。
また、傾斜地に置いた場合でも、 姿勢を保つために腕の曲げ方を工夫して腕立て動作を行います。
2006-06-28 発行。