PC-UNIX関連ドキュメント
「シリアルポートプログラミング」
   粕谷友章


 改訂履歴:
   2000/12/04 作成
   2001/08/06 includeするヘッダが見えなかったのを修正
   2002/01/12 色々と修正



1.はじめに

 Linuxがサーバ分野をはじめとするインターネットサーバ関連で急速に 採用されるなってきました。WindowsNTと比べて安価でかつ安定する Linuxは確かにサーバにはうってつけともいえます。一方、組み込みの 分野でもカスタマイズが可能、軽量、ネットワーク機能の強さが 利点となって徐々に浸透を始めてきています。本文章はFA機器で主要な インターフェイスであるシリアルポートを利用する簡単な方法を 記述しています。



2.Linux(POSIX)でのシリアルポート

 POSIXに準拠したUNIX系のOSでは全てのデバイスは/dev以下にデバイスファイルを 持ち、それがデバイスへのインターフェイスとして機能するようになっています。 POSIXには完全準拠していませんがLinuxでも同様の仕組みでデバイスを 管理しています。
 Linuxでは通常シリアルポートは/dev/ttyS0〜/dev/ttyS3が使われます。 それぞれDOS/WindowsのCOM1〜COM4に対応します。これらのデバイスファイルを オープンしてシステムコール(WindowsでいうAPI)を使えばシリアルポートに アクセスできる仕組みになっています。この考え方は全てのUNIXに共通です (たぶん)。



 2.1.termios

 LinuxでC言語を用いて/dev/ttyS?を制御する関数群はtermiosにまとめられて います。termios.hをincludeして適切な関数を用いればC言語からシリアルポートを 制御できるようになります。termiosの詳しい仕様は Linux Programmer's Manualにあります。



3.サンプルプログラム

 これ以上はクドクド説明しても仕方ないので簡単なソースを以下に示します。

sio.c
/* シリアル通信テストプログラム sio.c */

#include <stdio.h>
#include <termios.h>
#include <sys/signal.h>
#include <unistd.h>
#include <fcntl.h>

#define _POSIX_SOURCE 1 /* POSIX comliant source (POSIX準拠のソース)*/

//#define BAUDRATE B9600
#define BAUDRATE B19200

int fd;
struct termios oldtio; /* current serial port settings
                        (現在のシリアルポートの設定を待避)*/
unsigned char *receive_buffer; 
 
/*----- Open serial port method -----*/
int open_serial_port(char *modem_dev){

    struct termios newtio;

    /* Open modem device for reading and writing and not as controlling tty */
    /* 読み書きの為にモデムデバイスをオープンする。ノイズによってCTRL-Cが
              たまたま発生しても接続が切れないようにtty制御はしない */
    fd=open(modem_dev,O_RDWR | O_NOCTTY);

    if(fd < 0){
        perror(modem_dev);
        exit(-1);
    }

    tcgetattr(fd,&oldtio);

    /* Clear the struct for new port settings */ 
    /* 制御文字の初期化を行う */
    newtio.c_iflag = 0;
    newtio.c_oflag = 0;
    newtio.c_cflag = 0;
    newtio.c_lflag = 0;
    newtio.c_line = 0;
    bzero(newtio.c_cc,sizeof(newtio.c_cc));

    /* Settings for new port
      BAUDRATE : Set bps rate. You could also use cfsetispeed and cfsetospeed.
      ボーレートの設定。cfsetispeed,cfsetospeedも使用できる
      CRTSCTS  : Output hardware flow control ( only used if the cable has all
                  necessary lines. See sect. 7 of Serial-HOWTO )
             出力のハードウェアフロー制御(必要な結線が全てされているケースのみ)
      CS8      : 8n1 ( 8bit,no parity,1 stopbit)
      CLOCAL   : local connection,no modem control
      CREAD    : enable receiving characters
                    受信文字を有効にする
    */
    newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD ;

    /* IGNPAR  : ignore bytes with parity errors */
    /* (パリティエラーのデータは無視する )*/
    /* ICRNL  : */
    /* (CRをNLに対応させる。これを行わないと他のコンピュータでCRを入力しても
                 入力が終わりにならない)*/
    newtio.c_iflag = IGNPAR;

    newtio.c_oflag = 0;     /* Raw output (Rawモードでの出力)*/
    newtio.c_lflag = 0;     /* Set input mode (non-canonical,no echo,....) */
                            /* ICANN :カノニカル入力を有効にする
                                 全てのエコーを無効にし、プログラムに対し
                                 シグナルは送らせない */
    newtio.c_cc[VTIME] = 0; /* inter-character timer 2 sec
                                 (キャラクタタイマは使わない) */
    newtio.c_cc[VMIN] = 1;  /* read single char received
                                 (1文字来るまで読み込みをブロックする */ 

    /* Now clean the modem line and activate the settings for the port */
    /* モデムラインをクリアする */
    tcflush(fd,TCIFLUSH);
    /* 新しい設定を適用する */
    tcsetattr(fd,TCSANOW,&newtio);

    return 0;
}

/*----- Close serial port method -----*/
void close_serial_port(void){

/* Restore the old port settings */
    tcsetattr(fd,TCSANOW,&oldtio);
    close(fd);
}


/*----- Output a character to current serial port -----*/
int put_serial_char(unsigned char c){
    if(write(fd,&c,1) != 1)
        return -1;
    return 0;
}


/*----- Output a string to current serial port -----*/
int put_serial_string(char *s){
    if(write(fd,s,strlen(s)) != 1)
        return -1;
    return 0;
}


/*----- Get a character from current serial port -----*/
int get_serial_char(){
    unsigned char c;
    int res;
    res = read(fd,(char *)&c,1);
    return c;
}




get232c.c

/**********************************************************/
/*  シリアルポート電文受信プログラム                               */
/*                                 T.Kasuya   2002/01/12  */
/* 構成ファイル:                                             */
/*    Makefile                                            */
/*    get232c.c                                           */
/*    sio.c                                               */
/* 依存情報:                                                */
/*    libc.so.6 => /lib/libc.so.6                         */
/*    /lib/ld-linux.so.2 => /lib/ld-linux.so.2            */
/**********************************************************/

#include <stdio.h>
#include "sio.c"

#define STX 0x02
#define ETX 0x03
#define ENQ 0x05
#define ACK 0x06
#define LF 0x0a
#define CR 0x0d
#define NAK 0x15

int main(int argc,char **argv){

  /* シリアルポート通信関連の変数 */
  char rs_rbuffer[1024];
  char rs_sbuffer[256];
  int c;
  int i=0;
  static int STX_RECIEVED;
  char *serial_dev;

  /* 引数のチェック&シリアルデバイス名の取得 */
  if(argc==2 ){
    serial_dev=argv[1];
  }
  else{
    fprintf(stderr,"usage: get232c [serial_dev]\n");
    exit(1);
  }

  /* シリアル通信用バッファの初期化 */
  memset(rs_rbuffer,'\0',1024);
  memset(rs_sbuffer,'\0',256);
  /* 送信する電文の作成(ここでは"hoge") */
  sprintf(rs_sbuffer,"%choge%c",STX,ETX);

  /* シリアルポートの初期化 */
  open_serial_port(serial_dev);

  /* シリアルポートへデータを送信 */
  put_serial_string(rs_sbuffer);
           
  /* シリアルポートからのデータ受信待ちループ */
  while(1){
    c=0;
    /* シリアルポートから1バイトのデータを受信                   */
    /* データがある時だけ読み込み可能で,他の場合はブロックされる */
    c=get_serial_char();
    /* STX以降(ETXまで)をデータとして認識 */
    if(c == STX){
      STX_RECIEVED = 1;
    }
    /* アスキー文字のみをバッファに格納 */
    if(c >= 0x30){
      rs_rbuffer[i] = c;
      i++;
    }
    /* STX受信後にETXを受け取ったらそこまでを通信文と判断して処理 */
    if( c == ETX && STX_RECIEVED){
      /* 受信した電文を画面出力 */
      printf("[Recv(%d)]:%s\n",strlen(rs_rbuffer),rs_rbuffer);
      /* 受信バッファのクリア */
      memset(rs_rbuffer,'\0',1024);
      /* 受信文字列カウンタの初期化 */
      i = 0;
      STX_RECIEVED = 0;
    }
    /* バッファから溢れそうになったら終了 */
    if(i>1023)
      break;
  }

  /* シリアルポートを閉じる */
  close_serial_port();

  exit(0);
}




Makefile

SYSINCLUDE = /usr/include
CFLAGS = -g -O2 -I$(SYSINCLUDE) 
CC = gcc -Wall

all:: get232c

get232c:get232c.o
	$(CC) -o get232c get232c.o

clean:
	rm -f *.o get232c



 アーカイブをここにおきます。 展開してmakeして下さい。このプログラムは引数に通信を行うシリアルデバイスを指定して 使用します。使用例を以下に示します。
 ※テストを行う前にPCのCOM1(/dev/ttyS0)とCOM2(/dev/ttyS1)を クロスケーブルで接続して下さい。また/dev/ttyS*にプログラムを実行するユーザーがアクセスできる ようにパーミッションを変更(例.chmod 777 /dev/ttyS?など)しておいて下さい。

ターミナル1にて $ ./get232c /dev/ttyS0
ターミナル2にて $ ./get232c /dev/ttyS1

ターミナル2のコマンドを打ち込むとターミナル1に

ターミナル1 [Recv(4)]:hoge

と表示されるハズです。

 このプログラムではソースを見ていた頂いたらわかるように引数で与えられたシリアルデバイスを ボーレート19,200bps/データ長8ビット/パリティ無し/ストップビット1bitで オープンし,STX(0x02)+"hoge"+ETX(0x03)な電文を送信した後に受信待ちします。 受信は「STX+Data+ETX」のようなSTXとETXで囲まれたDataを受信すると、Dataを 標準出力に表示します。



4.おわりに

 3のプログラムで一応シリアルポートを用いた送受信が行えるようになります。 こんなプログラムでも少しいじるだけバーコードリーダーからのデータ取り込み/PLCとの通信 等が行えるようになります。C言語を用いないのであればsttyなどでコマンドラインからシリアルポートを制御し Tcl,Perl,Rubyなどのスクリプト言語やbash,tcshなどのシェルからでも簡単に 制御することもできます。後は用途に応じてということで...。



参考文献.
Linux JF(Japanese FAQ) Project Serial-HOWTO
Linux JF(Japanese FAQ) Project Serial-Programming-HOWTO
Linux Programmer's Manual TERMIOS 項
Linuxを用いた観測システムの紹介 Serial Programmingサンプル




戻る
writer:粕谷友章 kasuya@pd5.so-net.ne.jp