産業オープンネット展2024 デモ展示について---②
今回も引き続き、東京と大阪で開催された産業オープンネット展2024でのデモ展示について記載します。
Raspberry Pi 4のメインチップであるBCM2711の、
CPU0と1にSOLID OS
CPU2と3にRaspbian Linux
が搭載されて、それぞれ動作していることは先述の通りです。
各OS上で動作しているアプリケーションは、
Linuxアプリケーション ⇒ カメラまわりの処理
SOLIDアプリケーション ⇒ センサ系の処理
と、役割分担しています。
順番に見ていきます。
1.Linuxアプリケーション
カメラまわりの処理を行っているLinuxアプリケーションですが、より詳細には、以下の動作を行っています。
・カメラの初期設定を行う
・カメラのシャッタートリガによる静止画像データが更新されれば、そのデータをフレームバッファから取得し、ビュワーに表示する
※それ以外にも、カメラとのUSB通信を行ったりしていますが、これはアプリケーションより下位の層なので、割愛します。
これらのアプリには、既存のものを使用しました。
[カメラの初期設定を行う]
v4l2-utilsを使用しました。
[静止画像取得&表示&更新]
カメラに付属のビューワソフト「tcam-capture」を使用しました。
その他、gstreamerでも試しましたが、問題なく動作しています。
2.SOLID(uITRONベース)アプリケーション
2.1 仕様
センサ系の処理を行っているSOLIDアプリケーションについてご紹介します。
前回、「ファームウェア」という言葉を使ってご説明したもの、です。
繰り返しになりますが、ここで仕様をもう一度おさらいします。
まず、制御対象のシステムは以下です。
・カメラとセンサ二つが一直線に並んでいる
・円盤には4つの穴があいている。
- 透過センサによる光検出用の小穴二つ
- 反射センサによる光検出とカメラによる画像撮影用の大穴二つ
こちらは一つしか使用しない。片方は、円盤回転を安定させるためのダミー穴。
この円盤が右回転します。
円盤が回転すると、
① 透過センサが光検出
② 反射センサが光検出 ⇒ ①-②間の時間測定=時間Aとする
③ 透過センサが光検出 ⇒ (時間A-α)のカウント開始
(α:枠が見えるようにするため少し時間を減らす)
④ 時間A(-α)後にシャッタートリガ操作
⇒ちょうど穴と上部の枠が、カメラの正面に来ている
と動きます。
波形で示すと、こうなります。
2.2 制御対象のハードウェア
ソフトウェアで制御する対象のハードウェアは、3つあります。
・透過センサ
⇒センサ値はGPIO入力
・反射センサ
⇒センサ値はGPIO入力
・カメラシャッター用トリガ端子
⇒GPIO出力
すべて、GPIOです。
2.3リソース
次に、ソフトウェアが必要とするリソースについて見ていきます。
(1)ハードウェアを制御するためのGPIOが3本
以下のように割り当てました。
[補足]
Raspberry Pi4のCPUであるBCM2711では、全端子のGPIO割り込みが同じベクタにジャンプします。
ベクタで割り込み要因を確認し、どのGPIO端子からの割り込みなのかを確認することで、各端子からの割り込みを区別することができます。
今回のデモでは簡素化のため、GPIO3のみ割り込み検出を行いました。
(2)システムタイマ
時間Aの算出のため、先述の①と②のシステムタイマ時刻を知る必要がありました。
(3)アラーム
③通過後、時間A(-α)経過後にGPIO26を操作するため、アラーム機能を使用しました。
(4)データキュー
一度目の透過センサ通過時、その時のシステムタイマの示す時刻をメインルーチンに通知する目的で使用しました。
通知元はGPIOハンドラです。
メインルーチンとしては、このデータキューに値が入れば透過センサ通過したことがわかりますし、同時にその時刻も取得できます。
(5)イベントフラグ
(4)以外の、データやり取りを必要としない場面で使用しました。
2.4 GPIO操作ライブラリについて
今回3本のGPIOをいろいろと操作するため、GPIOに関する操作をライブラリとしてまとめました。
API関数は4つ定義しました。
・GPIO端子の設定
・端子状態のリード
・端子状態変更のためのライト
・割り込み設定&割り込み発生時にジャンプするアプリ側関数を登録
です。
void GPIO_setup(uint8_t channel_no, uint8_t direction, uint8_t edge, uint8_t pull_up_down);
bool_t GPIO_in(uint8_t channel_no);
void GPIO_out(uint8_t channel_no, bool_t new_state);
int gpio_int_enable(int (*func_ptr)(void *param, SOLID_CPU_CONTEXT *cnt, int GPEDS0_val, int GPEDS1_val), int priority);
ソースコードは割愛します。
最後に、ワークスペース一式をリンクしますので、そちらをご参照ください。
2.5 アプリケーション部ソースコード
仕様を思い出すため、波形をもう一度見てみます。
①と②の時間Aを計測し、③を起点にその時間A(実際は-αありますが)経過後にGPIO26を操作します。
アラーム機能、データキュー、イベントキューを使って、各割り込みハンドラとメインルーチンとのやりとりを行います。
ソースコードは以下です。
#include <solid_log.h>
#include <kernel.h>
#include <solid_timer.h>
#include "..\gpio_lib\lib.h"
#include <stdio.h>
#define WAIT_1st (0)
#define WAIT_2nd (1)
#define STATUS_2ND_SENSOR1 (1)
#define STATUS_FINISH_ALARM (2)
uint8_t sensor_status = WAIT_1st;
SOLID_TIMER_HANDLER g_timer;
#define DTQCNT 64 /*データ・キューの容量(データの個数)*/
static uint64_t current_time;
static ER_ID dtqid;
static FLGPTN event_1st_sensor2 = STATUS_2ND_SENSOR1;
static FLGPTN event_alarm = STATUS_FINISH_ALARM;
static ER_ID flag;
//円盤デモ
int sensor_demno_handler(void *param, SOLID_CPU_CONTEXT *cnt, int GPEDS0_val, int GPEDS1_val)
{
// SOLID_LOG_printf("GPIO interrupt.\n");
// SOLID_LOG_printf(" GPEDS0_val = %x\n", GPEDS0_val); //GPIO 0-31 : 1 = Event detected on GPIO pin n
// SOLID_LOG_printf(" GPEDS1_val = %x\n", GPEDS1_val); //GPIO 32-57: 1 = Event detected on GPIO pin n
uint64_t point1_tick;
//GPIO3=透過センサ割込み
if (GPEDS0_val & 0x00000008)
{
if (sensor_status == WAIT_1st)
{
point1_tick = SOLID_TIMER_GetCurrentTick(); //->データキューでSnd_dtqする(64ビットで)。main()のWhileループでで受ける
unl_cpu();
ER_ID error = psnd_dtq(dtqid, (intptr_t)point1_tick);
sensor_status = WAIT_2nd;
}
else if (sensor_status == WAIT_2nd)
{
sensor_status = WAIT_1st;
ER ercd = set_flg(flag, event_1st_sensor2);
}
GPIO_setup(GPIO_3, GPIO_IN, EDGE_INT_NONE, PUD_UP); //透過センサ;停止
}
return 0;
}
void alarm_handler(intptr_t exif)
{
//alarmハンドラで発火
GPIO_out(26, GPIO_LOW);
ER ercd = set_flg(flag, event_alarm);
}
//円盤デモ
//Wiring:
// GPIO2 ; [INPUT](反射センサ)
// GPIO3 ; [INPUT] & interrupt(透過センサ)
// GPIO26; [OUTPUT]
static void camera_tigger_demo(void)
{
uint64_t point1_tick;
uint64_t point2_tick;
FLGPTN ptn;
GPIO_setup(GPIO_3, GPIO_IN, EDGE_INT_FALLING, PUD_UP); //透過センサ;穴の光検出で↓エッジ -> 割込み
GPIO_setup(GPIO_2, GPIO_IN, EDGE_INT_NONE, PUD_DOWN); //反射センサ;穴の光検出で↑エッジ -> ポーリングで検出
GPIO_setup(GPIO_26, GPIO_OUT, EDGE_INT_NONE, PUD_UP);
GPIO_out(GPIO_26, GPIO_HIGH);
gpio_int_enable(sensor_demno_handler, 10);
//データキュー作成
const T_CDTQ pk_cdtq = {
/*変数の宣言,初期化*/
TA_NULL, /*データ・キュー属性(dtqatr)*/
DTQCNT, /*データ・キューの容量(データの個数)(dtqcnt)*/
(void *)current_time /*データ・キュー領域の先頭アドレス(dtq)*/
};
dtqid = acre_dtq(&pk_cdtq);
//イベント作成
const T_CFLG cflg = {
TA_TPRI,
0,
};
ER_ID ercd = acre_flg(&cflg);
flag = ercd;
//alarm作成
T_NFYINFO nfyinfo;
nfyinfo.nfymode = 0;
nfyinfo.nfy.handler.tmehdr = alarm_handler;
T_CALM pk_calm = {
TA_NULL,
nfyinfo
};
acre_alm(&pk_calm);
while (1)
{
//1度目の透過センサ検出待ち
ER_ID ercd = rcv_dtq(dtqid, (intptr_t *)&point1_tick);
//GPIO2=反射センサ光検出(HIGH)待ち(光検出でHIGHになる)
while(GPIO_in(GPIO_2)==GPIO_LOW);
point2_tick = SOLID_TIMER_GetCurrentTick();
//GPIO_out(26, GPIO_LOW);//波形取得用操作
//GPIO_out(26, GPIO_HIGH);//波形取得用操作
//1回目の透過センサ検出からの経過時間を計算
uint64_t wait_time = SOLID_TIMER_ToUsec(point2_tick - point1_tick);
//透過センサ2回目検出開始;穴の光検出で↓エッジ
GPIO_setup(GPIO_3, GPIO_IN, EDGE_INT_FALLING, PUD_UP);
//2度目の透過センサ検出待ち
ercd = wai_flg(flag, event_1st_sensor2, TWF_ORW, &ptn);
//2度目の透過センサ検出。
ercd = clr_flg(flag, ~event_1st_sensor2);
//計算した時間後にアラーム割り込み発生(sta_alm) -> アラームハンドラでGPIO26操作。
//[For Debug] wait_time_ms = (RELTIM)100000;//100ms ->100ms後にアラームハンドラがコールされることを波形で確認
sta_alm(1, (RELTIM)wait_time);
//アラームハンドラ発生&終了待ち
ercd = wai_flg(flag, event_alarm, TWF_ORW, &ptn);
//アラームハンドラ終了
ercd = clr_flg(flag, ~event_alarm);
dly_tsk(100); //100us (=GPIOをLowにしている時間)
GPIO_out(26, GPIO_HIGH);
GPIO_setup(GPIO_3, GPIO_IN, EDGE_INT_FALLING, PUD_UP); //透過センサ1回目検出待ち;穴の光検出で↓エッジ
}
}
extern "C" void slo_main()
{
SOLID_LOG_printf("Camera trigger operation start!\n");
camera_tigger_demo(); //円盤デモ
return;
}
※このソースコードでは、アラームハンドラ起床までの時間は時間Aとしており、(-α)の計算は行っていません。
※GPIO2端子のHIGH検出として、単にwhileループとしています。
while(GPIO_in(GPIO_2)==GPIO_LOW);
この間、より優先順位の低いスレッドは、実行されません。
今回のデモでは、並列動作する他スレッドがなかったため、加えて、簡易実装のため、このようにしていますが、実際のシステム設計時にはあまり推奨される実装ではありません。
3.まとめ
円盤の回転数が増える場合に、透過センサや反射センサが正しく検出し、正しくプログラムが動作するのか、、、という心配があったのですが、杞憂に終わりました。
当然といえば当然なのですが、高速回転する円盤でも、光が入ったなら即座に割り込みが発生しますし、uITRONベースのアプリケーションとしては、即時応答は得意なところなので、何の心配もなかったです。
前回、Linuxとの比較を行いましたが、やはりこういったブレのない高速動作を求められるとuITRONは強いな、と再認識しました。
最後にワークスペース一式のリンクと、デモビデオのリンクを貼っておきます。
よろしければ、お立ち寄りください。
ワークスペース:
https://solid.kmckk.com/SOLID/wp-content/uploads/2024/08/Camera_trigger.zip
デモビデオ:
https://www.youtube.com/watch?v=EY1o9xqiUjg