ロボットを制御するときに、一定周期で実行したくなります。ただし、汎用OS上で動かす場合、他のプロセスの優先順位などの関係から変動(ジッタ)が発生します。
PREENPT_RTというリアルタイム性を向上させるパッチを適用したLinuxカーネルを用いて、一定周期で実行するC言語プログラムを紹介します。
ハードウェア準備
用意するものはRaspberry Pi 4Bと32GBのmicorSDカードです。モニタやキーボード、ネットワークケーブルは必要に応じて接続します。
SBC:Raspberry Pi 4 B
microSD:32GB
ディストリビューションのダウンロード
PREENPT_RTのパッチをLinuxカーネルにあてる詳細な方法は、ネット等で検索すれば、有志によるサイトを見つけることができます。
今回はお手軽に、GitHubに公開されているPREEMPT_RT適用済みのUbuntuをダウンロードしてRasPiで使ってみます。ROS 2 Humbleも入っています。
以下のURLにアクセスします。
私が試したのは、ROS 2 Humble (22.04.3, 5.15.98-rt62-raspi)になります。
OS:Ubuntu 22.04.3
RTカーネル:PREEMPT_RT
ミドルウェア:ROS 2 Humble
書き込み
Raspberry Pi Imagerで、ダウンロードしたimgファイルをmicroSDに書き込みます。
OSを選択するときに、Use Customを選んで、ダウンロードしたimgファイル(圧縮ファイルのままでOK)”ubuntu-22.04.3-rt-ros2-arm64+raspi.img.xz”を指定します。
確認
現在実行中のカーネルについての名前と情報を確認します。PREEMPT_RTと書かれているので、PREEMPT_RTが適応されているのがわかります。
$ uname -a Linux ubuntu 5.15.98-rt62-raspi #1 SMP PREEMPT_RT Sun May 7 10:39:42 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux
現在実行中のカーネルが、実際にリアルタイム対応をしているかどうかを、以下のコマンドでも確認ができます。
$ cat /sys/kernel/realtime 1
ROSのバージョンは何かを確認します。
$ echo $ROS_DISTRO humble
一定周期プログラム
pthreadを使って、一定周期で実行する関数のマルチスレッドプログラムになります。
$ vi interrupt_pthread.c
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <time.h> #define INTERRUPT_INTERVAL_NS 10000000 // 10ミリ秒 void interrupt_main(void) { // 割り込み処理 printf("Interrupt occurred\n"); } void* interrupt_handler(void* arg) { while (1) { struct timespec start, end, ts; long elapsed_time_ns; // 開始時間の記録 clock_gettime(CLOCK_MONOTONIC, &start); // 割り込み処理 interrupt_main(); // 終了時間の記録 clock_gettime(CLOCK_MONOTONIC, &end); // 経過時間の計算(ナノ秒) elapsed_time_ns = (end.tv_sec - start.tv_sec) * 1000000000L + (end.tv_nsec - start.tv_nsec); // 待機時間の計算(INTERRUPT_INTERVAL_NSから経過時間を引く) ts.tv_sec = 0; ts.tv_nsec = INTERRUPT_INTERVAL_NS - elapsed_time_ns; // 10ミリ秒 - 経過時間 // 待機時間が負の場合は0にする if (ts.tv_nsec < 0) { ts.tv_nsec = 0; } // 待機 nanosleep(&ts, NULL); } return NULL; } int main() { pthread_t thread; // 割り込み処理スレッドの作成 if (pthread_create(&thread, NULL, interrupt_handler, NULL) != 0) { fprintf(stderr, "Error creating thread\n"); return 1; } // メインスレッドは他の処理を行う while (1) { // 他の処理 sleep(1); } // スレッドの終了を待機 pthread_join(thread, NULL); return 0; }
$ gcc -o interrupt_pthread interrupt_pthread.c $ ./interrupt_pthread
ちなみに、上記のプログラムはVScodeのCopilotに「pthreadを使って10ミリ秒毎に割り込み処理するC言語プログラムを書いて」と指示して、出力されたコードにさらに指示を加えたものです。
「一定周期で割り込み処理するプログラムの処理時間を計測して、処理時間が変動しても10ミリ秒間隔が変わらないように」など。
マイコンなどのタイマー割り込みと違い、一定時間になるようnanosleep関数で待つという処理を行っています。