Linux でハードなポートからデータをとってくる方法
数秒単位ならば、通常の方法があるので( sleep() とかね)、もっと
シビアな場合の方法は、RT-Linux を使うのが最良でしょう。
#シビアな場合とは、10mS以下の場合かな?
お約束
RT-Linux が走っていること。
手順その0(前提)
サンプルコード ( 2task ) をネタにして、実現方法を決める
方針1:実際にリアルタイムで動作する部分はモジュールとして後から追加する
方針2:メインからの入出力は /dev/rtf0,1,2,3 で行う
方針3:/dev/rtf0, /dev/rtf1 はデータの入出力
方針4:/dev/fd2 はリアルタイムタスクの制御用に使う
手順その1
モジュールを作る(リアルタイムで動作する部分)
サンプル(2task) に手を加えてみました。
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/rt_sched.h>
#include <linux/rtf.h>
#include "control.h"
RT_TASK tasks[2]; /* 二個のタスクを作るので */
/* t -- the fifo number */
- void fun( int t ) {
- while(1) {
- /* FIFO にデータを入れる */
- /* 実際にデータをとってくる所は get_data() にしてます */
- /* ということで get_data() は作ってくださいな */
- rtf_put( t, get_data(), sizeof( int ) );
- /* 次の用があるまでお休み */
- rt_task_wait();
}
}
/* task controle function */
/* /dev/rtf2 にデータが書き込まれれば自動的にこの関数が起動します */
/* 書き込むデータは、 my_msg_struct型で、その中身は */
/* コマンドとタスク番号と動作間隔が入っています */
int my_handler( unsigned int fifo ) {
- struct my_msg_struct msg;
- int err;
- RTIME now;
/* FIFO からデータを取ってきて、希望するデータならばコマンドを実行する */
while(( err = rtf_get(2, &msg, sizeof(msg))) == sizeof(msg)){
- switch( msg.command ){
- case START_TASK:
- /* タスクを起動する開始時間として、今の時間をとってきて */
- now = rt_get_time();
- /* タスクを指定時間間隔で起動します */
- rt_task_make_periodic( &tasks[msg.task], now, msg.periodic );
- break;
- case STOP_TASK:
- /* 指定したタスクを止めます */
- rt_task_suspend( &tasks[msg.task] );
- break;
- default:
- return -EINVAL;
- }
- }
- if( err != 0 ){
- return -EINVAL;
- }
- return 0;
}
/* モジュールの初期化関数 */
int init_module( void ) {
-
/* データを受け渡す FIFO を作る */
/* rtf_create( FIFOの番号、確保するFIFOのサイズ ) */
rtf_create( 0, 4000 );
rtf_creatr( 1, 4000 );
rtf_create( 2, 100 );
/* タスクの初期化 */
/* rt_task_init( RT_TASKへのポインター, 実行する関数, 実行する時に渡される引き数、スタックサイズ、優先度 */
rt_task_init( &tasks[0], fun, 0, 3000, 4);
rt_task_init( &tasks[1], fun, 1, 3000, 5 );
/* 2番のFIFO にデータが書き込まれたら起動する関数の登録 */
/* rtf_create_handler( FIFO の番号、起動する関数 */
rtf_create_handler( 2, &my_handler );
return 0;
}
/* 終了時の処理 */
void cleanup_module(void) {
-
/* FIFO の解放 */
rtf_destroy(0);
rtf_destroy(1);
rtf_destroy(2);
/* タスクの削除 */
rt_task_delete( &tasks[0] );
rt_task_delete( &tasks[1] );
}
手順その2
できたモジュールを組み込む。
コンパイルした時に、エラーが無いことを確認して、例えば rt_process.o を
組み込む時には insmod rt_process.o を実行して下さい。
当然、こんな危ないことをするので root 権限で実行します。
手順その3
スペシャルファイルを作る
通常のカーネルプロセスとのデータのやり取りに使う FIFO のスペシャルファイル
を作ります。
この、作業は1回行えば良いです。
作り方は、簡単で、この際ですから4個、一度に作っておきましょう。
for i in 0 1 2 3; do mknod /dev/rtf$i c 63 $i; done
を実行すればできます。(当然 root 権限が必要になります)
手順その4
メイン部分を作る。
サンプルソース task2 から
/* おきまりのヘッダーファイルのインクルード */
#include <stdio.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/rtf.h>
#include <asm/rt_time.h>
#include "control.h"
#define BUFFSIZE 70
char buff[ BUFFSIZE ];
int main() {
-
fd_set rfds;
struct timeval tv;
int retval;
int fd0, fd1, ctl;
int n;
int i;
struct my_msg_struct msg;
/* /dev/rtf0 のオープン、実際にオープンされるのは1番の FIFO */
/* こちら側(通常のプロセス)からの見えかたはキャラクターデバイス */
/* open で開いたので読み書きは read/write を使います */
fd0 = open( "/dev/rtf0", O_RDONLY );
if( fd0 < 0 ){
-
fprintf( stderr, "Error opening /dev/rtf0\n" );
exit(1);
}
fd1 = open( "/dev/rtf1", O_RDONLY );
if( fd1 < 0 ){
-
fprintf( stderr, "Error opening /dev/rtf1\n" );
exit(1);
}
ctl = open( "/dev/rtf2", O_WRONLY);
if( ctl < 0 ){
-
fprintf( stderr, "Error opening /dev/rtf2\n" );
exit(1);
}
/* 今までは FIFO のオープンで、ここから下がタスクの制御になります */
/* このサンプルでは、ctl に構造体を渡すことによりタスクの開始/停止を */
/* 実現しています */
/* now start the task */
msg.command = START_TASK;
msg.task = 0:
msg.period = ( RT_TICKS_PER_SEC * 5000 ) / 1000000;
if( wtrite( ctl, &msg, sizeof(msg) ) < 0 ){
-
fprintf( stderr, "Can't send a command to RT-task\n" );
exit(1);
}
msg.task = 1;
msg.period = ( RT_TICKS_PER_SEC * 2000) / 1000000;
if( write( ctl, &msg, sizeof( msg ) ) < 0 ){
-
fprintf( stderr, "Can't send a command to RT-task\n" );
exit(1);
}
/* ここまでがタスクの開始でした。 */
/* ここから下がタスクからデータを吸い上げる部分です */
/* データを吸い上げる方法は select() を使ってデータが現れるのを */
/* read( int, void*, int ) で読み込んでいます。 */
/* select() の使い方は man select を参照のこと */
for( i = 0; i < 100; i++ ){
-
FD_ZERO( &rfds );
FD_SET( fd0, &rfds );
FD_SET( fd1, &rfds );
tv.tv_sec = 1;
tv.tv_usec = 0;
retval = select( FD_SETSIZE, &rfds, NULL, NULL, &tv );
if( retval > 0 ) {
-
if( FD_ISSET( fd0, &rfds ) ){
-
n = read( fd0, buf, BUFSIZE-1 );
buf]n] = 0;
printf( "FIFO 0: %s\n", buf );
}
if( FD_ISSET( fd1, &rfds ) ){
-
n = read( fd1, buf, BUFSIZE-1 );
buf[0] = 0;
printf( "FIFO 1: %s\n", buf );
}
}
}
/* ここまでが、データのサンプリング部分でした */
/* ここからが、終了処理です */
/* このサンプルでは ctl に終了コマンドを送ってタスクを終了(実際は停止)*/
/* 本当にタスクが削除されるのはこのプログラムが終わった時から */
/* しばらくして、モジュールがアンロードされる時です(?) */
/* stop the tasks */
msg.command = STOP_TASK;
msg.task = 0;
if( write( ctl, &msg, sizeof(msg) ) < 0 ){
-
fprintf( stderr, "Can't send a command to RT-task\n" );
exit(1 );
}
msg.task = 1;
if( write( ctl, &msg, sizeof( msg ) ) < 0 ) {
-
fprintf( stderr, "Can't send a command to RT-task\n" );
exit(1);
}
return 0;
}
例えば、このメインのプログラムが app という名前だとすると
./app
と打って、実行すれば動作するはずです。
幸運を祈ります。
Written by H.Tokumoto (Orange System HIROSHIMA)