ASP3 版 mROS 2 で turtlesim を JoyStick で操作する方法
はじめに
前回までで ASP3 版 mROS 2 のビルドできる環境が整ったので、今回から開発方法を探っていきたいと思います。
まず簡単な所からということで、以前 高瀬先生がデモされていたこちらをやっていきます。
Q. え?サンプルあるんじゃないの?
A. 残念ながらこちらは Mbed 版であり ASP3 版は現時点では存在しません。
新規に ASP3 版で作成して動作している様子については以下の動画で確認可能です。
※今回は STM32CubeIDE ではなく STM32CubeMX を使用した例を紹介します。
使用する機器および、環境
-
ハードウェア
- Ubuntu 22.04.4 インストール済みのPC
- STMicro Nucleo-F767ZI
- 詳細: STMicroelectronics
- 購入先:MOUSER, Digi-key, マルツ, 他
- Arduino用 GroveベースシールドV2
- 詳細: Seeed Studio
- 購入先:秋月電子通商, 千石電商, マルツ, スイッチサイエンス, 他
- Grove ジョイスティック
- 詳細: Seeed Studio
- 購入先:秋月電子通商, 千石電商, マルツ, スイッチサイエンス, 他
-
ソフトウェア (Windows11)
-
ソフトウェア (Ubuntu20.04.4)
- SSH
- terminator
- ROS 2
- mturtlesim
- mROS 2
事前準備
Windows11 と Ubuntu22.04.4 は LAN ケーブルで直接接続し WinSCP を用いたファイルのやりとりを行いますので、前回の記事で扱った通り IP アドレスを固定しておきます。
固定 IP アドレスは次の通り
- Ubuntu PC enp1s0 と STMicro Nucleo-F767ZI の間
- 192.168.11.2 ⇒ STMicro Nucleo-F767ZI (mros2-asp3-f767zi)
- 192.168.11.3 ⇒ GMKtec N6005 NucBox7 Mini PC Ubuntu22.04.4
- Ubuntu PC enp2s0 と Windows PC の間
- 192.168.10.3 ⇒ GMKtec N6005 NucBox7 Mini PC Ubuntu22.04.4
- 192.168.10.4 ⇒ Windows11 PC
STMicro Nucleo-F767ZI と接続する enp1s0 も IP アドレスを同じように設定すれば、ルーターなしでも通信可能になります。
ROS 2 の通信で使用する enp1s0 は、以下のように設定しておきます。
こちらもルーター不要です。
enp1s0 IP アドレス
Terminator
Ubuntu では Terminal がありますが ROS 2 のように複数のログ表示が必要な場合は大量に立ち上げないといけません。
Terminator は一つの画面を分割して複数の画面を取りあつかう事ができるため、扱いやすくて便利です。
以下のコマンドを実行するとインストールが完了します。
sudo apt install terminator -y
起動するには以下のコマンドを打ち込むか、アプリケーション一覧から選択します。
terminator
アプリケーション一覧から起動
外観の違いはこんな感じです。
Terminal(左) と Terminator(右) の外観
画面の分割を行う場合、画面上で右クリックをすると一覧が出てくるので、そこから選びます。
- 水平に分割: Split Horizontally
- 垂直に分割: Split Vertically
右クリックで表示される一覧
このような分割も可能です。
水平分割 & 上部垂直分割
STM32CubeMX
当初は Ubuntu で実行しようと考えていましたが、操作中にフリーズするという現象が解決しなかったため Windows11 で使用する方法を紹介します。
ダウンロードとインストール
STM32Cube初期化コード生成ツール
こちらからダウンロードを行います。
アカウントを作成してダウンロードするか、ゲストとしてダウンロードするか選んでください。
ログインするか、ゲストとしてでダウンロード
※インストールは特殊な事をしないので、説明については割愛します。
マイコンの選択
STM32CubeMX を起動すると Home 画面が表示されます。今回は ACCESS TO MCU SELECTOR を選びます。
STM32CubeMX Home
左側の Series で STM32F7 を選択、 Package で LQFP144 を選択することでマイコンを絞り込みます。
右側の一覧から STM32F767ZI を選択します。これが今回使用する NUCLEO-F767ZI に搭載されているマイコンです。
選択したら、右上の Start Project を押下して次へ進みます。
STM32CubeMX MCU/MPU Selector
マイコンの周辺機能を編集可能な画面が表示されます。
STM32CubeMX Pinout&Configuration
A/D 変換の設定
A/D 変換を行うピンの設定をします。
今回は Arduino 用 Grove ベースシールド V2 の A0 コネクタに Grove ジョイスティックを接続して使用するので STM32F767ZI のどのピンに繋がるか調べます。
各ピンアサインについては、以下のページが参考になります。
ピンの接続状態を確認した結果、以下のような関係が解ります
- A0-A1 = PA3(STM32F767ZI) ⇒ ジョイスティック X 軸
- A0-A0 = PC0(STM32F767ZI) ⇒ ジョイスティック Y 軸
- A0-VCC ⇒ 3.3V
- A0-GND
マイコンのピンをクリックすると設定可能な機能が表示されます。
ピンの機能選択
一覧から ADC1_IN と記載のある機能を PC0 と PA3 へ設定します。
※マウスのホイールで画面を拡大すると作業しやすいです。
ピンの設定
左の一覧から Analog ⇒ ADC1 の順に選択すると、以下のような画面が表示されます。 IN3 と IN10 のみが選択されていることを確認してください。
ADC1 Mode
ADC1 の設定を変更する箇所は以下の通りです。
- Scan Conversion Mode ⇒ Enable
- Discontinuuous Conversion Mode ⇒ Enable
- Number of Conversion ⇒ 2
- Rank1 Channel ⇒ Channel 3
- Rank2 Channel ⇒ Channel 10
ADC1 Configuration
コードの生成
上部の一覧にあるタブから Project Manager を選択すると、生成するコードの保存先などを設定する画面に切り替わります。
設定する場所は以下の通りです。
- Project Name: 任意
- Project Location: 任意
- Application Structure: Basic
- Do not generate the main(): ☑
- Toolchain / IDE: Makefile
設定を終えたら GENERATE CODE をクリックしてコードを生成します。
Project Manager
完了後、生成ファイルのあるフォルダを開くか聞かれるので Open Folder をクリックします。
以下の画像の通りのファイルが生成されていれば OK で、今回必要になるのは src フォルダ内の二つのソースコードだけです。
- main.c
- stm32f7xx_hal_msp.c
生成ファイル
mturtle_teleop_joy
A/D 変換をして操作するプログラムを追加するだけなので、ASP3 版の mturtle_teleop
を元に改修していきます。
ディレクトリ構成
mturtle_teleop
から一式コピーして、ディレクトリの名前を mturtle_teleop_joy
と変更します。
ディレクトリ構造は以下の通りです。
mturtle_teleop_joy ディレクトリ構成
追加するのは STM32CubeMX
ディレクトリと中に格納されているソースファイルです。
生成した C ファイルは Windows11 に存在しますので Ubuntu 側にデータを送信する必要があります。
※WinSCP で移動するのが楽ですが、もちろん USB メモリ等で移動しても構いません。
内容の変更などは Windows、Ubuntu どちらで作業しても OK です。
最終的にディレクトリ構成とソースコードが正しければ問題ありません。
STM32CubeMX 関連ファイル
STM32CubeMX
で生成したファイルの多くはすでに Toppers/ASP3
で使用されているため、追加機能として使用する A/D 変換関連の処理を追記するだけで機能します。
必要になるのは新規に追加した A/D 変換関連の処理がある以下の二つのソースコードです。
- main.c
- stm32f7xx_hal_msp.c
stm32f7xx_hal_msp.c
は変更を加える必要がないため、生成されたファイルをそのまま使用します。
stm32f7xx_hal_mx.c 作成
STM32CubeMX が作成する main.c
から必要な関数だけを抜き取り、新たな C のソースファイルを作成します。
内容は、生成された main.c
のソースコードから ADC1 の初期化と GPIO の初期化を移植しており、それらで必要になるソースコードも記載しています。
#include "app.h"
#include "stm32f7xx_nucleo_144.h"
ADC_HandleTypeDef hadc1;
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = ENABLE;
hadc1.Init.NbrOfDiscConversion = 1;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 2;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
void MX_Init(void)
{
MX_GPIO_Init();
MX_ADC1_Init();
}
main.h 作成
生成された stm32f7xx_hal_msp.c
は、インクルードファイルに main.h
の記載があります。
stm32f7xx_hal_msp.c
を無修正で使用するためにダミーのヘッダーファイルとして作成します。
中身はたった一行 stm32f7xx_nucleo_144.h
を記載します。
#include "stm32f7xx_nucleo_144.h"
各ファイルの変更
公開されている mturtle_teleop
は turtlesim を PC のキーボードで操作する処理になっています。
これをジョイスティックから取得した電圧を A/D 変換して turtlesim を操作できるように変更を加えます。
app.c
こちらのファイルがメインに処理を行うファイルになります。
main 関数が最初に呼ばれるため、最初に A/D 変換関連の初期化を行っています。
teleop_task 関数では A/D 変換した値を twist して ROS 2 へ送信する処理を行っています。
処理の最後に配置している osDelay は 100 ミリ秒に設定しているので、必要に応じて数値を変えてください。
#include "app.h"
#include "mros2.h"
#include "geometry_msgs/msg/vector3.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "stm32f7xx_nucleo_144.h"
#include "kernel_cfg.h"
#include "syssvc/serial.h"
#define COEFF_LIN 10.0
#define COEFF_ANG 10.0
#define CONSOLE_LIN 0.5
#define CONSOLE_ANG 1.0
ADC_HandleTypeDef hadc1;
int main(int argc, char * argv[])
{
MX_Init();
MROS2_INFO("mROS 2 application is started");
MROS2_INFO("app name: mturtle_teleop");
mros2::init(argc, argv);
MROS2_DEBUG("mROS 2 initialization is completed");
BSP_LED_Toggle(LED1);
act_tsk(TELEOP_TASK);
BSP_LED_Toggle(LED3);
}
geometry_msgs::msg::Twist set_twist_val(
double linear_x, double linear_y, double linear_z,
double angular_x, double angular_y, double angular_z)
{
geometry_msgs::msg::Vector3 linear;
geometry_msgs::msg::Vector3 angular;
geometry_msgs::msg::Twist twist;
linear.x = linear_x;
linear.y = linear_y;
linear.z = linear_z;
angular.x = angular_x;
angular.y = angular_y;
angular.z = angular_z;
twist.linear = linear;
twist.angular = angular;
return twist;
}
std::string double_to_string(double value)
{
int intpart, fracpart;
char str[12];
intpart = (int)value;
fracpart = (int)((value - intpart) * 10000);
sprintf(str, "%d.%04d", intpart, fracpart);
return str;
}
void teleop_task(void)
{
unsigned short adc_init[2];
unsigned short adc_data[2];
mros2::Node node = mros2::Node::create_node("mturtle_teleop");
mros2::Publisher pub = node.create_publisher<geometry_msgs::msg::Twist>("turtle1/cmd_vel", 10);
MROS2_INFO("ready to pub/sub message");
geometry_msgs::msg::Vector3 linear;
geometry_msgs::msg::Vector3 angular;
geometry_msgs::msg::Twist twist;
linear.y = 0;
linear.z = 0;
angular.x = 0;
angular.y = 0;
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
adc_init[0] = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
adc_init[1] = HAL_ADC_GetValue(&hadc1);
auto publish_count = 10;
double speed = 0.5;
double turn = 1.0;
char c;
while (1)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
adc_data[0] = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
adc_data[1] = HAL_ADC_GetValue(&hadc1);
linear.x = COEFF_LIN * (double)(adc_data[0] - adc_init[0]) / 4096.0;
angular.z = COEFF_ANG * (double)(adc_data[1] - adc_init[1]) / 4096.0;
MROS2_INFO("ADC:%4d, %4d linear.x: %s, angular.z: %s", \
adc_data[0], adc_data[1], double_to_string(linear.x).c_str(), double_to_string(angular.z).c_str() );
twist.linear = linear;
twist.angular = angular;
pub.publish(twist);
osDelay(100);
}
mros2::spin();
}
void main_task(void)
{
/*
* initialize serial port
*/
serial_opn_por(TASK_PORTID);
serial_ctl_por(TASK_PORTID, (IOCTL_CRLF | IOCTL_FCSND | IOCTL_FCRCV));
main(0, NULL);
}
void
led_cyclic_handler(intptr_t exinf)
{
BSP_LED_Toggle(LED2);
}
app.h
app.cpp
から STM32CubeMX
で生成したファイルの初期化を行いたいので MX_Init
関数を追加します。
それ以外の変更はありません。
+ void MX_Init(void);
/*
* header file for the mros2 application
*/
#include <kernel.h>
/*
* Priorities for TOPPERS tasks
*/
#define MAIN_PRIORITY 5
#define HIGH_PRIORITY 9
#define MID_PRIORITY 10
#define LOW_PRIORITY 11
/*
* Constants that may depend on the target
*/
#ifndef TASK_PORTID
#define TASK_PORTID 1
#endif /* TASK_PORTID */
#ifndef STACK_SIZE
#define STACK_SIZE 4096
#endif /* STACK_SIZE */
/*
* Declaration of prototyle functions
*/
#ifndef TOPPERS_MACRO_ONLY
#ifdef __cplusplus
extern "C" {
#endif
void main_task(void);
void teleop_task(void);
extern void led_cyclic_handler(intptr_t exinf);
void MX_Init(void);
#ifdef __cplusplus
}
#endif
void Error_Handler();
#endif /* TOPPERS_MACRO_ONLY */
Makefile.inc
STM32CubeMX
関連や A/D 変換に必要になるファイル類を make
の時に使用されるように追加しています。
以下の二つのファイルは別の場所でビルドされるため、ファイル自体の追加はしていません。
- stm32f7xx_hal_adc.c
- stm32f7xx_hal_gpio.c
しかし app.cpp
のビルドには必要になるため、オブジェクトファイルを使用するように設定しています。
#
# Makefile definition for mros2 application
#
# name of main application
APPLNAME = app
# log level: 0(DEBUG), 1(INFO), 2(WARN), 3(ERROR), 4(FATAL), 5(NONE)
# NOTE: also check `initLogMask` in app.cdl for syslog setting
CDEFS := -DMROS2_LOG_MIN_SEVERITY=0
# information for directory tree
APPLDIR := $(APPDIR)
APPLDIR += $(APPDIR)/config
APPLDIR += $(APPDIR)/STM32CubeMX
# link option for C++ code
SRCLANG = c++
APPL_CXXLIBS += -specs=nano.specs
# compile switch
CDEFS += -DUSE_ASP3_FOR_STM
# additional files for configuration
APPL_COBJS := task_impl.o
APPL_COBJS += autosar_os_ext_user_config.o
APPL_COBJS += autosar_os_ext_asp3_user_config.o
APPL_COBJS += mros2_user_config.o
APPL_COBJS += stm32f7xx_hal_mx.o
APPL_COBJS += stm32f7xx_hal_msp.o
APPL_COBJS += stm32f7xx_hal_adc.o
APPL_COBJS += stm32f7xx_hal_gpio.o
mros2-asp3-f767zi
ビルド環境構築は済んでいるので、このままビルドすることが可能です。
もしもまだビルド環境が構築出来てない場合は、以下の記事をご参照ください。
mROS 2 app のビルドと書き込み
ワークスペースへ移動し、今回は新たに作成した mturtle_teleop_joy
を make
します。
cd ~/mros2-asp3-f767zi/workspace/
make clean
make app=mturtle_teleop_joy
エラーもなくビルドが完了すると asp.bin
ファイルが生成されています。
以下のコマンドで確認できます。
ls asp*
bin ファイルの確認
書き込みをする際はSTMicro Nucleo-F767ZI と Mini-PC がUSB ケーブルで接続されているか確認してください。
次に以下のコマンドで STMicro Nucleo-F767ZI に直接書き込む事ができます。
cp asp.bin /media/$USER/NODE_F767ZI/
※$USER は各自が設定したログイン名(私の場合は himura)
コマンドで書き込みを行った際の様子
mturtlesim のビルド
mturtlesim をビルドします。
以下のコマンドを打ち込みリポジトリのクローンを作成します。
cd ~/ros2_humble/src/
git clone -b mros2/humble-devel https://github.com/mROS-base/ros_tutorials
リポジトリのクローンを作成したらビルドを行います。
cd ~/ros2_humble/ && colcon build --packages-select mturtlesim
mturtle_teleop_joy の実行
STMicro Nucleo-F767ZI の mROS 2 と Mini-PC でビルドした mturtlesim を起動し、動作を確認します。
- 以下のコマンド打ち込み
picocom
を立ち上げます。
sudo picocom -b 115200 /dev/ttyACM0
- 次にボード上の黒いリセットスイッチを1度押下します。
以下の画像のようにボードからのログが表示されれば OK です。
STMicro Nucleo-F767ZI から送信されてきたログ
- mturtlesim を起動します。
cd ~/ros2_humble/
source install/local_setup.bash
ros2 run mturtlesim turtlesim_node
mturtlesim が起動すると LAN ケーブルを介して STMicro Nucleo-F767ZI と通信し 表示されている亀がジョイスティックで操作できるのが解ります。
ジョイスティックで操作される亀の様子
おわりに
予想していたよりも説明が長くなってしまいました。
しかし、実際の内容はそんなに多くないので、やりやすいと思います。
STM32CubeMX を利用して機能を追加できることが分かったので、次は別の機能を試してみたいですね。
今回の記事も参考になりましたら幸いです。
Discussion