見出し画像

産業オープンネット展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


この記事が気に入ったらサポートをしてみませんか?