自作のROS対応ロボットを制作しよう！

# 本記事の目的

rosserialを使用してArduinoとROSの通信を行い、簡易的なROS対応ロボットを作成します。

# 環境
本記事は以下の環境で実験しています.

| 項目 | バージョン |
|:-:|:-:|
| Ubuntu | 18.04 |
| ROS | Melodic |

## ロボットの制作

材料と機具は以下のものを使用しました。

- [Jetson nano 4GB](https://www.amazon.co.jp/NVIDIA-Jetson-Nano-Development-Kit/dp/B085NQTKS9/ref=sr_1_1_sspa?dchild=1&keywords=jetson+nano&qid=1619190240&s=computers&sr=1-1-spons&psc=1&smid=A3A9CLKQ7Y9T2V&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUFWRzRaM0pWRkwwWlEmZW5jcnlwdGVkSWQ9QTA4MDM3MTYzUVY2RjRaQU1SS0JIJmVuY3J5cHRlZEFkSWQ9QTFONEJNVEFYWjAzSjYmd2lkZ2V0TmFtZT1zcF9hdGYmYWN0aW9uPWNsaWNrUmVkaXJlY3QmZG9Ob3RMb2dDbGljaz10cnVl)
- [RPLIDAR A1M8](https://www.amazon.co.jp/-/en/RPLiDAR-Technology-Navigation-Environmental-Scanning/dp/B07J9HSWJ6/ref=dp_prsubs_1?pd_rd_i=B07J9HSWJ6&psc=1)
- [Arduino Mega 2560](https://www.amazon.co.jp/ELEGOO-MEGA2560-ATmega2560-ATMEGA16U2-Arduino/dp/B06Y4KZV1J/ref=sr_1_1_sspa?dchild=1&keywords=Arduino+mega&qid=1622997734&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEyUklZUE9OQjFCSTJVJmVuY3J5cHRlZElkPUEwNTIyOTMzREtNVVZBREFJRFZXJmVuY3J5cHRlZEFkSWQ9QTJVUVhKNlhaSjZVVlMmd2lkZ2V0TmFtZT1zcF9hdGYmYWN0aW9uPWNsaWNrUmVkaXJlY3QmZG9Ob3RMb2dDbGljaz10cnVl)
- [DC Motor with Encoder](https://osoyoo.store/products/dc-motor-robotic-car-speed-encoder-9v-for-arduino-raspberry-pi-platform-diy?variant=31930889830511)
- [Moter Driver](https://osoyoo.store/collections/arduino-robot-car-v2-0/products/osoyoo-model-x-motor-driver-module-for-arduino-v2-0-robot-carmodel-2018000800?variant=31648878755951)
- [アクリル板](https://www.akurirukoubo.com/shopbrand/ct123)
- [Jetson nano バッテリー](https://www.amazon.co.jp/gp/product/B07YRK5BCF/ref=ppx_yo_dt_b_asin_title_o02_s01?ie=UTF8&psc=1)
- [モータドライバ　バッテリー](https://item.rakuten.co.jp/k-power/ac18650-002/)
- [Ball Casters](https://www.amazon.co.jp/gp/product/B001VZHRW2/ref=ppx_yo_dt_b_asin_title_o04_s00?ie=UTF8&psc=1)
- [Bolt Set](https://www.amazon.co.jp/gp/product/B07CNDZB9C/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&psc=1)
- [小型Spacer](https://www.amazon.co.jp/gp/product/B07LBCDG68/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1)
- [大型Spacer](https://www.amazon.co.jp/gp/product/B0166GCHD4/ref=ppx_yo_dt_b_asin_title_o02_s01?ie=UTF8&psc=1)
- [Electric Drill Driver](https://www.amazon.co.jp/-/en/Protection-Overheating-Discharge-Accessories-Guaranteed/dp/B0953SCNGS/ref=sr_1_40?dchild=1&keywords=%E9%9B%BB%E5%8B%95%E3%83%89%E3%83%AA%E3%83%AB&qid=1622998206&sr=8-40)

以下のように寸法と穴の位置を決めていきます。今回は三段構造のロボットなので三枚分穴の位置を決めます。

<table>
<tr>
<td><img src="https://user-images.githubusercontent.com/52307432/120931834-41207000-c72e-11eb-8ac3-cd694d52d416.jpg" width="200"></td>
<td><img src="https://user-images.githubusercontent.com/52307432/120931966-d459a580-c72e-11eb-90d7-0026b8bcbd49.jpg" width="200"></td>
<td><img src="https://user-images.githubusercontent.com/52307432/120931976-e5a2b200-c72e-11eb-8f05-fa093763fdb6.jpg" width="200"></td>
</tr>
</table>

穴の位置を電動ドリルで開けてねじでそれぞれの部品を固定します。

以下の画像が一段目になります。

<table>
<tr>
<td><img src="https://user-images.githubusercontent.com/52307432/120932098-72e60680-c72f-11eb-82be-aea39b37d6bd.jpg" width="300"></td>
<td><img src="https://user-images.githubusercontent.com/52307432/120932135-98731000-c72f-11eb-9bbc-7560f2779562.jpg" width="300"></td>
</tr>
</table>

左画像が二段目、右画像が三段目になります。

<table>
<tr>
<td><img src="https://user-images.githubusercontent.com/52307432/120932088-6366bd80-c72f-11eb-9f03-ac49fd1a6ea6.jpg" width="300"></td>
<td><img src="https://user-images.githubusercontent.com/52307432/120932063-3fa37780-c72f-11eb-852a-751945fbbfdc.jpg" width="300"></td>
</tr>
</table>

最終的に上記三段を組み合わせて以下のようなロボットを組み立てます。

|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|![Qiita](https://user-images.githubusercontent.com/52307432/120932298-2bac4580-c730-11eb-8b14-66a66b8db855.jpg "Qiita")|

## Jetson nanoの設定
以下の記事を参考に進めてください。

https://qiita.com/MMM-lab/items/04431add6210c80d3462

## Arduino IDEのインストール
今回モータ制御にはArduinoを使用するため、ArduinoとROSの間で通信を行えるrosserialを使用できる環境を構築します。

ますはIDEをこちらからダウンロードします。

https://www.arduino.cc/en/Main/Software

Linux 64 bitsリンクをクリックすると、**arduino-1.8.13-linux64.tar.xz**がダウンロードできます。

以下のコマンドに従って、展開とインストールを行います。

```bash
$ cd ~/Download
$ tar Jxfv arduino-1.8.13-linux64.tar.xz
$ mv arduino-1.8.13 /home/user/
$ cd arduino-1.8.13
$ sudo ./install.sh
```
```bash
[sudo] password for ramune: 
Adding desktop shortcut, menu item and file associations for Arduino IDE...

done!
```
## Arduino IDEにrosserialインストール
以下のコマンドを順に実行することでIDEをインストールする事が出来ます。

```bash
$ sudo apt-get install ros-melodic-rosserial
$ sudo apt-get install ros-melodic-rosserial-arduino
$ cd arduino-1.8.13/libraries/
$ rosrun rosserial_arduino make_libraries.py .
```
## Arduino rosserial 動作確認
以下のコマンドを実行することでArduinoとROSを接続します。問題なく接続出来れば成功です！

```bash
$ sudo chmod 666 /dev/ttyACM0
$ rosrun rosserial_python serial_node.py /dev/ttyACM0 _baud:=115200
```

## ROSとArduinoの通信を行う
それでは実際にROSとArduino間で通信を行ってロボットを動かしてみましょう！今回はROS側からcmd_velを受け取って、それをPWMに変換してモーターに加えるという一番シンプルな動作を行おうと思います。

以下Arduinoで使用するcodeです。

```cpp
#include <ros.h>
#include <ArduinoHardware.h>
#include <geometry_msgs/Twist.h>

/***********************************************************************
 * Macro
 **********************************************************************/
#define EN_L 12
#define IN1_L 7
#define IN2_L 8

#define EN_R 11
#define IN1_R 5
#define IN2_R 6

/***********************************************************************
 * Global variables
 **********************************************************************/
double w_r = 0.0;
double w_l = 0.0;

//wheel_rad is the wheel radius ,wheel_sep is
double wheel_rad = 0.03;
double wheel_sep = 0.32;

double speed_ang = 0.0;
double speed_lin = 0.0;

/***********************************************************************
 * Function
 **********************************************************************/
void messageCb( const geometry_msgs::Twist& msg){
  speed_ang = msg.angular.z;
  speed_lin = msg.linear.x;
  w_r = (speed_lin/wheel_rad) + ((speed_ang*wheel_sep)/(2.0*wheel_rad));
  w_l = (speed_lin/wheel_rad) - ((speed_ang*wheel_sep)/(2.0*wheel_rad));
}

// ROS
ros::NodeHandle nh;
ros::Subscriber<geometry_msgs::Twist> sub("cmd_vel", &messageCb );

/***********************************************************************
 * prottype
 **********************************************************************/
void Motors_init();
void MotorL(int Pulse_Width1);
void MotorR(int Pulse_Width2);

void setup(){
  Motors_init();
  nh.initNode();
  nh.subscribe(sub);
}

void loop(){
  MotorL(w_l * 30);
  MotorR(w_r*  30);
  nh.spinOnce();
}

/***********************************************************************
 * Function
 **********************************************************************/
void Motors_init(){
    pinMode(EN_L, OUTPUT);
    pinMode(EN_R, OUTPUT);
    pinMode(IN1_L, OUTPUT);
    pinMode(IN2_L, OUTPUT);
    pinMode(IN1_R, OUTPUT);
    pinMode(IN2_R, OUTPUT);
    digitalWrite(EN_L, LOW);
    digitalWrite(EN_R, LOW);
    digitalWrite(IN1_L, LOW);
    digitalWrite(IN2_L, LOW);
    digitalWrite(IN1_R, LOW);
    digitalWrite(IN2_R, LOW);
}

void MotorL(int Pulse_Width1){
 if (Pulse_Width1 > 0){
     if (Pulse_Width1 > 240) {
        Pulse_Width1 = 240;
     }

     analogWrite(EN_L, Pulse_Width1);
     digitalWrite(IN1_L, LOW);
     digitalWrite(IN2_L, HIGH);
 }

 if (Pulse_Width1 < 0){
     Pulse_Width1=abs(Pulse_Width1);
     if (Pulse_Width1 > 240) {
        Pulse_Width1 = 240;
     }

     analogWrite(EN_L, Pulse_Width1);
     digitalWrite(IN1_L, HIGH);
     digitalWrite(IN2_L, LOW);
 }

 if (Pulse_Width1 == 0){
     analogWrite(EN_L, Pulse_Width1);
     digitalWrite(IN1_L, LOW);
     digitalWrite(IN2_L, LOW);
 }
}

void MotorR(int Pulse_Width2){
 if (Pulse_Width2 > 0){
     if (Pulse_Width2 > 240) {
        Pulse_Width2 = 240;
     }

     analogWrite(EN_R, Pulse_Width2);
     digitalWrite(IN1_R, LOW);
     digitalWrite(IN2_R, HIGH);
 }

 if (Pulse_Width2 < 0){
     Pulse_Width2=abs(Pulse_Width2);
     if (Pulse_Width2 > 240) {
        Pulse_Width2 = 240;
     }
     
     analogWrite(EN_R, Pulse_Width2);
     digitalWrite(IN1_R, HIGH);
     digitalWrite(IN2_R, LOW);
 }

 if (Pulse_Width2 == 0){
     analogWrite(EN_R, Pulse_Width2);
     digitalWrite(IN1_R, LOW);
     digitalWrite(IN2_R, LOW);
 }
}
```

やっている事は非常にシンプルで、ROS側から受け取ったcmd_velをコールバック関数内で左右の角速度に変換し、loop内でその値に一定値をかけてモーターにPWMを印加しています。モーターに負荷がかかりすぎないように、今回はPWMを240でサチュレーションしています。ピン設定などは使用するモーター環境に合わせてください。基本的にはピンをきちんと指定すれば問題なく動かせると思います。

## 動作確認
まずは上記のcodeをArduinoに書き込みます。

次にjetson nanoとssh接続します。ssh接続を行うにはIPアドレスとユーザー名とPWが必要なので事前に控えておいてください。

```bash
ssh ユーザー名@192.168.xxx.xxx
```

成功したら、以下のコマンドを順に実行して動作確認を行います。

```bash
$ sudo chmod 666 /dev/ttyACM0
$ rosrun rosserial_python serial_node.py /dev/ttyACM0
$ rosrun teleop_twist_keyboard teleop_twist_keyboard.py
```

以下のように八方向移動とその場旋回が出来れば成功です！(あまりきれいではありませんが・・・（笑）)

![Videotogif (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628764/d5ad3d55-1f6a-7810-9201-8df09f4797d8.gif)

# まとめ
- 詳細な設計はせずに誰でも制作可能な簡易版ロボットを制作しました
- rosserialを用いてROSと通信を行い、cmd_velでロボットを八方向に動かしました