前回ROS2のインストールが完了しましたので、続いて、OSOYOO PI CARキットをROS2を使って走らせようと思います。今回は、下記の順番で進めようと思います。
各ノードの設計については、できるだけ簡潔に説明しようと思いますが、ROS2のお作法的なところは省略させていただきます。
- OSOYOO PI CARを動作させる前準備
OSOYOO PI CARはPCA9685 PWMライブラリを使用して駆動モーターを制御します。
PCA9685を使用するためにI2C、GPIOを有効化します。 - パッケージの準備
まずは、駆動系、テストノード設計に必要なパッケージを用意します。
OSOYOO PI CARのリソースがPythonですので、まずは、python用を用意します。 - 駆動系ノード
車輪を駆動して車を移動制御するノードです。 - テストノード
駆動系ノードのテストドライバです。
■ OSOYOO PI CARを動作させる前準備
OSOYOO PI CARはPCA9685 PWMライブラリを使用して駆動モーターを制御します。
PCA9685を使用するために必要なI2C、GPIOを有効化します。
〇 raspi-configの有効化
ラズパイのI2Cバスを有効にするためと、今後のデバイスの有効化/無効化を行う際にraspi-configを入れておくと楽なので、下記手順でUnbuntuに導入しておきます。
なお、この時シェルはbashを使用してください。
% bash $ sudo echo "deb http://archive.raspberrypi.org/debian/ buster main" >> /etc/apt/sources.list $ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 7FA3303E $ sudo apt-get update $ sudo apt-get install raspi-config $ sudo mount /dev/mmcblk0p1 /boot
〇 I2Cの有効化
I2Cを有効化するためにrspi-configを起動します。
$ sudo rspi-config
起動後に下記の手順でメニューを選択してI2Cバスを有効化します。
「Interfacing Options」→「I2C」→「Yes」→「Ok」→「Fiinish」
〇 gpio/pca9685 pythonライブラリ導入
車輪モーターの駆動に必要なライブラリ:GPIO/PCA9685を導入します。
$ sudo pip3 install adafruit-gpio $ sudo pip3 install adafruit-pca9685
■ パッケージの準備
いきなりパッケージという用語が出てきてわかりにくいと思いますが、ROS2はビルド/実行単位をパッケージ毎に行います。プログラムの実行単位(ノード)は、パッケージ内に複数作成することが可能です。このパッケージはプログラム言語毎に作成することになっています。(python用、C++用等々)
今回は、下記のようなパッケージとすることにしました。
| パッケージ名 | 言語 | 内容 |
| picar_py_pkg | python | python用 pi car パッケージ |
| picar_cpp_pkg | C++ | C++用 pi car パッケージ |
| picar_interfaces | – | picar制御に必要なメッセージ、サービス定義 |
| picar_bringup | python | launch設定 |
一応、パッケージは決めましたが、ルールを決めただけでパッケージが必要になった時点で順次作成していこうと思いますが、作成手順を記載します。
今回は、下記のような構成でROS2ワークスペースを構成します。
. ├── build ← ROS2環境生成時に自動生成される ├── install ← 同上 ├── log ← 同上 └── src ← ソースディレクトリ(緑字は、これから生成するパッケージ) ├── picar_py_pkg ├── picar_cpp_pkg ├── picar_interfaces └── picar_brinngup
ここでは、ros2_wsをROS2ワークスペースとして取り扱います。
パッケージ生成後に各言語毎におまじないをしなと使えません。生成時に説明しようと思います。
$ cd ros_ws
$ colcon build
$ mkdir src
$ ros2 pkg create --build-type ament_python picar_py_pkg
$ ros2 pkg create --build-type ament_cmake picar_cpp_pkg
$ ros2 pkg create picar_interfaces
$ ros2 pkg create bringup
■ 駆動系ノード
駆動系ノードは、pythonで作成することにしました。
強い理由はないですが、osoyooのリソースがpythonだったくらいの理由です。
picarの駆動制御IFは、外部ノードより「前進」「後退」「左回転」「右回転」の指示に従い、動作するように設計します。今回は、ROS2で標準的に用意されているメッセージgeometry_msgs/twistを使用します。このメッセージは下記のように定義されていますが、便宜上、picar制御用に読み替えて使用します。
| Twist メンバー | linear/angularメンバー | picar 制御 |
| linear | x | +方向:前進(0.0 – 1.0) -方向:後進(-1.0 – 0.0) 0:停止 数値は、前進/後退スピードを示す |
| y | 未使用 | |
| z | 未使用 | |
| angular | x | 未使用 |
| y | 未使用 | |
| z | z軸の回転時間を示す。 -方向:右方向の回転。(0.0 – 1.0) +方向:左方向の回転。(-1.0 – 0.0) 数値は、回転時間(秒)を示す。大体1秒で360度回転程度。 舵角制御したいので、指定時間が来たら停止。 |
具体的には、外部ノードが”/picar_vehicle/cmd_vel”という名称で発信するメッセージを駆動系ノードでキャッチして指示に従い動作するように設計します。
picar_vehicle:駆動系ノード(PicarVehicleNode)
#!/usr/bin/env python3
import rclpy
import time
from rclpy.node import Node
from Adafruit_PCA9685 import PCA9685
import RPi.GPIO as GPIO
from geometry_msgs.msg import Twist
class PicarVehicleNode(Node):
def __init__(self):
super().__init__("picar_vehicle")
# this get the paramter to set running mode.
self.declare_parameter("debug", False)
self.is_debug_ = self.get_parameter("debug").value
if self.is_debug_ == True:
self.get_logger().info("debug mode has been started")
else:
self.get_logger().info("native mode has been started")
# this configuration device
self.pwm_ = PCA9685(address=0x40) # PCA9685:address = 0x40
self.pwm_.set_pwm_freq(60) # 60hz for motor.
GPIO.setmode(GPIO.BCM) # is to specify GPIO No.
GPIO.setwarnings(False) # without warning.
self.left_backward_ = 23 # dir = backward.(GPIO23)
self.left_forward_ = 24 # dir = foward.(GPIO24)
self.right_backward_ = 27 # dir = backward.(GPIO27)
self.right_forward_ = 22 # dir = forward.(GPIO22)
self.en_left_ = 0 # PCA9685 left channel.
self.en_right_ = 1 # PCA9685 right channle.
# GPIOをOUTPUTモードに設定
GPIO.setup(self.left_backward_, GPIO.OUT)
GPIO.setup(self.left_forward_, GPIO.OUT)
GPIO.setup(self.right_backward_, GPIO.OUT)
GPIO.setup(self.right_forward_, GPIO.OUT)
# this starts cmd_vel subscription.
self.subscriber_ = self.create_subscription(
Twist, "picar_vehicle/cmd_vel", self.callback_cmd_vel, 10)
# this starts vehicle controller.
self.action_unit_ = 0.01
self.life_time_ = 2.0
self.x_val_ = 0.0
self.theta_val_ = 0.0
self.heart_beat_ = 0.0
self.vehicle_timer_ = self.create_timer(
self.action_unit_, self.vehicle_controller)
def callback_cmd_vel(self, msg):
self.get_logger().info("linear(" +
str(msg.linear.x) + "," +
str(msg.linear.y) + "," +
str(msg.linear.z) + ") angular(" +
str(msg.angular.x) + "," +
str(msg.angular.y) + "," +
str(msg.angular.z) + ")")
self.heart_beat_ = self.life_time_
self.theta_val_ = msg.angular.z
self.x_val_ = msg.linear.x
def vehicle_controller(self):
if self.heart_beat_ <= 0.0:
self.action_stop()
return;
self.heart_beat_ = self.heart_beat_ - self.action_unit_
if self.theta_val_ < -self.action_unit_:
self.theta_val_ = self.theta_val_ + self.action_unit_
if self.is_debug_ == True:
self.get_logger().info("move right:" + str(self.theta_val_));
self.action_left(self.theta_val_)
return
elif self.theta_val_ > self.action_unit_:
self.theta_val_ = self.theta_val_ - self.action_unit_
if self.is_debug_ == True:
self.get_logger().info("move left:" + str(self.theta_val_));
self.action_right(self.theta_val_)
return
if self.x_val_ > self.action_unit_:
if self.is_debug_ == True:
self.get_logger().info("move forward");
self.action_forward(self.x_val_)
elif self.x_val_ < -self.action_unit_:
if self.is_debug_ == True:
self.get_logger().info("move backward");
self.action_backward(self.x_val_)
else:
self.action_stop()
def action_forward(self, speed):
if speed > 1.0:
speed = 1.0
GPIO.output(self.left_backward_, GPIO.LOW)
GPIO.output(self.left_forward_, GPIO.HIGH)
GPIO.output(self.right_backward_, GPIO.LOW)
GPIO.output(self.right_forward_, GPIO.HIGH)
self.pwm_.set_pwm(self.en_left_, 0, int(speed * 4095))
self.pwm_.set_pwm(self.en_right_, 0, int(speed * 4095))
def action_backward(self, speed):
if speed < -1.0:
speed = -1.0
GPIO.output(self.left_backward_, GPIO.HIGH)
GPIO.output(self.left_forward_, GPIO.LOW)
GPIO.output(self.right_backward_, GPIO.HIGH)
GPIO.output(self.right_forward_, GPIO.LOW)
self.pwm_.set_pwm(self.en_left_, 0, int(-speed * 4095))
self.pwm_.set_pwm(self.en_right_, 0, int(-speed * 4095))
def action_stop(self):
GPIO.output(self.left_backward_, GPIO.LOW)
GPIO.output(self.left_forward_, GPIO.LOW)
GPIO.output(self.right_backward_, GPIO.LOW)
GPIO.output(self.right_forward_, GPIO.LOW)
self.pwm_.set_pwm(self.en_left_, 0, 0)
self.pwm_.set_pwm(self.en_right_, 0, 0)
def action_right(self, speed):
if speed > 1.0:
speed = 1.0
GPIO.output(self.left_backward_, GPIO.LOW)
GPIO.output(self.left_forward_, GPIO.HIGH)
GPIO.output(self.right_backward_, GPIO.HIGH)
GPIO.output(self.right_forward_, GPIO.LOW)
self.pwm_.set_pwm(self.en_left_, 0, int(speed * 4095))
self.pwm_.set_pwm(self.en_right_, 0, int(speed * 4095))
def action_left(self, speed):
if speed < -1.0:
speed = -1.0
GPIO.output(self.left_backward_, GPIO.HIGH)
GPIO.output(self.left_forward_, GPIO.LOW)
GPIO.output(self.right_backward_, GPIO.LOW)
GPIO.output(self.right_forward_, GPIO.HIGH)
self.pwm_.set_pwm(self.en_left_, 0, int(-speed * 4095))
self.pwm_.set_pwm(self.en_right_, 0, int(-speed * 4095))
def main(args=None):
rclpy.init(args=args)
node = PicarVehicleNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
| PicarVehicleNode メソッド | 機能概要 |
| __init__ | PicarVehicleNodeの初期化を行う。 PCA9685(PWMコントローラ)、GPIOのセットアップ後サブスクリプション(callback_cmd_vel)、駆動制御(vehicle_controller)を起動します。 |
| callback_cmd_vel | “/picar_vehicle/cmd_vel”サブスクリプションを行います。 受け取ったメッセージを駆動制御値に変換します。 |
| vehicle_controller | 駆動制御値に従い、picarを前進、後退、左回転、右回転、停止します。 それぞれの制御には以下のメソッドを呼び出して制御します。 |
| action_forward | 指定されたspeedで前進します。 |
| action_backward | 指定されたspeedで後退します。 |
| action_stop | picarを停止します。 |
| action_right | picarを右回転します。 |
| action_left | picarを左回転します。 |
■ テストノード
駆動系ノードIFにgeometry/twistを使用しましたので、テストノードは手を抜いてROS2標準搭載のノード(teleop_twist_keyboard)を下記のように実行します。
ros2 runはノードの実行を行います。
下記は、「teleop_twist_keyboardパッケージのteleop_twist_keyboardノードを実行し、twistメッセージは、”/picar_vehicle/cmd_vel”で送信する。」ということなります。
あとは、キー操作でpicarが意図通りに動けば、駆動系ノードの動作確認完了です。
$ ros2 run teleop_twist_keyboard teleop_twist_keyboard cmd_vel:="/picar_vehicle/cmd_vel" Moving around: u i o j k l m , . For Holonomic mode (strafing), hold down the shift key: --------------------------- U I O J K L M < > t : up (+z) b : down (-z) anything else : stop q/z : increase/decrease max speeds by 10% w/x : increase/decrease only linear speed by 10% e/c : increase/decrease only angular speed by 10% CTRL-C to quit currently: speed 0.5 turn 1.0
■ 動作確認
駆動系ノードの動作確認を一通った様子です。
ほぼ、期待通りに動作することを確認できました。
■ 次回
次回は、動画配信のノードを試行していこうと思います。
また、よろしければ見ていただけると嬉しいです。

