カエデ自動機械

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

倒立振子ロボットを作る(5)-ソフトの全体構造とマイコンとのI/Fノード

ソフトの全体構造のイメージ

若干順番が前後したような気もしましたが、倒立振子ロボットを作る(2)-姿勢制御とそのためのパーツ - 技術開発日記@KTDの記事で、Arduino互換のマイコンであるLambda Plusを使って、姿勢角を取得できるようになりました。
このままLambda Plusに遠隔操縦指令を受け付ける機能とか障害物を回避する機能とかを付け加えていくことも可能だとは思うのですが、最初に述べた通り、「ROSを使ってみたい」という副目標のようなものがありますし、上位計算機としてUbuntuが動いているような大きなマシンがあるのは、重い処理を受け持たせられるというメリットもあります。
せっかく持っている近藤科学サーボモータ+ROS対応ドライバを使ってみたいというのもありますが。

そんなわけで、
Lambda Plus:

  • 姿勢角検出
  • 上位PCへ転送
  • その他、LEDや簡単なセンサのインターフェースとしての機能


上位PC

  • 姿勢角に基づくモータ制御(サーボのROSパッケージ)
  • ジョイスティックやその他環境認識結果などからの速度指令生成、モータ制御への添加
  • カメラなどの高度なセンサとのインターフェース

みたいな感じで役割分担をさせることにしました。

Lambda PlusとROSのインターフェース

いきなり悲しい話ですが、本来であればArduino自体がROSのノードとして動くはずなのですが、残念ながら僕には上手くできませんでした。
メモリ(?)がいっぱいですみたいなことを言われて怒られてしまい、まともに書き込めない。


Arduino IDEの設定ファイル?みたいのを書き換えてメモリ空き容量を増やすことはできたけど、安定稼働には至らず。
ROSノードとして動くPythonスクリプトに、普通にシリアル通信で送る形を取ることにしました。


インターフェースノードに求められる機能

  • Lambda Plusにデータ送信をリクエストする

高度な同期処理や、通信バッファに溜まったデータを処理する自信がないため、1回PCからリクエスト→1回Lambdaから返信の繰り返しにする

  • Lambda PlusからIMUデータを受け取り、適宜変換してROSメッセージとしてパブリッシュ


とこれだけです。簡単。
ホイールオドメトリのためのエンコーダデータはサーボ制御ノードから取りますので、このノードが処理する情報はIMU関係のみ。
IMU関係データは今の所モータへの指令値を決める制御ノードだけの予定ですが、折角なのでsensor_msg/Imu型のメッセージでパブリッシュすることにしました。


ROSのImu型メッセージはクオータニオンで姿勢角を表現しますが、デバッグの時とか、手軽に車体の傾き角を見られるよう、オリジナルのPosture_angleというメッセージを定義して、オイラー角をパブリッシュしています。
その他、なぜこの書き方にしたのか覚えていない部分も結構あるのですが、一応コードも上げてみようと思います・・・

実際のコード

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# code for python2.7

#
# this node is a interface node to arduino
# it collects IMU data from arduino.
#

import rospy
import serial
import time
import signal
import sys
import tf
import math
from sensor_msgs.msg import Imu

# message for publishing auler angle, so that I can recognize robot's posture intuitively
from whipbot.msg import Posture_angle

# start serial communication
# using symbolic link, Lmabda will recognized as "/dev/Lambda" whereever/whenever port you connect
ser = serial.Serial('/dev/Lambda', 115200)

# prepare command to request data send to Lambda Plus
# I forgot why I write as bellow?
send_command = []
send_command += [chr(1)]

# prepare data list sent from Lambda Plus
data = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]


def get_data_from_Lambda():  # function to receive IMU data from Lambda. Executed at 100 Hz by ROS node
    # reset input buffer
    ser.reset_input_buffer()

    # send data request to Lambda
    ser.write(send_command)

    # wait until the reply has reached to this node (36 must be inappropriate...)
    while ser.inWaiting() < 36:
        pass

    # i:0 to 8, roll, pitch, heading, accelX,Y,Z, gyroX,Y,Z
    for i in range(9):
        data[i] = ser.readline()  # read data line by line
        # remove return code and end point null
        data[i] = data[i].replace('\r\n', '')
        data[i] = float(data[i])  # convert into float type parameter

    # contain into ROS message
    posture.roll = data[0]
    posture.pitch = data[1]
    posture.heading = data[2]

    # transform posture angle[deg] to [radian]
    for i in range(3):
        data[i] = data[i] * math.pi / 180.0

    # transform posture from euler to quaternion
    posture_angle_quaternion = tf.transformations.quaternion_from_euler(
        data[0], data[1], data[2])

    # contain to the message : /imu
    imu_data.orientation.x = posture_angle_quaternion[0]
    imu_data.orientation.y = posture_angle_quaternion[1]
    imu_data.orientation.z = posture_angle_quaternion[2]
    imu_data.orientation.w = posture_angle_quaternion[3]

    # transform to [m/s^2] from [g] and contain to the message : /imu
    imu_data.linear_acceleration.x = data[3] * 9.81
    imu_data.linear_acceleration.y = data[4] * 9.81
    imu_data.linear_acceleration.z = data[5] * 9.81

    # transform to [rad/sec] from [deg/sec] and contain to the message : /imu
    imu_data.angular_velocity.x = data[6] * math.pi / 180
    imu_data.angular_velocity.y = data[7] * math.pi / 180
    imu_data.angular_velocity.z = data[8] * math.pi / 180


if __name__ == '__main__':
    rospy.init_node('get_sernsor')  # initialize node

    posture_angle_pub = rospy.Publisher(
        'posture_angle', Posture_angle, queue_size=1)  # define auler angle publisher

    # define /Imu publisher
    imu_pub = rospy.Publisher('/imu', Imu, queue_size=1)

    # declare publisher
    posture = Posture_angle()
    imu_data = Imu()
    imu_data.header.frame_id = 'map'
    # you should wait for a while until your Lambda Plus is ready
    time.sleep(5)

    # set the loop rate at 100Hz
    rate = rospy.Rate(100)

    while not rospy.is_shutdown():
        try:
            get_data_from_Lambda()  # function to get data from Lambda
            # publish posture angle at auler angle
            posture_angle_pub.publish(posture)
            imu_pub.publish(imu_data)  # publish as sensor_msgs/Imu

        except IOError:
            pass

        rate.sleep()

次回は、今回実装したノードがパブリッシュしたsensor_msgs/Imuデータをサブスクライブして、その情報に基づいてモータを制御するノードを実装していきます。
ktd-prototype.hatenablog.com