カエデ自動機械

ちょっとしたものづくりや電子工作のメモなど。技術開発とは今は呼べないかな。

自律ローバーを作る(番外1)-失敗集など

自律ローバー番外編です。
自律ローバーを作っていて、うっかりミス等によりハマったことを、敗北の歴史として残します。

見出しは、発生原因とは無関係な、「発生時点に自分がやっていたこと」を書くようにしています。
結果的に、「何もしていないのに壊れた」的な間抜けさが出て気に入っています。

機構・構造

ホイールマウントが歪む

モータ出力軸とホイールは3Dプリンタ製のマウントで繋いでおります。

モータ出力軸はΦ6mmのDカット軸になっており、3Dプリンタで作ったマウントはボス部分をネジで締めることでモータの出力軸に固定し、ホイール側とはフランジ部でボルト留めする形状としました。

f:id:ktd-prototype:20200421001208j:plain
ホイールマウント部品。ボス部分(というのかな?)のネジを締めることでモータ出力軸をクランプする。

が、おそらくボス部分がモータ出力軸をクランプする際に歪み、それがフランジ側まで伝わってしまっている様子。
モータを回転させると、進行方向から見た時に明らかにホイールの動きが歪んでいました。幸い偏心はしていない様子ですが、推進効率は若干落ちると思われます。

この問題は倒立振子ロボットの時代から発生しており、この形状である限り特に解決策も思いついておりません。
マウントの部材がもっと柔らかい(もっと硬い?)ものであれば、留めネジ締め付けによる歪みがフランジにまで伝播しなくてすむのでしょうか。

現在は、ボス部分とフランジ部分を別体の部品にしようと目論んでいます。
ボス分は純正の部品がモータのメーカであるServoCityから出ていますので、こちらを使います。クランプのネジだけがインチネジなので要注意です。
www.servocity.com

上記の部品と、ホイール側と接続するためのフランジ部品を、上記の部品の4つのネジ穴(こっちはメートルねじ・・・)で固定すれば、多少は改善する気がします。



回路、電装部品

JetsonNanoとArduinoのUSBプラグ干渉

ありがちなミスで、ロボットのサイズを一生懸命小型化した挙句、無邪気にJetsonNanoとArduinoを配置したところ、互いのUSBコネクタが向かい合う形となりプラグが干渉することになりました。

配置向き自体は変えたくなかったので、それぞれのマウント部品の形状を変更し、プラグがぶつからないようスペースを確保しました。
このため、JetsonNanoは若干車体からはみ出して搭載されています(当初想定のルンバサイズのシルエットにはまだ収まっていますが)

フルカラーLEDが意図通り光らない(その1)

ロボットの状態(走行モード、バッテリ残量等)を示すためにフルカラーLEDを2個搭載しています。

LEDは以下の2種類を所持しており、参考になりそうな事例も見つかったことからPL9823-F5の方を使おうと思っておりました。
マイコン内蔵RGB 5mmLED PL9823−F5: LED(発光ダイオード) 秋月電子通商-電子部品・ネット通販
マイコン内蔵RGB 5mmLED OST4ML5B32A: LED(発光ダイオード) 秋月電子通商-電子部品・ネット通販

参考にした事例:
toboli.hatenablog.com

ここでちょっと私が勘違いしてしまったのですが、上記サイト様にあるLEDの模式図が、4本の足の長さのバランス(1が長く、2と3が中間、4が短い)的にOST4ML5B32Aっぽく見えるんですね。

現物には型番等書いてないので、上記サイト様の模式図と現物を見比べ、同じような足の長さ構成を持つLEDがPL9823-F5だと勘違いしてしまいました。

実際のPL9823-F5は秋月電子様のサイトにあるように、「2本が長く、2本が短い」ものです。

私の手元では、

  • OST4ML5B32Aを(PL9823-F5だと思って)実装する
  • 上記サイト様を参考にPL9823-F5用のArduinoコードを実装する

というミスを犯し、LEDが意図通り光らない事象がありました。

きっかけは忘れましたが首尾よく途中で気づくことができたため、事なきを得ました。

上記サイト様も間違ったことを書いているわけではなく(ピンの機能も順番通りだし、コードもちゃんと動く)、模式図をFritzingから取ってくる時の都合だったのだと思います。
参考にする側がよく確認するべきでした。


フルカラーLEDが意図通り光らない(その2)

上記サイト様を参考に、PL9823-F5は

pixels.clear();

というArduinoコードで消灯できそうと思ったのですが、当初は消灯させられず、代わりに

pixels.setPixelColor(i, pixels.Color(0,0,0)); //すべてゼロを指定
pixels.show(); 

を使って消灯していました。

実際は上記サイト様の記載を見逃しており、

pixels.clear();
pixels.show(); 

と、showコマンドとセットで初めてコマンドが有効になるようです。見落としていました。

フルカラーLEDが意図通り光らない(その3)

部品代をケチるために、回路にはんだ付けをする場合、ピンソケットを介して行うことが多いです。
ピンソケットのみをはんだ付けして、費用がかさむセンサ類はピンソケットに挿すだけ、後で再利用できるように。

ただ、使用している以下のピンソケットと上記PL9823-F5の相性がそんなに良くない(PL9823-F5の足が細すぎる?)ようで、接触不良が多かったです。

PL9823-F5はそれほど高価でもないので、直接基板にはんだ付けすることにしました。
ピンソケットが悪いわけではないですが、ピンソケットとピンの相性には要注意でした。


ソフト

Arduino側でモータドライバへのコマンドが異常値を吐く

Arduinoがモータドライバに送る指令値はPC側の速度制御ノードで決め、シリアル通信で他の情報とともに送信しています。
ところが、右のモータへの指令値だけ急に−200とか300とかを突然出し始める。最大値は300にセットしていて、普段は30程度しか使っていないのだから、かなりの異常値ぶりであり、モータの挙動も露骨におかしくなります。

これは、PC→Arduinoの通信が5バイトを送っている(ヘッダ1、左モータ指令値2、右モータ指令値2)にも関わらず、5バイトを受信し切る前にそれらのデータをドライバへの指令値用変数に格納してしまうことが原因のようでした。

結果的に、最も受信し損ねやすいであろう最後のコマンドが異常値になっていたようです。
受信バッファに5バイト以上格納されているときのみ、受信処理を開始するようにArduinoコードを書き換えることで、解決しました。

ただ、指令値用変数は0で初期化してあり、データが来ていないとしてもそうおかしな値が入るものでもないと思うのですが、そこだけが疑問です。

そして、後述の理由により、この解決策は不採用となり、指令値が大きく飛んだら異常値として弾く、という力技での解決となりました。


シリアル通信中にArduinoがフリーズする

当初調子の良かったArduinoが、ある日突然、PCとの通信開始後10秒くらいでフリーズするようになりました。
シリアル送受信のインジケータLEDも消灯しています。

当初は

  • PC側が要求する100Hzでの送受信にArduino側の処理が追いついていない
  • I2C通信と、エンコーダ情報を取得する割り込み処理の干渉(Arduino シリアル通信 フリーズ とかでググったら出てきた)

辺りを疑っていました。

そこで一度自作のPythonコードとのシリアル通信は切って、同様の情報を機械的にシリアルで垂れ流すコードにしたところ、どうやら問題はなさそう。

IMU(I2C接続)の情報取得に2ms、ArduinoからPCにシリアル送信するのに2ms(@230400bps)程度かかっており、100Hzの通信は、余裕があるとは言えないかも知れませんが不可能ではない様子。

この間、エンコーダ情報もIMU情報も正常通り取れており、干渉の線も無さそう。


もう一度現象をよくよく観察しようと、自作Pythonコードと接続して、PC側のシリアル受信バッファを監視してみると、フリーズする際は必ず、受信済みバイト数が0でした。

ArduinoはPCから前述の5バイトを受信・変数格納したら、直ちにその時点でのエンコーダ、IMU、バッテリ電圧を送り返すようにしていますので、1バイトもPC側に来ていないということは、Arduino側の受信処理が完了できていないのでしょう。

どのタイミングで止まっているのかわからず、一方で良い試験方法も思いつかなかったので、オンボードのLEDのON/OFFをArduinoコードのあちこちに仕込み、フリーズした時点でのオンボードLEDのON/OFFで判断することにしました。

結果的には、そもそも5バイトがPCから来ず、ArduinoはPCからの通信を待ちながらひたすらエンコーダやIMUのデータ取得を行っている様子でした。
最初の10秒位は普通に動くので、特段変な部分があるとも思えないのですが・・・

試しに、前項で改善した「5バイトがPCから来るのを確実に待つ」部分を、1バイトずつ減らしていくと、フリーズするまでの期間が伸び、2バイトを待つことにしたらフリーズしなくなりました。

結局、Arduino側の受信バッファにPCから2バイト以上データが届いていたら、見切り発車でArduino側の受信→格納→PCにセンサデータを返信 の処理を行うのが現行のコードとなっています。

奇妙なのは、フリーズ現象が起こっている以上、PCから4バイト以下しか届いていないサイクルが必ずあるはずなのですが、その時のモータ指令値がおかしいかと言うと、そうでもないんですね。ここはよくわかりませんでした。

とはいえ、たまにPCからの受信データが異常になるのは確かなので、その際は素直に弾いています。
現在は自律ローバーの動作モードを加えた6バイトをPCから受け取っていますが、異常値はやはり後半の4バイト、モータ指令値の部分のようで、弾くのは簡単でした。

ちなみに、シリアル通信の通信速度を下げたり、Arduino側の通信処理開始直後にDelayを入れると異常値を吐く頻度が減るようでした。

どのみち異常値を弾いたり、加速度の制限を行っていますので、大きなメリットも無いかと思い、通信速度は230400bpsのまま、念の為0.2msだけ、オマジナイとしてDelayを入れています。

ROSのteleop_twist_joyパッケージでenableボタンが上手く使えない
モータの速度制御(PID制御)のゲイン調整が上手く行かない
コードをクラス化したらArduinoとのシリアル通信が繋がらなくなった

ROSが走るPCとArduinoは、ROS系の外で(ROSノード内に埋め込まれた普通のPythonコードによって)シリアル通信をしています。

さて、Arduinoとの通信役を担うコードを当初はメイン関数ベタ打ち(さすがに処理の関数化はしますが)していたところ、途中でクラスを使うように変更しました。

この際、何故かArduinoとのシリアル通信が繋がらなくなりました。厳密には、送信動作はできているのだけれど、Arduinoから返事が来ない。
前のコードに戻せば、普通に通信はできる。Arduinoに送っているデータは一言一句同じ。

コンストラクタ内でシリアルポートを開けて通信を確立する操作も、間違っては無さそう。ネットで見つけた複数の記事を参考にしているし、ポートオープン時も送信時エラーも吐いていないし。

これは本当にコードをジッと見つめるしかなくて、結果、奇跡的に気づけたのですが、ポートオープン後、直ちに通信を始めていたのが間違いだったらしい。
ポートオープン後、1秒待ったところ、普通に通信ができるようになった。

当初のコード

# start serial communication with arduino
self.serial = serial.Serial('/dev/MEGA#1', 230400)

self.serial.reset_input_buffer() # flush the buffer of the port
self.send_data() # function to send data

上記の一行空きの部分に

time.sleep(1)

を挿入することで、通信が始まるようになりました。
実際は10msくらい待てば十分かも知れませんが。

クラス化する前は、別の理由でArduinoとの通信を始めるまで5秒位待つようにしていたんですね。(Arduino側で、IMUの初期化等に時間がかかっていため)

一方、Arduinoは電源投入直後から処理を開始しているわけで、わざわざPC側で処理を走らせてから更に待ってやる必要は無いだろうと、当該部分を削除した結果、このようなことになりました。


ちなみに、クラス化によりメリットがあったわけではなかったです。
クラスを使っている、グローバル変数が減った、等の自己満足感を得るためにやったことでした。ただ、少しクラスの扱いに慣れた気がしました。

別件でC++でもクラスの継承等をやっているので、Pythonでももっと自在に扱えるようになれば良いのですが。