ROS講座46 ROSでarduinoを使う

# 環境
この記事は以下の環境で動いています。

| 項目 | 値 |
|:-:|:-:|
| CPU | Core i5-8250U |
| Ubuntu | 16.04 |
| ROS | Kinetic |

インストールについては[ROS講座02 インストール](https://qiita.com/srs/items/e0e0a9dc3f94c2d3348e)を参照してください。
またこの記事のプログラムはgithubにアップロードされています。[ROS講座11 gitリポジトリ](https://qiita.com/srs/items/25a56a6abda41dcf396e)を参照してください。

# 概要
arduinoは便利で使いやすいマイコンシステムです。基本的にはUbuntuが載るような「高レベル」のPCではモーターなどのハードウェアを扱うことはできません。そこでarduinoのような「低レベル」のマイコンを挟むことでROSとモーター等をつなぎます。
この記事では以下の内容について記述します。

* Arduinoをplatformioを用いてCLIで使う
* platformioをROSのディレクトリで管理する
* rosserialを使ってPCと通信する。

# platformIOのインストール・使い方
platformIOはarduinoやmbedなどのマイコンの開発環境をCLIで使うためのツールです。公式のarduinoコンパイラはGUIで提供されているのでとっつきやすいのですがROSの環境と合わせるのが難しいです。ROSのプログラムとArduinoのプログラムを別々に管理してもよいのですが、バージョン管理の問題が起こったりするのでできるだけ同じ環境ですべてできることが理想です。そのためにArduinoもCLIのツールで用います。公式にもarduino CLIというCLIツールがありますが、裏でIDEが立ち上がりだいぶ重いみたいなので今回は見送ります。

## 使用するボード
今回はarduino megaを使用します。一番普及しているのはunoですがこれはピン数や機能が少なくロボットには不足します。megaなら複数のモーターを制御することも可能です。

![arduino_mega.jpg](https://qiita-image-store.s3.amazonaws.com/0/254442/26359e4a-3fd4-ca08-e260-eb0f500f4d5e.jpeg)
[arduinoストアより](https://store.arduino.cc/usa/arduino-mega-2560-rev3)

## インストール
pipでもインストールできますが以下の方法で行うのが一番確実です。

```shell:platformioのインストール
python -c "$(curl -fsSL https://raw.githubusercontent.com/platformio/platformio/develop/scripts/get-platformio.py)"
```

## 使うボードの選択
`platform boards`で使えるボード（マイコン）の一覧が出ます。長いので`grep‘するのがおすすめです。例えば今回使うarduino megaは

```shell:mega2560
megaatmega2560        ATMEGA2560     16MHz     248KB   8KB    Arduino Mega or Mega 2560 ATmega2560 (Mega 2560)
```
と表示されるので1番最初に表示されている`megaatmega2560`という名前で使えば良いことがわかります。

## シリアルポートの権限
標準では一般ユーザーはシリアルポートのアクセス権限がありません。以下のコマンドで永続的に権限が持てます。

```shell:シリアルポートへの権限付加
sudo gpasswd --add $USER dialout
``` 

## ワークスペースの作成
作る場所はどこでもよいですが`~/mega_project`にします。後半は`platformio.ini`という設定ファイルにボードへの書き込みの設定を追加しています。

```shell:workspaceの作成
mkdir ~/mega_project
cd ~/mega_project/
platformio init -b megaatmega2560
echo "upload_port = /dev/ttyACM0" >> platformio.ini 
echo "targets = upload" >> platformio.ini 
```

## プログラムの作成
以下のようなプログラムを`~/mega_project/src/main.ino`に書きます。

```c++:~/mega_project/src/main.ino
void setup(){
  pinMode(13, OUTPUT);
}

void loop(){
  digitalWrite(13, HIGH);
  delay(500);
  digitalWrite(13, LOW);
  delay(500);
}
```
* `setup()`は起動時に1度だけ最初に実行する関数です。
* `loop()`は`setup`後に無限に実行される関数です。
* `pinMode(13, OUTPUT);`は13番ピンを出力する関数です。
* `digitalWrite(13, HIGH);`は13番ピンをHIGH(5V)にする関数です。
* `digitalWrite(13, LOW);`は13番ピンをLOW(0V)にする関数です。

## コンパイル&アップロード
以下のコマンドでプログラムのコンパイルとアップロードの両方を行います。ArduinoをUSBポートに刺してから実行してください。

```shell:コンパイル&アップロード
cd ~/mega_project/
platformio run
```

arduinoのボードではLEDが点滅しているはずです。

# rosパッケージに統合する。
arduinoのプログラムもROSパッケージと統合するのが便利です。統合といっても実際にやるのはcatkin_wsの中にplatformioのファイルを置くこと、起動用の実行ファイルを作るだけです。

## ワークスペースの作成
ROSパッケージの中の`hard_lecture/platformio/ino01`にワークスペースを作ります。

```shell:workspaceの作成
roscd hard_lecture
mkdir -p platformio/ino01
cd platformio/ino01
platformio init -b megaatmega2560
echo "upload_port = /dev/ttyACM0" >> platformio.ini 
echo "targets = upload" >> platformio.ini 
```

## ソースコードの作成
`hard_lecture/platformio/ino01/main.ino`にプログラムを書きます。内容はさっきと同様のLチカで良いでしょう。

## コンパイル&アップロード用のスクリプトを作成
先ほどと同様にコマンドを実行してもよいですがあんまりROSっぽくないのでスクリプトを書いてrosrunで実行します。

```shell:hard_lecture/scripts/write_ino01.sh
echo "writing arduino"
echo "connect arduino on USB"

cd `dirname ${0}`/../platformio/ino01
platformio run
```

実行権限を付与します。

```shell
roscd hard_lecture
chmod +x scripts/write_ino01.sh
```

## 実行
以下のコマンドで実行できます。roscoreを実行している必要はありません。

```shell
rosrun hard_lecture write_ino01.sh 
```

# rosserialの使用
マイコンのプログラムの一番面倒な部分は通信です。PCとArduinoの通信を自作のプロトコルで作っていたら、その製作だけでも大変なことになってしまいます。そこでできたのがrosserialです。Arduinoでrosのpublisherやsubscriberが作れます。
今回は説明しませんがparameterの取得もできます。

## インストール

```shell
sudo apt-get install ros-kinetic-rosserial
sudo apt-get install ros-kinetic-rosserial-arduino
```

## ワークスペースの作成

```shell
roscd hard_lecture
mkdir -p platformio/ino02
cd platformio/ino02
platformio init -b megaatmega2560
echo "upload_port = /dev/ttyACM0" >> platformio.ini 
echo "targets = upload" >> platformio.ini 
```

## ros_libのコピー
rosのメッセージヘッダをコピーします。以下のコマンドで現在のワークスペースのメッセージファイルをarduinoで使うために変換できます。arduinoのコンパイルに使うライブラリは`hard_lecture/platformio/ino02/lib`にコピーします。`lib`はplatformioのワークスペースごとの個別に使うlocalなライブラリの保管場所です。
platformioのワークスペースを作るごとに必要な作業です。

```shell
roscd hard_lecture/platformio/ino02/lib
rosrun rosserial_arduino make_libraries.py .
```

## ソースコード
以下がrosserialでpublish、subscribeをするためのarduino側のソースコードです。いつものrosのプログラムと見た目は似ていますが、細部はいくらか違うので注意が必要です。

```shell:hard_lecture/platformio/ino02/src/main.ino
#include <ros.h>
#include <std_msgs/String.h>
#include <std_msgs/Bool.h>

ros::NodeHandle  nh;
std_msgs::String str_msg;
ros::Publisher chatter("chatter", &str_msg);
char hello[13] = "hello world!";

void led_cb(const std_msgs::Bool& msg){
  if(msg.data)digitalWrite(13, HIGH);
  else digitalWrite(13, LOW);
}
ros::Subscriber<std_msgs::Bool> sub0("led", &led_cb);

void setup()
{
  nh.initNode();
  nh.advertise(chatter);
  nh.subscribe(sub0);
  
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(13, OUTPUT);
}

void loop()
{
  str_msg.data = hello;
  chatter.publish( &str_msg );
  nh.spinOnce();
  delay(500);
}
```

## 書き込み用のスクリプト

```shell:hard_lecture/scripts/write_ino02.sh
echo "writing arduino"
echo "connect arduino on USB"

cd `dirname ${0}`/../platformio/ino02
platformio run
```

## 実行
初めに書き込みを行います。

```shell:arduinoのコンパイル・書き込み
rosrun hard_lecture write_ino02.sh 
```

この後実行します。

```shell:1つ目のターミナル
roscore
```

```shell:1つ目のターミナル
rosrun rosserial_python serial_node.py _port:=/dev/ttyACM0
```

この状態で`rostopic list`をするとarduinoでpublish、subscriveした`/chatter`と`/led`のROSトピックが見えます。
`rostopic echo /chatter`とすると`data: "hello world!"`と見えます。
`rostopic pub -1 /led std_msgs/Bool "data: true"`とするとledが点灯します。

## ros_libの必要なディレクトリ
`hard_lecture/platformio/ino02/lib/ros_lib`以下には多数のディレクトリが生成されます。しかしすべてのディレクトリが必要なわけではありません。特に今回はgitで管理するのであまり多くの無駄なディレクトリを作りたくありません。
ディレクトリのうち常に必須なのは`ros`と`rosserial_msgs`とros_lib直下のファイルのみです。
これとは別にarduinoのプログラムからリンクしている`std_msgs`が必要です。

# コメント
Raspberry PiはLinuxが動いて、GPIOが多数あるのでこれだけでロボットが作れるように見えますが、Raspberry Pi上でROSを動かしてかつモーターなどの制御をするという方式はお勧めしません。理由は3つあります。

* 1つ目はデバッグ性の問題で、Raspberry Piでモーター制御等のハードウェア部分を作ってしまうと、制御プログラムがRaspberry Piありきの物になってしまいます。こうすると例えばハードウェア部分だけを手元のノートPCにつないでテストするということができなくなります。
* 2つ目はポートの性能の問題です。じつはRaspberry Piでは純粋なハードウェアPWMは1つしかなく、他は精度が劣るソフトウェアPWMです。またGPIOの数も30ピン程度と大規模なロボットを作ろうとすると簡単に不足します。この範囲を超えた場合はRaspberry Piを複数使うことになり面倒です。
* 3つ目はリアルタイム性の問題です。例えば100usごとに定期的な処理をするというのはArduinoでは簡単ですが、Raspberry PiはLinuxを使っているために難しいです。

# 参考
[platformioのインストール](http://docs.platformio.org/en/latest/installation.html)
[platformioの使い方](http://emattsan.hatenablog.com/entry/2017/12/31/094954)
[rosserialの使い方](http://makemove.hatenablog.com/entry/2015/04/05/181604)

# 目次ページへのリンク
[ROS講座の目次へのリンク](https://qiita.com/srs/items/5f44440afea0eb616b4a)