プログラミング

ハードウェアが出来上がったら、 ソフトウェアを書かなくてはなりません。 でも、本当にロボットの自律制御プログラムなんて書けるの?

腕立て君の写真

アジェンダ

プロジェクトの準備

まず、CodeWarriorでプロジェクトを作成します。 このプロジェクトは、 簡単にコーディングをするためにProcessor Expertを利用し、 センサ&モータ・ドライバ基板は、 Processor Expertから使用します。

CodeWarrior V5.0を使います

この文書では、CodeWarrior V5.0を使用しています。 現在の最新版は、CodeWarrior V5.1です。 ところが、V5.1に搭載されているプロセッサ・エキスパートに 既知のバグが発見されえいるため、 この説明通りにプログラムを作成してもうまく動きません。

このバグに対応するパッチは、非公式にリリースされていますので、 V5.1を使う場合には、このパッチが必要です。

CodeWarriorを起動する

まず、CodeWarriorを起動します。 スタートメニューからCodeWarriorを 選択するとCodeWarriorIDEが起動し、 下のようなStartupダイアログが出てきます。

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 codeANSI startup code
memory modelSmall
floating point formatNone

設定が終わったら、「次へ」ボタンをクリックします。

PC-Lintツールの選択

PC-Lintは、プログラムの書き方を検査してくれます。 今回は、使いませんので、"No"を選択します。

PC-Lintツールの選択

設定が終わったら、「完了」ボタンをクリックすると 新しいプロジェクトが作成されます。

使用するパッケージの選択

プロジェクトの作成には、もうちょっと設定が必要です。 次は、使用するパッケージの選択ダイアログが現れます。

使用するパッケージの選択

今回は、16ピンのDIPパッケージを使用するので、 "MC9S08QG8_16"以外のチェックをはずします。

設定が終わったら、「OK」ボタンをクリックします。

デバッグとリリースの選択

プロジェクト作成のための設定も最後になりました。 最後に選ぶのは、デバッグとリリースの選択です。

デバッグとリリースの選択

プログラムをコンパイルするときに、 デバッガをつなぐことを想定するか、製品に組み込むことを想定するかで コンパイル方法が変わってくる場合があります。 そんな時に使用するのが、ここで指定する"Configuration"です。 デバッガをつなぐときには、"Debug"を 製品向けのマイコンにプログラムを書き込むときには"Release"を それぞれ選択してコンパイルします。

今回は、評価ボードをそのまま使うので、"Debug"設定しか使いません。 そこで、"Release"のチェックをはずします。

設定が終わったら、「OK」ボタンをクリックします。

新規プロジェクトの完成

以上で、新しいプロジェクトが完成しました。

プロジェクトの初期画面

画面は、上記のようにいくつかのパネルに分かれています。

ビーンを定義する

Processor Expertは、 ビーンと呼ばれる部品を組み合わせることによって、 比較的簡単にプログラミングを行うことができます。 以下は、このプロジェクトで使用するビーンの一覧です。

ビーンの割り当て
ビーンクラスビーンインスタンス ポート用途
ADCAD1 PTA1加速度センサX軸
PTB2加速度センサY軸
PTB3加速度センサZ軸
BitIOMotor1APTB4モータ1制御 A
BitIOMotor1BPTA3モータ1制御 B
BitIOMotor2APTB6モータ2制御 A
BitIOMotor2BPTB7モータ2制御 B
BitIOPWM1PTA0モータ1制御 PWM
BitIOPWM2PTB5モータ2制御 PWM

このセンサ&モータ・ドライバ基板は、PWM制御対応になっているのですが、 今回は、PWMを使わずにプログラムを書いてみました。

A/Dコンバータ・ビーンの設定

3軸加速度センサの出すアナログ信号は、A/Dコンバータビーンで受信します。

ADCビーンの呼び出し

以下の図のように ビーン・セレクタからADCビーンを選んでダブルクリックします。

ADCビーンを選ぶ

すると、プロジェクト・パネルに"AD1"というビーンが追加され、 ビーン・インスペクタに"AD1"のプロパティが表示されます。

プロジェクトパネルのADCビーン ADCビーンのプロパティ

プロパティの数が違うときは

プロパティには、BASIC, ADVANCED, EXPERTの三つのレベルがあり、 BASICの場合は表示されないプロパティが EXPERTの場合には表示されるようになります。

このプロジェクトでは、EXPERTレベルの表示で 説明を行っていますので、 もし、並んでいるプロパティの数が違っている場合には、 ビーン・インスペクタの下の方にある EXPERTボタンをクリックしてください。

これから、プロパティを設定していきます。

A/Dの入力チャネルの設定

センサ&モータ・ドライバ基板に搭載されているのは3軸の加速度センサなので、 三つのアナログ信号として以下のポートに伝達されます。

アナログ入力チャネル
ポートADCチャンネル用途
PTA1ADP1加速度センサX軸
PTB2ADP6加速度センサY軸
PTB3ADP7加速度センサZ軸

そこで、ビーン・インスペクタの"A/D channels"(A/Dのチャネル数)の +アイコンをクリックして チャネル数を3にします。 そして、チャネル0から2までの端子名を以下のように変更します。

AD1のチャネル設定
項目名設定する値
A/D channelsA/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変換の時間の設定

次に設定するのは、A/D変換の時間の設定です。 ビーン・インスペクタの"Conversion time"(A/D変換時間)の 点々アイコンを クリックすると変換時間を設定するダイアログが現れます。

変換時間設定ダイアログ

このダイアログの下半分にある"Possible settings"(可能な設定選択肢)の 中にある"46オs"を左クリックするとプルダウンメニューが現れます。 プルダウンメニューの中から"46オs"を選択し、 最後に「OK」ボタンをクリックすれば設定完了です。

"オs"って何だ?

この"オ"の部分はいわゆる「半角カタカナ」フォントの文字で、 英語などで使用される"Western"フォントでは、"µ"が表示されます。 つまり、これは「マイクロ秒」を示しているのです。 CodeWarriorは日本語フォントに対応していないので、 このような表示になってしまいます。 せめて、"us"で代用してくれれば、こんな事にはならなかったのに。

以上で、"ADC"ビーンの設定は終了です。 最終的にプロパティは以下のようになったはずです。

ADCビーンの設定済みプロパティ

モータ制御出力の設定

このプログラムでは、モータの動作を汎用出力端子で制御しています。 この制御に使用されるのが"BitIO"と呼ばれるビーン群です。 ここでは全部で六つのビーンを定義します。

BitIOビーンの呼び出し

以下の図のように ビーン・セレクタからBitIOビーンを選んでダブルクリックします。

BitIOビーンを選ぶ

すると、プロジェクト・パネルに"Bit1"というビーンが追加され、 ビーン・インスペクタに"Bit1"のプロパティが表示されます。

プロジェクトパネルのBit1ビーン Bit1ビーンのプロパティ

ここまでの手順は、以下の六つのビーンに共通です。 以下、それぞれのビーンのカスタマイズをしていきます。

Motor1Aビーンのカスタマイズ

カスタマイズする項目は、以下の三つです。

ビーンの名前

デフォルトのビーン名は、"Bit1"のような適当な名前になっています。 このままの名前でも使えることは使えるのですが、 プログラムを書いていると他のビーンとの区別が難しくなります。 そこで、ビーンにはわかりやすい名前を付けます。 最初のビーンは、第一モータのA制御信号につながるので、 ビーン・インスペクタの"Bean name"(ビーンの名前)に "Motor1A"と入力します。

I/Oに使用される端子

次は、このビーンがどの端子に関連付けられるかを指定します。 ビーン・インスペクタの "Pin for I/O"(I/Oに使用される端子)のリストから "PTB4_MISO"を選択します。

入出力方向

最後にこのビーンが出力専用であることを明示します。 このビーンは、入出力ビーンとして定義して実行時に方向を指定するような 使い方もできるのですが、 出力にしか使わないのであれば出力専用と指定したほうが プログラムがコンパクトになります。 ビーン・インスペクタの "Direction"(入出力方向)のリストから "Output"を選択します。

以上でカスタマイズは終了です。 最終的にプロパティは以下のようになったはずです。

Motor1Aビーンの設定済みプロパティ

Motor1Bビーンのカスタマイズ

Motor1Aと同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。

Motor1Bの非デフォルト設定
項目名設定する値
Bean nameビーンの名前Motor1B
Pin for I/OI/Oに使用される端子 PTA3_KBIP3_SCL_ADP3
Direction入出力方向 Output

最終的にプロパティは以下のようになったはずです。

Motor1Bビーンの設定済みプロパティ

Motor2Aビーンのカスタマイズ

Motor1AおよびMotor1Bと同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。

Motor2Aの非デフォルト設定
項目名設定する値
Bean nameビーンの名前Motor2A
Pin for I/OI/Oに使用される端子 PTB6_SDA_XTAL
Direction入出力方向 Output

最終的にプロパティは以下のようになったはずです。

Motor2Aビーンの設定済みプロパティ

Motor2Bビーンのカスタマイズ

Motor1A, Motor1BおよびMotor2Aと同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。

Motor2Bの非デフォルト設定
項目名設定する値
Bean nameビーンの名前Motor2B
Pin for I/OI/Oに使用される端子 PTB7_SCL_EXTAL
Direction入出力方向 Output

最終的にプロパティは以下のようになったはずです。

Motor2Bビーンの設定済みプロパティ

PWM1ビーンのカスタマイズ

これも上記と同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。

PWM1の非デフォルト設定
項目名設定する値
Bean nameビーンの名前PWM1
Pin for I/OI/Oに使用される端子 PTA0_KBIP0_TPMCH0_ADP0_ACMPPLUS
Direction入出力方向 Output

最終的にプロパティは以下のようになったはずです。

PWM1ビーンの設定済みプロパティ

このビーンには、"PWM1"という名前をつけましたが、 モータ・ドライバをON/OFFさせるのにしか使用していません。

PWM2ビーンのカスタマイズ

これも上記と同様に"BitIO"ビーンを呼び出し、 以下のようにプロパティを設定します。

PWM2の非デフォルト設定
項目名設定する値
Bean nameビーンの名前PWM2
Pin for I/OI/Oに使用される端子 PTB5_TPMCH1_SS
Direction入出力方向 Output

最終的にプロパティは以下のようになったはずです。

PWM2ビーンの設定済みプロパティ

このビーンには、"PWM2"という名前をつけましたが、 "PWM1"ビーンと同様に モータ・ドライバをON/OFFさせるのにしか使用していません。

時間待ちメソッドの有効化

以上ですべてのビーンの定義が終わりました。 このプログラムでは、割り込みを一切使わずに機能を実現しています。 その代わり、時間待ちをする"Delay100US"というメソッドを使います。 このメソッドは、プロジェクト・パネルから"Cpu:MC9S08QG8CPB"項目を開くと 現れます。

無効なDelay100USメソッド

ご覧のようにこのメソッドには、 デフォルトでは「使用不可」であることを示す Xマークが 付いていますので、使用することができません。 そこで、このメソッド表示のところで、「右クリック」でメニューを出し、 "Enable"(有効)を選択します。

有効なDelay100USメソッド

するとメソッドが使用可能である事を示す チェックマークに 変わります。 これで、時間待ちメソッドを使うことができます。

コード生成とコンパイル

以上でプログラムで使用する材料はそろいました。 しかし、プロセッサエキスパートの設定を行っただけでは ソースコードは生成されません。 ソースコードを生成させるには、 プロジェクト・パネルの Makeボタンを クリックします。

Makeボタン

すると、コード生成、コンパイル、リンクが行われます。 生成されたソースコードは、 プロジェクト・パネルから見える "Generated Modules"(生成されたモジュール)フォルダの中に 収納されます。

生成されたモジュール

センサの動きを見る

出来上がったプロジェクトにプログラムを書いて、 3軸加速度センサの動作を見てみましょう。

A/D変換プログラムを作る

まず、必要になるのは、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();
  }

"Work around"とは?

無限ループの前にAPCTL1レジスタに値を書き込む 文があります。 これは、プロセッサ・エキスパートのあるバグを回避するために入れた 文です。

このバグは、A/Dコンバータが使用するチャネルを有効にする際に APCTL1レジスタに誤った値を書き込んでしまうという もので、これを回避しないとモータ制御用端子が出力になりません。

このバグについては、Freescaleでバグとして認識され、 パッチも開発されたのですが、この原稿を書いている時には、 まだ一般には公開されていない模様です。 そこで、不本意ですが、バグを回避する一文をここに書くことにしました。

マイコンにプログラムを書き込む

さて、いよいよマイコンにプログラムを書き込みます。 使用する開発ツールは、 DEMO9S08QG8評価ボードにすでに搭載されています。

DEMO9S08QG8評価ボードのジャンパを設定する

このアプリケーションでは、 PTA0をモータ制御出力として、また、PTA1をX軸アナログ入力として 使用します。 このため、評価ボード上の半固定抵抗と光センサの出力が これらの信号と衝突しないように、 "USER_EN"ジャンパのRV1およびRZ1をはずしておきます。

DEMO9S08QG8評価ボードを接続する

ジャンパを設定したら、腕立て君に搭載した DEMO9S08QG8評価ボードをUSBケーブルで接続します。 この時、モータ駆動用の電池ははずしておいてください。

最初に評価ボードを接続したときには、 「新しいハードウェアの検索ウィザードの開始」 というダイアログが表示されます。 この時には、 「ソフトウェアを自動的にインストールする」を選択して "USB Multilink"のデバイスドライバをインストールします。

デバッガ兼書き込みプログラムを呼び出す

CodeWarriorのプログラム書き込み機能は、 デバッガやシミュレータと兼用のツールから利用します。

デバッガの呼び出し

ドロップダウンリストが"P&E Multilink/Cyclone Pro"になっているのを 確認して、 DEBUGアイコンをクリックすると コード生成、コンパイル、リンクの後、 デバッガが立ち上がります。

USBインターフェースの設定

デバッガの初期化が終わると "ICD - Connection Manager"(In-Circuit Debuggerの接続マネージャ) ウィンドウが現れます。 ここでは、USBインターフェースの設定を行います。

接続方法の指定

"Interface:"と"Port:"の項目は、 自動的に認識されて正しい値が入っているはずです。 "Port:"に"DEMO9S08QG8"が表示されているのを確認して、 "Connect"をクリックします。

マイコンに書き込む

先の画面でボタンをクリックすると、 "Erase and Program Flash?"(フラッシュを消去、書き込みするか?)という ダイアログが現れます。

消去確認

今回は、プログラムの書き込みが目的ですので、 「Yes」をクリックします。 もし、デバッグだけの目的でしたら、 「No」をクリックします。

今度は、"CPROGHCS08 Programmer"(CPROGHCS08プログラマ)という ウィンドウが現れ、 瞬く間にフラッシュの消去、書き込み、確認を行い、 自動的に消滅します。

プログラマ

これで、マイコンへの書き込みは終了です。 書き込みが終わったら、デバッガの初期画面が現れます。

デバッガ初期画面

デバッガでA/D変換の結果を観測する

ここで、ツール・バーにある Start/Continue ボタンをクリックするとプログラムは自律走行を始めます。

Start/Continueボタン

加速度センサの値は、acc_value[]配列に代入され、 データ1・パネルの表示が時々刻々と変わるはずです。

acc_value配列の表示

データ1・パネルの"acc_value"の左にある "+"を クリックすると、配列の中身が見えるようになります。

acc_value配列の中身の表示

ところが、腕立て君を傾けても値が変わってくれません。 これは、配列表示の更新周期が「自動」になっているためです。 この値を周期的に更新するには、設定を変更しなくてはなりません。 設定のためのメニューが、 データ1・パネルで「右クリック」すると出てきます。

表示方法設定メニュー

"Mode → Periodical..."とたどると "Update Ratio"(更新周期)ダイアログが現れます。

表示方法設定メニュー

このダイアログで、更新周期を設定すると、 データ1・パネルの値がその周期に従って更新されるようになります。 デフォルトの"10"のまま、「OK」ボタンをクリックします。

周期的に表示を変更する変数

すると、変数の値の表示が周期的に変更されるので、 現在の値を簡単に確認することができます。

A/D変換の値からわかること

確かに腕立て君を傾けると表示が変化する様子がわかります。 でも、この数値は、何を意味しているのでしょうか。

データシートによると、加速度センサの出力は、 加速度ゼロの時に電源電圧の50%の値を出して、1gあたり 800mVの変化を示すと書いてあります。

このデータ・シートの情報を基に、 上のデータ1・パネルの値から加速度を計算してみます。

A/D変換値電圧加速度
X4961.598-0.064g
Y4961.598-0.064g
Z7592.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種類のコマンドを姿勢検知部分から受け、 姿勢をある目標値に向けて動かします。

等幅パルス制御

コマンドを受けたモータ駆動部は、 コマンドの種類によってモータに通電します。 以下のタイミングチャートは、モータ駆動信号のパターンを描いたものです。

モータ駆動タイミングを制御するために、 三つのパラメータが用意されています。

tDrive
モータに通電する時間を100マイクロ秒単位で与えます。 この時間が長くなるとモータに流れる電流が大きくなり力が出ます。
tIdle
モータに電流を流さない時間を100マイクロ秒単位で与えます。 モータ・ドライバは、この期間休むことができます。
nPulse
一動作あたりのモータ駆動パルスの数を与えます。 この数を大きくすると、一動作あたりの動きが大きくなり、 おおざっぱな動作をするようになります。

上のタイミングチャートでは、 二つのモータが双方とも動作する様子が描かれていますが、 コマンドによっては、片方のモータだけが動作することもあります。

モータ駆動プログラム

以上の方針を元にモータ駆動プログラムを作成します。 以下のプログラムを"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();
  }

これで、指示された方角に向くと静止するようになるはずです。 ふらふらして落ち着きが無いようであれば、 toleranceXtoleranceYの 値を大きくしてみてください。

また、targetYの値を変えると頭の高さが、 targetXの値を変えると左右の傾きが変わります。

ふるえるのは、なぜ?

toleranceが小さいと最適な状態が見つからずに 右左に揺れたまま落ち着きません。 これは、加速度センサが純粋に重力方向を検出しているのではなく、 左右の揺れも検出してしまっているためです。 この揺れの誤差を吸収するためにtoleranceという パラメータを取り入れました。

同様に一動作当たりの移動量が大きいと最適な姿勢を通り過ぎてしまうため、 やはり左右に揺れてしまいます。 これを解決するために、nPulsetDriveの パラメータを小さくして、一動作当たりの移動量を減らしています。 ただ、本当に落ち着いた動作をさせたいのであれば、 目標値からの誤差にしたがって、一動作当たりの移動量を決定する 本来の意味の「制御」を行う必要があります。 ここでは、そこまでの厳密な制御は扱いません。

腕立て動作戦略

最後に腕立て動作をする戦略プログラムを導入します。 以下のプログラムを"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 発行。

Copyright (C) 2006 noritan.org ■