PR

ロボット制御 #16 Arduino UNO R4 Wifi と Raspberry Pi 5 をROS2で繋いでみる
Raspberry Pi 5 と Arduino UNO R4 Wifi を ROS2 で繋いで MicroServo MG90S を動かしてみた話

制御

今回は、「Arduino UNO R4 Wifi」と「Raspberry Pi 5」をROS2 Jazzy を使って繋いでみたいと思います。ArduinoでROS2を動かすためのライブラリは幾つかあるようですが、残念ながら公式ライブラリと思われる「ros2arduino」は、gitリポジトリを確認する限り4年前くらいでメンテナンスがストップしており、コンパイルエラーが発生して動かすことが出来ないので断念して、「micro-ROS」を使用します。

ここでの前提となる「Arduino UNO R4 Wifi」のセットアップは、以下の記事を参考にしてください。

Raspberry Pi 5」のセットアップは、以下の記事を参考にしてください。

Raspberry Pi 5」に「ROS2 Jazzy Jalisco」をセットアップするのは、以下の記事を参考にしてください。

micro-ROS-arduino のセットアップ

Arduino 側のセットアップ

まずは、「micro-ROS/micro_ros_arduino」をダウンロードします。

右側の [<> Code]ボタンを押して表示されるメニューの中から「Download ZIP」を選択します。
適当なところに保存して、後で「Arduino IDE」にインポートします。

最新の「Jazzy」を使おうと思うので、「micro_ros_arduino-jazzy.zip」をダウンロードしました。

「Arduino IDE」を起動してメニューの「スケッチ」→「ライブラリをインクルード」→「.ZIP形式のライブラリをインストール…」を選びます。
ファイル選択ウィンドウで先ほどダウンロードしたライブラリを選択して「開く」ボタンを押します。

micro_ros_arduino は、まだ「Arduino UNO R4」に対応していないため、ダウンロードしたライブラリのコードを一部、変更しないといけません。

変更するファイルは、/Arduino/libraries/micro_ros_arduino/src/default_transport.cpp の7行目の「#include <sys/time.h>」を「#include <time.h>」に変更します。

変更前

#include <Arduino.h>

extern "C"
{
  #include <stdio.h>
  #include <stdbool.h>
  #include <sys/time.h>

  int clock_gettime(clockid_t unused, struct timespec *tp) __attribute__ ((weak));
C

変更後

#include <Arduino.h>

extern "C"
{
  #include <stdio.h>
  #include <stdbool.h>
  #include <time.h>

  int clock_gettime(clockid_t unused, struct timespec *tp) __attribute__ ((weak));
C

Raspberry Pi 側のセットアップ

事前に「ROS2 Jazzy」の開発・実行環境は構築済です。

自分の環境は、~/ros2_ws の下に幾つかのワークスペースを分けて作っています。

micro-ros 用のワークスペースを新たに作ります。
ソースディレクトリに移動して、「git clone」を実行し、micro-ROSセットアップパッケージをダウンロードします。

ダウンロードが完了したら1つ上のディレクトリに移動して、依存関係の確認を行います。

ワークスペースのビルドを行います。

アンダーレイとしてROS インストール環境設定は、起動スクリプトとしてソースしているので、オーバーレイとして、micro-ros セットアップ環境設定をソースします。

最後に micro-ROSエージェントのセットアップを実行して完了です。

micro-ROS のデモを動かしてみる

現時点で「micro-ROS」は、まだ「Arduino UNO R4」ボードに対応していないためコードの一部を修正しました。ただし、GitHubのpullリクエストは出ているようなので対象にノミネートされる可能性はあるのではないかと思います。

ライブラリのコードの修正

【修正点①】time.hの修正

/Arduino/libraries/micro_ros_arduino/src/default_transport.cpp」 の7行目の「#include <sys/time.h>」を「#include <time.h>」に変更します。

変更前:「default_transport.cpp

#include <Arduino.h>

extern "C"
{
  #include <stdio.h>
  #include <stdbool.h>
  #include <sys/time.h>

  int clock_gettime(clockid_t unused, struct timespec *tp) __attribute__ ((weak));
C

変更前:「default_transport.cpp
7行目変更

#include <Arduino.h>

extern "C"
{
  #include <stdio.h>
  #include <stdbool.h>
  #include <time.h>

  int clock_gettime(clockid_t unused, struct timespec *tp) __attribute__ ((weak));
C

【修正点②】対応ボードの追加

/Arduino/libraries/micro_ros_arduino/src/micro_ros_arduino.h」と「wifi_transport.cpp」に「defined(ARDUINO_UNOR4_WIFI)」および「#include <WiFiS3.h>」の追加と、「uint」を「uint32_t」に変更します。

変更前:「micro_ros_arduino.h


#if defined(ESP32) || defined(TARGET_PORTENTA_H7_M7) || defined(ARDUINO_NANO_RP2040_CONNECT) || defined(ARDUINO_WIO_TERMINAL)

#if defined(ESP32) || defined(TARGET_PORTENTA_H7_M7)
#include <WiFi.h>
#include <WiFiUdp.h>
#elif defined(ARDUINO_NANO_RP2040_CONNECT)
#include <SPI.h>
#include <WiFiNINA.h>
#elif defined(ARDUINO_WIO_TERMINAL)
#include <rpcWiFi.h>
#include <WiFiUdp.h>
#endif

extern "C" bool arduino_wifi_transport_open(struct uxrCustomTransport * transport);
extern "C" bool arduino_wifi_transport_close(struct uxrCustomTransport * transport);
extern "C" size_t arduino_wifi_transport_write(struct uxrCustomTransport* transport, const uint8_t * buf, size_t len, uint8_t * err);
extern "C" size_t arduino_wifi_transport_read(struct uxrCustomTransport* transport, uint8_t* buf, size_t len, int timeout, uint8_t* err);
#ifndef TARGET_PORTENTA_H7_M7
struct micro_ros_agent_locator {
	IPAddress address;
	int port;
};
#endif

static inline void set_microros_wifi_transports(char * ssid, char * pass, char * agent_ip, uint agent_port){
C

変更後:「micro_ros_arduino.h
98行目変更、109-110行目追加、124行目変更


#if defined(ESP32) || defined(TARGET_PORTENTA_H7_M7) || defined(ARDUINO_NANO_RP2040_CONNECT) || defined(ARDUINO_WIO_TERMINAL) || defined(ARDUINO_UNOR4_WIFI)

#if defined(ESP32) || defined(TARGET_PORTENTA_H7_M7)
#include <WiFi.h>
#include <WiFiUdp.h>
#elif defined(ARDUINO_NANO_RP2040_CONNECT)
#include <SPI.h>
#include <WiFiNINA.h>
#elif defined(ARDUINO_WIO_TERMINAL)
#include <rpcWiFi.h>
#include <WiFiUdp.h>
#elif defined(ARDUINO_UNOR4_WIFI)
#include <WiFiS3.h>
#endif

extern "C" bool arduino_wifi_transport_open(struct uxrCustomTransport * transport);
extern "C" bool arduino_wifi_transport_close(struct uxrCustomTransport * transport);
extern "C" size_t arduino_wifi_transport_write(struct uxrCustomTransport* transport, const uint8_t * buf, size_t len, uint8_t * err);
extern "C" size_t arduino_wifi_transport_read(struct uxrCustomTransport* transport, uint8_t* buf, size_t len, int timeout, uint8_t* err);
#ifndef TARGET_PORTENTA_H7_M7
struct micro_ros_agent_locator {
	IPAddress address;
	int port;
};
#endif

static inline void set_microros_wifi_transports(char * ssid, char * pass, char * agent_ip, uint32_t agent_port){
C

変更前:「wifi_transport.cpp

#if defined(ESP32) || defined(TARGET_PORTENTA_H7_M7) || defined(ARDUINO_NANO_RP2040_CONNECT) || defined(ARDUINO_WIO_TERMINAL)
#include <Arduino.h>


#if defined(ESP32) || defined(TARGET_PORTENTA_H7_M7)
#include <WiFi.h>
#include <WiFiUdp.h>
#elif defined(ARDUINO_NANO_RP2040_CONNECT)
#include <SPI.h>
#include <WiFiNINA.h>
#elif defined(ARDUINO_WIO_TERMINAL)
#include <rpcWiFi.h>
#include <WiFiUdp.h>
#endif
C

変更後:「wifi_transport.cpp
1行目変更、14行目-15行目追加

#if defined(ESP32) || defined(TARGET_PORTENTA_H7_M7) || defined(ARDUINO_NANO_RP2040_CONNECT) || defined(ARDUINO_WIO_TERMINAL) || defined(ARDUINO_UNOR4_WIFI)
#include <Arduino.h>


#if defined(ESP32) || defined(TARGET_PORTENTA_H7_M7)
#include <WiFi.h>
#include <WiFiUdp.h>
#elif defined(ARDUINO_NANO_RP2040_CONNECT)
#include <SPI.h>
#include <WiFiNINA.h>
#elif defined(ARDUINO_WIO_TERMINAL)
#include <rpcWiFi.h>
#include <WiFiUdp.h>
#elif defined(ARDUINO_UNOR4_WIFI)
#include <WiFiS3.h>
#endif
C

修正点④ 対応CPUの追加

/Arduino/libraries/micro_ros_arduino/library.properties」に「renesas_uno」を追加します。

変更前:「library.properties

url=https://github.com/micro-ROS/micro_ros_arduino
precompiled=true
category=Other
architectures=stm32,OpenCR,Teensyduino,samd,sam,mbed,esp32,mbed_portenta
Java

変更前:「library.properties
10行目の変更

url=https://github.com/micro-ROS/micro_ros_arduino
precompiled=true
category=Other
architectures=stm32,OpenCR,Teensyduino,samd,sam,mbed,esp32,mbed_portenta,renesas_uno
Java

以上で「Arduino UNO R4 Wifi」ボード対応のコード修正は、終了です。

サンプルコードを動かしてみる

サンプルに「micro-ros_publisher_wifi」があるので、これを動かしてみようと思います。
各サンプルでもボード宣言が先頭にあるので、コードの修正が必要になります。

#include <micro_ros_arduino.h>

#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>

#include <std_msgs/msg/int32.h>

#include "arduino_secrets.h"    // add

// add ARDUINO_UNOR4_WIFI start
#if !defined(ESP32) && !defined(TARGET_PORTENTA_H7_M7) && !defined(ARDUINO_NANO_RP2040_CONNECT) && !defined(ARDUINO_WIO_TERMINAL) && !defined(ARDUINO_UNOR4_WIFI)
// add ARDUINO_UNOR4_WIFI end
#error This example is only avaible for Arduino Portenta, Arduino Nano RP2040 Connect, ESP32 Dev module and Wio Terminal
#endif
C++

14行目の最後に「&& !define(ARDUINO_UNOR4_WIFI)」を追加して、エラーが出ないように変更しました。

また、ネットワークに接続するためのSSIDやパスワード、エージェントのIPアドレス、ポートをインクルードファイルを作成して、そちらで定義します。

aruduino_secrets.h」:

// arduino_secrets.h header file
#define SECRET_SSID "ssid"
#define SECRET_PASS "password"
#define AGENT_IP "XXX.XXX.XXX.XXX"
#define AGENT_PORT 8888
C++

なお、エージェントを動かす Raspberry Pi 5 のIPアドレスを調べるのは、以下のコマンドを実行します。3つ目のアダプタ「wlan0」のところにIPアドレスが書いてあります。

micro-ros_publisher_wifi.ino」:

#include <micro_ros_arduino.h>

#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>

#include <std_msgs/msg/int32.h>

#include "arduino_secrets.h"    // add

// add ARDUINO_UNOR4_WIFI start
#if !defined(ESP32) && !defined(TARGET_PORTENTA_H7_M7) && !defined(ARDUINO_NANO_RP2040_CONNECT) && !defined(ARDUINO_WIO_TERMINAL) && !defined(ARDUINO_UNOR4_WIFI)
// add ARDUINO_UNOR4_WIFI end
#error This example is only avaible for Arduino Portenta, Arduino Nano RP2040 Connect, ESP32 Dev module and Wio Terminal
#endif

// add ssid, pass, ipad, port start
char ssid[] = SECRET_SSID;    // your network ssid (name)
char pass[] = SECRET_PASS;    // your network password
char ip[] = AGENT_IP;         // micro_ros_agent ip address
uint32_t port =  AGENT_PORT;  // micro_ros_agent port number
// add ssid, pass, ipad, port end

rcl_publisher_t publisher;
std_msgs__msg__Int32 msg;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;

#define LED_PIN 13

#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}}


void error_loop(){
  while(1){
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    delay(100);
  }
}

void timer_callback(rcl_timer_t * timer, int64_t last_call_time)
{
  RCLC_UNUSED(last_call_time);
  if (timer != NULL) {
    RCSOFTCHECK(rcl_publish(&publisher, &msg, NULL));
    msg.data++;
  }
}

void setup() {
  // change network settings start
  // set_microros_wifi_transports("WIFI SSID", "WIFI PASS", "192.168.1.57", 8888);
  set_microros_wifi_transports(ssid, pass, ip, port);
  // change network settings end

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  delay(2000);

  allocator = rcl_get_default_allocator();

  //create init_options
  RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));

  // create node
  RCCHECK(rclc_node_init_default(&node, "micro_ros_arduino_wifi_node", "", &support));

  // create publisher
  RCCHECK(rclc_publisher_init_best_effort(
    &publisher,
    &node,
    ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
    "topic_name"));

  msg.data = 0;
}

void loop() {
    RCSOFTCHECK(rcl_publish(&publisher, &msg, NULL));
    msg.data++;
}
C++
Expand

プログラムは完成ですが、プログラムを Arduino UNO R4 Wifi にアップロードする前に Raspberry Pi 5 側でエージェントを起動しておきます。

続いて Arduino UNO R4 Wifi にプログラムをアップロードします。

プログラムをアップロードすると Raspberry Pi 5 のターミナルのコンソールが凄い速さで流れていきます。

サンプルプログラムは、無事実行できました。

Arduino UNO R4 Wifi が対応ボードになっていない理由は何か

ライブラリのコードを修正しないと「micro_ros_arduino」は、Arduino UNO R4 Wifi に書き込むことが出来ませんでした。アップロードしている時に少し思ったのは、RAM領域が結構使用されており、簡単なサンプルプログラムを動かすだけでも圧迫しているように感じました。

  • 最大262144倍とのフラッシュメモリのうち、スケッチが103612バイト(39%)を使っています。
  • 最大32768バイトのRAMのうち、グローバル変数が22404バイト(68%)を使っていて、ローカル変数で10364バイト使うことができます。

ROS2のプログラムは、C++で記述されており、実行時にクラスインスタンスのロードによりメモリをかなり使う印象があります。このため複雑なROS2のインターフェースやサービスなどを実行するとRAM領域が枯渇してしまうのかもしれませんね。

そこで元々対応しているボードを調べてみます。

Portenta H7

Arduino Portenta H7 は、Nanoサイズのマイコンボードですが、2つのMCUを搭載しており、組み込みコンピュータの性能を備えているプロ仕様のボードです。ただし、値段は、日本円で2万円近くします。

項目仕様
マイクロコントローラーSTM32H747XI デュアル Cortex®-M7+M4 32ビット 低消費電力 Arm® MCU
無線モジュールMurata 1DX デュアル Wifi 802.11b/g/n 65 Mbps および Bluetooth® (Cordio スタック経由の Bluetooth® Low Energy.5、Arduino スタック経由の Bluetooth® Low Energy 4.2)
メモリ8 MB SDRAM
16 MB QSPIフラッシュ
セキュアエレメント(デフォルト)NXP SE0502
ボード電源
(USB/VIN)
5V
対応バッテリーLi-Po 1S 3.7V、700mAh 最小(内臓充電器)
回路動作電圧3.3V
ディスプレイコネクタMIPI DSI ホスト と MIPI D-PHY でピン数の少ない大型ディスプレイとインターフェース
グラフィックプロセッサーChrom-ART グラフィカル ハードウェア アクセラレータ™
タイマー22倍のタイマーとウォッチドッグ
シリアル4x ポート (2つはフロー制御付き)
イーサネットPHY10 / 100 Mbps (拡張ポート経由のみ)
SDカードSDカードコネクタ用インターフェース(拡張ポート経由のみ)
動作温度-40℃~+85℃
MKR ヘッダー既存の工業用MKRシールドのいずれかを使用します。
高密度コネクタ2つの80ピンコネクタにより、ボードのすべての周辺機器を他のデバイスに公開できます。
カメラインターフェース8 ビット、最大 80 MHz
アドバンスト最大 16 ビットの分解能を持つ 3 つの ADC (最大 36 チャネル、最大 3.6 MSPS)
DAC2×12ビットDAC(1MHz)が利用可能で、そのうち1つのみが外部A6ピンを介してユーザーにアクセスできます。
USB-Cホスト/デバイス、DisplayPort出力、高速/フルスピード、電力供給

これはハイスペックなマイコンボードです。
8 MBのSDRAM と 16 MB QSPI フラッシュ なのでメモリの問題はなさそうです。

ピンはこんなに出ていないので、基本は、以下のような拡張ボードに差し込んで使うのではないかと思います。

Arduino Nano RP2040 Connect

こちらは、まだホビーユースのうちに入るのではないかと思います。公式サイトで5千円弱の値段です。
Raspberry Pi の RP2040 マイコンチップを搭載しています。

項目 仕様
マイクロコントローラRaspberry Pi® RP2040
USBコネクタマイクロ USB
内臓LEDピン13
デジタルI/Oピン20
アナログ入力ピン8
PWMピン20 (A6、A7 を除く)
外部割込み20 (A6、A7 を除く)
Wi-FiNina W102 uBlox モジュール
Blootooth®Nina W102 uBlox モジュール
セキュアエレメントATECC608A-MAHDA-T 暗号 IC
IMUセンサLSM6DSOXTR (6軸)
マイクロフォンMP3DT06JTR
コミュニケーションシリアル
I2C
SPI
電源回路動作電圧: 3.3V
入力電圧(VIN): 5 – 21V
IOピンあたりのDC電流: 4mA
クロック速度プロセッサ 133MHz
メモリAT25SF128A-MHB-T: 16MB フラッシュ IC
Nina W102 uBlox モジュール: 448KB ROM、520 KB SRAM、16MB フラッシュ
寸法重さ: 6g
寸法: 18mm x 25mm

スペックと価格は申し分ないのですが、公式サイトでしか手に入らないようで、ハードルが少し高い感じです。

手頃な値段で実用的なマイコンボードは「Espressif」の「ESP32-WROOM-32」あたりになるのではないかと思います。
ESP32-WROOM-32」については、別の記事に書きたいと思います。ここでは、とりあえず簡単なコードで試してみたいと思います。

Arduino UNO R4 Wifi でサーボモータを動かしてみる

Raspberry Pi 5 を パブリッシャーに、Arduino UNO R4 Wifi をサブスクライバーにして、Raspberry Pi 5 からの指示(角度)をArduino UNO R4 Wifi がネットワーク経由で受け取り、モーターを動かすというものです。

D3ピンはPWM出力可能なので、D3ピンとサーボのPWM、あとは5V、GNDを繋ぎます。
モーターは、TowerPro の MG90S を使いました。

Arduino のコード

Arduino IDE のスケッチは、以下のようにしました。

ros2_servo_test.ino」:

// Include the required header files
#include <micro_ros_arduino.h>
#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <std_msgs/msg/int32.h>
#include <Servo.h>

// Includes various network settings
#include "arduino_secrets.h"

// Declare variables for ROS2 subscribers
rcl_subscription_t subscriber;
std_msgs__msg__Int32 msg;
rclc_executor_t executor;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;

// Declaring the network settings
char ssid[] = SECRET_SSID;    // your network ssid (name)
char pass[] = SECRET_PASS;    // your network password
char ip[] = AGENT_IP;         // micro_ros_agent ip address
uint32_t port =  AGENT_PORT;  // micro_ros_agent port number

// Declare a variable for the servo motor
Servo myservo;
int deg = 0;

#define LED_PIN 13

#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}}

// What to do when an error occurs
void error_loop() {
  while(1) {
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    delay(100);
  }
}

// Subscriber callback function
void subscription_callback(const void * msgin)
{
  const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin;
  deg = int(msg->data);
  myservo.write(deg);
  Serial.print("Servo motor is move: "); Serial.println(deg);
}

void setup() {
  Serial.begin(115200);
  set_microros_wifi_transports(ssid, pass, ip, port);
  delay(2000);

  // create node
  allocator = rcl_get_default_allocator();
  RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));
  RCCHECK(rclc_node_init_default(&node, "servo", "", &support));

  // create subscriber
  RCCHECK(rclc_subscription_init_default(
    &subscriber, &node,
    ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
    "servo/deg"));
  
  // create executor
  RCCHECK(rclc_executor_init(&executor, &support.context, 1, &allocator));
  RCCHECK(rclc_executor_add_subscription(&executor, &subscriber, &msg, &subscription_callback, ON_NEW_DATA));

  // Attach D3 (PWM) pin for servo motor
  myservo.attach(3, 500, 2500);
  Serial.println("The servo motor is attached to the d3 pin.");
}

uint32_t cnt = 0;
void loop() {
  delay(10);
  Serial.print("loop connt:"); Serial.println(++cnt);
  RCCHECK(rclc_executor_spin_some(&executor, RCL_MS_TO_NS(10)));
}
C++
Expand

コードを解説します。基本的には「micro_ros_arduino」ライブラリに付属しているスケッチ例の「micro-ros_subscriber」を参考にしています。

先頭には、必要なインクルード文を記述しています。

// Include the required header files
#include <micro_ros_arduino.h>
#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <std_msgs/msg/int32.h>
#include <Servo.h>
C++

ここで「micro_ros_arduino」のサブスクライバーとして実行するために必要なインクルード文を書いています。なお。メッセージは標準から用意されている「std_msgs/msg/int32」を使います。
Arduino/libraries/micro_ros_arduino/src/std_msgs/msg」の中には、他にも標準提供のメッセージが定義されています。
他の「rcl」で始まるインクルード文は、「micro_ros_arduino」を使う場合は、ほぼ定型としてインクルードしています。
最後の「Servo.h」は、サーボモータを動かすためにインクルードしています。

次の1文は、上の「サンプルコードを動かしてみる」でも使ったネットワーク設定を記述したヘッダーファイルをインクルードしています。

// Includes various network settings
#include "arduino_secrets.h"
C++

arduino_secrets.h」:

// arduino_secrets.h header file
#define SECRET_SSID "ssid"
#define SECRET_PASS "password"
#define AGENT_IP "XXX.XXX.XXX.XXX"
#define AGENT_PORT 8888
C++

ここに実際のSSID、PASSWORD、エージェントのIPアドレス、ポートを設定します。

続いて、ROS2サブスクライバーに必要な変数を定義しています。

// Declare variables for ROS2 subscribers
rcl_subscription_t subscriber;
std_msgs__msg__Int32 msg;
rclc_executor_t executor;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;
C++

汎用的なサブスクライバーを変数として「rcl_subscription_t subscriber;」を定義、
メッセージを変数として「std_msgs__msg__Int32 msg;」を定義、
残りは、基本的な定義で 「micro_ros_arduino」を使う場合は、定型的に定義しています。

続いて、ネットワーク設定に必要な各種設定を宣言しています。
ここに直接ローカルネットワーク設定を指定しても構いません。

// Declaring the network settings
char ssid[] = SECRET_SSID;    // your network ssid (name)
char pass[] = SECRET_PASS;    // your network password
char ip[] = AGENT_IP;         // micro_ros_agent ip address
uint32_t port =  AGENT_PORT;  // micro_ros_agent port number
C++

続いて、サーボモータ制御用の変数を定義しています。

// Declare a variable for the servo motor
Servo myservo;
int deg = 0;
C++

以下は、サンプルコードをそのまま持ってきています。
エラー判定用のマクロと、エラーが発生した場合には、ビルトインのLEDをチカチカ点滅するようになっています。

#define LED_PIN 13

#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}}

// What to do when an error occurs
void error_loop() {
  while(1) {
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    delay(100);
  }
}
C++

次は、サンプルスケッチには無いコードです。
サブスクライバーがメッセージを受け取った時に呼び出される関数です。

// Subscriber callback function
void subscription_callback(const void * msgin)
{
  const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin;
  deg = int(msg->data);
  myservo.write(deg);
  Serial.print("Servo motor is move: "); Serial.println(deg);
}
C++

受け取ったメッセージをint型に変換して「deg」に設定しています。
myservo.write(deg);」でサーボモーターを「deg」°に動かします。その後、シリアルポートにメッセージを送信しています。

次は、セットアップ処理です。

void setup() {
  Serial.begin(115200);
  set_microros_wifi_transports(ssid, pass, ip, port);
  delay(2000);

  // create node
  allocator = rcl_get_default_allocator();
  RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));
  RCCHECK(rclc_node_init_default(&node, "servo", "", &support));

  // create subscriber
  RCCHECK(rclc_subscription_init_default(
    &subscriber, &node,
    ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
    "servo/deg"));
  
  // create executor
  RCCHECK(rclc_executor_init(&executor, &support.context, 1, &allocator));
  RCCHECK(rclc_executor_add_subscription(&executor, &subscriber, &msg, &subscription_callback, ON_NEW_DATA));

  // Attach D3 (PWM) pin for servo motor
  myservo.attach(3, 500, 2500);
  Serial.println("The servo motor is attached to the d3 pin.");
}
C++

set_microros_wifi_transports(ssid, pass, ip, port);」は、WiFi でメッセージを受信する形態にするためのコードです。
残りのほとんどのコードは、定型名です。その中で62行目の「"servo"」はノード名で、68行目の「"servo/deg"」は、トピック名になります。

75行目の「myservo.attach(3, 500, 2500);」は、サーボモーターをアタッチしています。
最初の引数の「3」はピンの指定で、次の「500」はサーボモーターの最小の「μ秒」、次の「2500」は最大の「μ秒」を指定しています。

uint32_t cnt = 0;
void loop() {
  delay(10);
  Serial.print("loop connt:"); Serial.println(++cnt);
  RCCHECK(rclc_executor_spin_some(&executor, RCL_MS_TO_NS(10)));
}
C++

最後のこの部分は、実行処理の登録になります。
rclc_executor_spin_some」で、サブスクライバーがメッセージ待ちの状況になります。
RCL_MS_TO_NS(10)」で10ミリ秒間隔で信号をチェックするはずですが、動作確認する限りは、信号のチェックは「10秒毎」でした。
※ここは、別の人の動作確認では、触れられていませんでしたので、使い方が間違っている等の原因があるのかもしれません。(要調査)

Raspberry Pi 5 のエージェント起動

Raspberry Pi 5 側は、以下のコマンドで「ros_micro_agent」を起動します。

実行すると以下のように表示されます。

Arduino UNO R4 Wifi にスケッチをアップロードする

先ほど書いたコードを「Arduino UNO R4 Wifi」にアップロードします。

シリアルモニタには、約10秒毎に「loop()」が呼び出されているようです。

これで、Arduino UNO R4 Wifi でサブスクライバーが起動しました。

Raspberry Pi 5 でパブリッシャーを動かす

Arduino UNO R4 Wifi でサブスクライバーを起動すると「micoro_ros_agent」を実行したターミナルにメッセージが流れます。

最初は、サーボモーターは以下の状態になっています。

以下のコマンドを入力してサーボモーターを動かします。

これでサーボモーターを0°の位置に動かします。

続けてサーボモーターを180°の位置に動かします。

最後にサーボモーターを90°の位置に動かして終了します。

Arduino UNO R4 Wifi でサブスクライバーが動く

Raspberry Pi 5 からのパブリッシュメッセージを受け取って Arduino UNO R4 Wifi は、サーボモータを動かしてみましたが、その際に表示したシリアルモニタのメッセージを確認してみます。

動くには動きましたが、トピックを送信してから動き出しまでに約20秒くらいの間隔が空いてしまいます。とりあえず、Raspberry Pi 5Arduino UNO R4 Wifi が ROS2 Jazzy のmicro-ROSを利用して繋がるところまでは出来ました。

次は、もっと本格的に Interface を実装して、四足歩行ロボットの制御をしていきたいと思います。

以上です。

コメント

タイトルとURLをコピーしました