前回は、「13.速度制御プログラムの作成」を行いましたが、駆動情報や制御情報の変更・速度情報の配信のテストなどはテストに必要なプログラムが未作成なので行えませんでした。今回は、ラズパイ・ゼロの最後のプログラムで「RosRemoCon.py」となります。


 1.リモコン信号の流れの復習
 2.ソースコードの説明と入力
 3.プログラムの実行とテスト


1.リモコン信号の流れの復習: 目次に戻る

 プログラムの説明に入る前に、以前の記事で説明したラジコン・カーに付属していたリモコンの信号の流れと改造後の信号の流れ、そしてソフトウェアの分散処理の流れを復習しておきます。プログラムの理解の参考にして下さい。

付属リモコン①ラジコン・カーに付属のリモコンを図に示しましたが、左タイヤの前後操作と右タイヤの前後操作が左右のレバースイッチで分かれています。
 プレステ等のゲームリモコンと異なり、ジョイスティックによる操作角度による連続的な強弱指示が出来ず、オンかオフの指示のみ可能です。





 右肩の「SLOWLY」スイッチを押すと「電源表示LED」が緑色に変わり低速モードとなります。左肩の「SPEEDY」スイッチを押すと「電源表示LED」が赤色に変わり高速モードに変わります。

ブロック図小②この図は「4.ラジコン・カーの改造(その3:ラズパイ・ゼロを搭載)」で使用したブロック図ですが、ラジコン・カーの内部にある制御基板には2個のICが図のように接続されています。
 先の図の付属リモコンで操作されたスイッチ情報は、左側の「リモコン受信制御IC」で処理され4本の信号となって右側の「モーター駆動IC」に入力されます。



モーター駆動IC真理値表小③この図は、前回の「1.PWM駆動による回転制御:」で使用した真理値表ですが、右側の「モーター駆動IC」のMX1919Lは、入力Aまたは入力BにPWM信号を入力することで駆動力を可変させることが出来ます。





改造ブロック図小④そこで、「4.ラジコン・カーの改造(その3:ラズパイ・ゼロを搭載)」で行った改造によって、付属リモコンからの4本の信号をラズパイ・ゼロのGPIOポートに取り込みプログラムで処理した後、GPIOポートから「モーター駆動IC」に出力しています。






ROSの分散処理構成図小⑤この図は、「6.ラズパイ・ゼロとノートパソコンの分散処理」で使用したソフトウェア構成図ですが、今回の付属リモコンは左上の「RosRemoCon.py」で処理され、ROSのトピックでノートパソコン側の「RosBrdgRcGuiV2.py」でROS標準の速度情報「/cmd_vel」トピックに変換されてラズパイ・ゼロ側の「RosSpConV2.py」でPID制御されモーター駆動ICに出力されます。






2.ソースコードの説明と入力: 目次に戻る

 もうお馴染みと思いますが、前回の「RosSpConV2-new.py」の作成と同じ手順で進めていきます。新規ファイルを作成して、空白の編集エリアにキーボードからの入力やコピー&ペーストでパイソン・プログラムを完成させてから、実行して動作確認を行っていきます。

①最初に、以前の「7.Geanyの操作と新規パイソン・プログラムの作成:」と同じように、「Geany」を開いて「RosRemoCon.py」をエクスプローラーからドラッグ&ドロップして下さい。前回使用した「RosSpConV2.py」や「RosSpConV2-new.py」が開いていたら紛らわしいので閉じておきましょう。

②「新しいファイルを作成」アイコンを使用して新しいファイルを「RosRemoCon-new.py」で保存しておいてください。保存するときに、保存ディレクトリが「RosRemoCon.py」と同じディレクトリになるように注意して下さい。

③ここからは「RosRemoCon.py」のプログラム解説を行番号を指定して行っていきます。「RosRemoCon.py」のタブをクリックして内容を確認しながら、「RosRemoCon-new.py」のタブをクリックしてコピー&ペーストを活用しながら書き込んで行って下さい。


・1行目と2行目:パイソンのプログラムのお決まり事でしたね。

・4行目から41行目:コメント行です。ご自身の理解した内容でしっかりと記載しておきましょう。

・46行目:パイソン・プログラムからROSを使用する時に必須になります。

・47行目:ROSのトピックを使用して付属リモコンのスイッチ情報を送信するために8ビットの1番短いメッセージの形式を使用しています。4つのスイッチの状態を2ビットずつにエンコードしてパッキングしています。

・48行目:ラズベリーパイのGPIOをパイソン・プログラムから使用するときに必須となります。このプログラムでは、リモコン受信ICからの入力ポートに使用しています。

・49行目:スイッチが押された時刻や離された時刻を記録することで、5ms周期のPWM信号なのか通常のスイッチ操作なのかを区別するのに使用しています。

・50行目:配列(リスト)の複製(コピー)に使用しています。リストのコピーについては、使用している167行目で説明します。

・56行目から59行目:リモコン受信ICから出力されるスイッチの捜査情報を入力するGPIOポート番号をグローバル変数に設定しています。プログラムでは「IN_R_D」からの連続するポート番号を想定していますから連番である必要があります。
 連番が出来ないケースでは「RotaryEncoder.py」の69行目のようなコードにする必要があります。

・64行目から66行目:スイッチの状態を3つの数字で定義しています。スイッチのオンとオフの2値だけであれば1ビットで表現できますが、PWMの状態を追加したので3値になり2ビット必要になります。

・189行目から190行目:今回も、いきなり最終行に来てしまいましたが、このファイルから実行されたときだけ「main」関数を実行します。パイソン・プログラムの常とう手法ですね。

・109行目:最初に実行される「main」関数で183行目までのメインブロックを構成します。

・110行目:使用するGPIOポート番号をBCM番号で指定するように設定しています。

・114行目から117行目:4つのスイッチを入力するGPIOポートを入力に設定しています。

・122行目から125行目:4つのスイッチが押された時と離された時の両方のタイミングで割り込み処理関数の「switch_on」を呼ぶように設定しています。「RotaryEncoder.py」の167行目から170行目と同じ設定になります。

・129行目:現在の時刻を保持するローカル変数の「ctm」を作成しています。

・130行目:前回ROSのトピックで配信したスイッチの変化情報を、ローカル変数の「msw」配列に作成しています。

・131行目:割り込み処理関数の「switch_on」で変化したスイッチを検出するために前回処理したスイッチの状態を、グローバル変数の「psw」配列に作成しています。変数名の前に関数名を付加するとグローバル変数として作成されます。

・132行目:割り込み処理関数の「switch_on」で変化したスイッチの前回処理した時刻を、グローバル変数の「ptm」配列に作成しています。

・137行目:「rospy」ライブラリの「init_node」関数を使用して「/rc_remocon」と言う名称のノードを作成しています。

・138行目:「rospy」ライブラリの「loginfo」関数を使用してROSノードの実行開始をログ端末に表示しておきましょう。

・142行目:「rospy」ライブラリの「Publisher」クラスを使用してリモコンのスイッチが変化した時に配信するためのメッセージのオブジェクト「pub」を作成します。トピック名は「/remocon_info」でトピックのメッセージ型にはROS標準の「UInt8」を使用し、バッファのサイズを10個使用します。

・143行目:「rospy」ライブラリの「Rate」クラスを使用して100ヘルツ(Hz)のタイマーオブジェクト「rate」を作成しています。

・144行目:ROS標準のメッセージ型「UInt8」を使用し、メッセージオブジェクト「u8」を作成しています。

・145行目:スイッチの変化をトピックに配信するタイミングを10ms遅らせるためのフラグをローカル変数の「s1」に作成しています。スイッチ情報がPWMの場合は、5ms周期で「ハイ」と「ロー」を繰り返すため10ms遅延させることによって、スイッチオンとPWMを分離するのに使用しています。

・149行目:177行目の「except」文と対になって、150行目から173行目のブロック内で発生した例外事項をキャッチさせます。例外事項とは、キーボードからの「Ctrl+C」入力を検出して終了処理を実行させます。

・150行目:作成したROSのノードが実行中は、以降の「while」ブロックを実行します。

・151行目:現在時刻を再取得して前回スイッチ情報が変化してからの時間を計算するのに使用します。

・155行目:現在のスイッチがPWM状態のスイッチの検索し、そのスイッチのインデックスをリストしてPWM信号の終了を検出しています。ちょっと複雑な「for」文なので説明しておきます。
「for」文の基本は、次のような構文になります。
  • for <変数> in <配列> :
 <配列>とは、リストやタプルを使用した複数の要素の集まりで、その要素を一つずつ順番に取り出して、<変数>に代入して「for」文のブロックを要素の数だけループさせます。155行目の<変数>は「idx」で<配列>を別の「for」文で作成しています。配列作成の基本形は次になります。
  • [ <変数2> for <変数2> in <配列2> if <条件> ]
 <配列2>から取り出した要素を<変数2>に代入して<条件>文が成立した時の<変数2>を全て「[ ]」でリストの要素として作成します。155行目では更に、配列のインデックス(要素番号)を取り出す必要があるため、<配列2>に「enumerate」関数を使用しています。
  • [ <変数3> for <変数3>, <変数4> in enumerate(<配列3>) if <条件2> ]
 「enumerate」関数に<配列3>を与えると<変数3>にインデックスが代入され<変数4>に要素が代入されます。<条件2>文が成立する<変数4>の要素がある全てのインデックス<変数3>をリストに作成しています。

 結果、155行目は、「switch_on.psw」にある4つのスイッチ要素を変数「x」にその要素のインデックスを変数「i」にセットで取り出して、要素の「x」がPWM状態を示す「SW_PWM」と一致したインデックス「i」をリストに設定しています。作成したリストから要素を1つずつ変数「idx」に取り出し「for」文を実行しています。

・156行目:155行目で見つけたPWM状態のスイッチが前回の変化から10ms(0.01秒)以上経過していないかチェックしています。

・157行目:PWM信号なのに10ms以上変化していないという事は、スイッチを押すのを止めてオフ状態になったか、「SLOWLY」から「SPEEDY」に変化してオン状態になったと判断して新しく現在の状態をポートから取り込みます。入力ポートのGPIO番号を「idx + IN_R_D」としているため、「IN_R_D」ポートから4つの入力ポートは連番である必要があります。

・158行目:コメントにしていますが、PWMのスイッチ状態を解除した時刻とスイッチ状態をデバッグ時に表示しています。「str」関数は、引数を文字列に変換して文字列を連結できるようにしています。

・162行目:前回、ROSのトピックで配信したスイッチ情報から現在のスイッチ情報が変化しているか確認します。配列の変数名で比較すると配列の要素全てが等しいかどうかチェックします。

・163行目:スイッチ情報が変化しているようであれば、変数「s1」をチェックして最初の変化なのか変化から2回目なのか調べます。

・164行目:スイッチ情報の変化から1回目の時は、変数「s1」を「False」に設定して2回目を待ちます。

・165行目:スイッチ情報の変化から2回目の時は、次のブロックを実行します。

・166行目:スイッチ情報の変化から2回目の時は、変数「s1」を「True」に戻して置き次は1回目にします。

・167行目:現在のスイッチ状態「switch_on.psw」をローカル変数の「msw」に保存して、次回のスイッチ状態と比べて変化を確認できるようにします。
 ここで、配列をコピーするのに「msw = switch_on.psw」とせずに、50行目でインポートしている「copy」ライブラリの「deepcopy」関数を使用して実行しています。
 前者の場合は「配列を共有する」事になるため「switch_on.psw」の要素を変更すると「msw」の同じ要素も変更され、逆に「msw」の要素を変更すると「switch_on.psw」の同じ要素も変更されてしまいます。一方、後者の場合は別の配列として全ての要素をコピーしますから一方の要素を変更しても、もう一方の要素は影響されません。

スイッチのエンコード・168行目:現在のスイッチ状態を8ビットの変数「u8」にパッキングしてROSのメッセージにエンコードしています。図のように、2ビットずつのスイッチ情報はx4倍していくことで上位のビットにパックすることが出来ます。

・169行目:ROSのメッセージ型「u8」にパッキングした最新のスイッチ状態を、142行目で作成した「pub」オブジェクトの「publish」メソッドを使用して配信しています。

・170行目:コメントにしていますが、配信したスイッチ状態をデバッグ時にログしています。「str」関数は、引数を文字列に変換して文字列を連結できるようにしています。

・172行目:143行目で作成した「rate」オブジェクトの「sleep」メソッドを使用してオブジェクト作成時に指定した10msの残り時間をこの関数で消費します。例えば、ループのコード実行に3msかかっていたとすると残りの7msを「sleep」関数が消費します。

・173行目:コメントにしていますが、ループの動作時間をログしてみると50ms程度ループの実行にかかっているため、10msの「sleep」関数は機能していませんでした。

・177行目:149行目の「try」文とセットになって、「try」ブロック内で発生した「Ctrl+C」をキャッチして以降のブロックを実行します。

・178行目:177行目の「except」ブロックを作成するために何も実行しない「pass」文を挿入しています。

・179行目:「Ctrl+C」の入力によってROSのノードが終了したことをログに表示しています。

・183行目:プログラムの終了の前に使用したGPIOポートを元に戻しておきます。


・79行目:スイッチの状態が変化した時に実行されるように122行目から125行目で設定した割り込み処理関数です。引数に変化したGPIOポート番号が保持されています。

・80行目:変化した対象のスイッチ状態をローカル変数の「csw」に取り込みます。

・81行目:変化した時刻をローカル変数の「ctm」に取り込みます。

・82行目:対象のスイッチをGPIOポート番号から配列のインデックスに変換し、ローカル変数の「n」に設定します。157行目でも記載しましたが、「IN_R_D」ポートから4つの入力ポートは連番である必要があります。

・83行目:コメントにしていますが、出力ポートに設定した「OT_DEB」ポートに割り込みの度に反転された信号をデバッグ出力します。

・84行目:グローバル変数「switch_on.psw」に保存されている前回のスイッチ状態と今回のスイッチ状態を比較し同じ状態だったら次のブロックを処理します。

・85行目:グローバル変数「switch_on.ptm」に現在の時刻だけ変更しておきます。

・86行目:コメントにしていますが、処理内容をデバッグ表示しています。

・87行目:スイッチ状態に変化が無かった時は、時刻情報だけ更新してこの「return」文で関数から抜けて元の処理に戻ります。

・88行目:スイッチ状態が変化した時は、先の変化から10ms経っていなければPWM信号と判断して、次のブロックを実行します。

・89行目:グローバル変数「switch_on.ptm」を現在の時刻に変更しておきます。

・90行目:グローバル変数「switch_on.psw」を「SW_PWM」状態変数に変更しておきます。

・91行目:コメントにしていますが、処理内容をデバッグ表示しています。

・92行目:関数から抜けて元の処理に戻ります。

・93行目:スイッチ状態が先の変化から10ms以上経っていれば、通常のスイッチ処理として80行目で取り込んだスイッチの状態を保存します。

・94行目:グローバル変数「switch_on.ptm」を現在の時刻に変更しておきます。

・95行目:コメントにしていますが、処理内容をデバッグ表示しています。

 コードの説明は、以上です。次に、簡単なテストをしてみましょう。

3.プログラムの実行とテスト: 目次に戻る

 それでは、作成した「RosRemoCon-new.py」の実行とテストを以前の「RotaryEncoder-new.py」で行った「3.プログラムの実行とテスト:」の手順と同じように行っていきます。

①「Windowsターミナル」から「roscore」の実行や4分割画面を作成して置いてください。

②4分割画面の左上の「端末」からラズパイ・ゼロにログインして置いてください。

③ラズパイ・ゼロの「端末」から「roscd rc_car/scripts/」と入力してスクリプト用ディレクトリに移動して下さい。

④4分割画面の右上の「端末」から「cd ~/Python/rc_car/raspi/」と入力して作成したプログラムのディレクトリに移動して下さい。

⑤Ubuntu18で作成したプログラムをラズパイ・ゼロにコピーします。右上の「端末」から「sput RosRemoCon-new.py」と入力して下さい。

⑥コピーしたファイルに実行権限を追加します。左上の「端末」で「chmod +x RosRemoCon-new.py」を実行して下さい。

⑦左下の「端末」からもラズパイ・ゼロにログインして「rosrun rc_car RosRemoCon-new.py」と入力して実行して下さい。エラー無く実行出来たでしょうか?

⑧左下の「端末」からは、138行目のログが「ROSノード[rc_remocon]を開始しました。」表示されたでしょうか?「rc_remocon」ノードからの表示は、以上です。

⑨ラジコン・カーに付属のリモコンの電源を入れて下さい。赤いLEDが点灯したら準備OKです。

⑩右下の「端末」から「rostopic echo /remocon_info」と入力して「Enter」を入力して下さい。

リモコンSPEEDY⑪リモコンのスイッチを操作して、右下の「端末」に表示されるメッセージを確認してみて下さい。
 図は、右のスイッチを上側に倒して「data: 4」が表示され、離して「data: 0」が表示されています。続けて、右のスイッチを下側に倒して「data: 1」が表示され、離して「data: 0」が表示されています。同じように、左スイッチの上で「data: 64」が、下で「data: 16」が表示されています。



リモコンSLOWLY⑫リモコンの右肩のスイッチを押して、電源LEDが赤色から緑色に変わればOKです。右下の「端末」に表示されるメッセージを確認してみて下さい。
 図のように、右スイッチの上で「data: 8」が表示され、右スイッチの下で「data: 2」が表示され、左スイッチの上で「data: 128」が、下で「data: 32」が表示されたでしょうか。



 「SPEEDY」モードは「1」に「SLOWLY」モードは「2」にエンコードしているので、表示が各々のスイッチで2倍になっていますね。

 以上で動作確認とテストを終了します。右下の「端末」と左下の「端末」に「Ctrl+C」を入力してプログラムを終了させてください。
 今回でラズパイ・ゼロ側の4つのプログラムは終了です。次回からはノートパソコン側のプログラムについて紹介していきます。



 目次に戻る

・・・