C 言語による RS-232C 通信プログラミング

 

1. RS-232C とは? [1,2]

 

2. PC-98 シリーズでのシリアル通信 [1,2]

 

 

3. Windows でのシリアル通信 [3-5]

 Windows でのシリアル通信のためのプログラム開発にはいくつかの方法があります。

 ここでは、Win 32 API を利用し、プログラムの開発環境として Borland C++ Compiler ( BCC )を用いた場合について述べる。 

 Borland C++ Compiler 5.51 から Win32 API を使用することによってシリアル通信を行うことが出来る。以下に送信側から送られた"Hello"という文字を受信し、ターミナル上に表示させるプログラムを示す。

/* ----------------------------------------
    シリアル通信プログラム(受信専用)
---------------------------------------- */
#include <windows.h>
#include <stdio.h>
#include <stdlib>

HANDLE h;

void main() {
    int i=0;
    char sBuf[1];
    char str[100];
    int baudRate = 2400;
    unsigned long nn;
    DCB dcb;
    COMMTIMEOUTS cto;

    /* ----------------------------------------------
        ファイルのクリエイト/オープン
    ---------------------------------------------- */
    // クリエイトしたファイルのファイルハンドラを返す
    h = CreateFile( "COM1", 
                            GENERIC_READ | GENERIC_WRITE,
                            0,
                            0,
                            OPEN_EXISTING,
                            0,
                            0 ); 
    if ( h == INVALID_HANDLE_VALUE ) {
        printf("Open Error!\n");
        exit(1);
    }

    /* ----------------------------------------------
        シリアルポートの状態操作
    ---------------------------------------------- */
    GetCommState( h, &dcb ); // シリアルポートの状態を取得
    dcb.BaudRate = baudRate;
    SetCommState( h, &dcb ); // シリアルポートの状態を設定

    /* ----------------------------------------------
        シリアルポートのタイムアウト状態操作
    ---------------------------------------------- */
    GetCommTimeouts( h, &cto ); // タイムアウトの設定状態を取得
    cto.ReadIntervalTimeout = 1000;
    cto.ReadTotalTimeoutMultiplier = 0;
    cto.ReadTotalTimeoutConstant = 1000;
    cto.WriteTotalTimeoutMultiplier = 0;
    cto.WriteTotalTimeoutConstant = 0;
    SetCommTimeouts( h, &cto ); // タイムアウトの状態を設定

    /* ----------------------------------------------
        受信データの読み込み(1行分の文字列)
    ---------------------------------------------- */
    while(1) {
        ReadFile( h, sBuf, 1, &nn, 0 ); // シリアルポートに対する読み込み
        if ( nn==1 ) {
            if ( sBuf[0]==10 || sBuf[0]==13 ) { // '\r'や'\n'を受信すると文字列を閉じる
                if ( i!=0 ) {
                    str[i] = '\0';
                    i=0;
                    printf("%s\n",str);
                }
            } else { 
                str[i] = sBuf[0];
                i++;
            }
        }
    }
}

プログラム1, シリアル通信(受信のみ)を行うプログラム

 このプログラムは、32 ビットのコンソールアプリケーションとして作成しているために、コンパイル時に"-WC"オプションをつける必要がある。コンパイラの設定が正しく行われていれば、以下のコマンドによって表 2 のプログラムをコンパイルすることができる。

C:\home\C>bcc32 -WC rs232c.cpp

 コンパイルしたプログラムの実行結果を以下に示す。この結果はPICマイコンから送信された文字列("Hello")をPC側で受け取り、コンソール上に出力しています。

C:\home\C>rs232c
Hello
Hello
Hello
Hello
Hello

 しかし、このプログラムでは、受信を無限ループで行っているため、強制終了させる必要があり、プログラムとしては問題がある。

 そこで、キーボードからの入力があれば、受信を終えて次の手続きを始めるプログラムとして、スレッド処理を行うプログラムに拡張した。

/* ----------------------------------------
    シリアル通信プログラム(スレッド処理)
---------------------------------------- */
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib>

void getStrComm ( char str[100] );
void sub ( void * );
HANDLE h;
int imax=0;
char str[1000][100];

void main() {

    int i;
    int baudRate = 2400;
    DCB dcb;
    COMMTIMEOUTS cto;
    unsigned long dummy;

    /* ------------------------------------------------------
        ファイルのクリエイト/オープン
    ------------------------------------------------------ */
    // クリエイトしたファイルのファイルハンドラを返す
    h = CreateFile( "COM1", 
                            GENERIC_READ | GENERIC_WRITE,
                            0,
                            0,
                            OPEN_EXISTING,
                            0,
                            0 ); 

    if ( h == INVALID_HANDLE_VALUE ) {
        printf("Open Error!\n");
        exit(1);
    }

    /* ------------------------------------------------------
        シリアルポートの状態操作
    ------------------------------------------------------ */
    GetCommState( h, &dcb ); // シリアルポートの状態を取得
    dcb.BaudRate = baudRate;
    SetCommState( h, &dcb ); // シリアルポートの状態を設定

    /* ------------------------------------------------------
        シリアルポートのタイムアウト状態操作
    ------------------------------------------------------ */
    GetCommTimeouts( h, &cto ); // タイムアウトの設定状態を取得
    cto.ReadIntervalTimeout = 1000;
    cto.ReadTotalTimeoutMultiplier = 0;
    cto.ReadTotalTimeoutConstant = 1000;
    cto.WriteTotalTimeoutMultiplier = 0;
    cto.WriteTotalTimeoutConstant = 0;
    SetCommTimeouts( h, &cto ); // タイムアウトの状態を設定

    /* ------------------------------------------------------
        何かキーボードから入力される受信スレッドを開始
    ------------------------------------------------------ */
    printf("Enterキーを押すと受信を受信スレッドを開始します\n");
    getchar(); 
    printf("受信開始!\n\n\n");
    _beginthread( sub, 0, &dummy );

    /* ------------------------------------------------------
        何かキーボードから入力されるまで受信を継続
    ------------------------------------------------------ */
    printf("Enterキーを押すと受信を終了し、結果を表示します\n");
    getchar(); 

    /* ------------------------------------------------------
        受信データの出力
    ------------------------------------------------------ */
    printf("受信データの出力!\n");
    for ( i=0; i<imax; i++ ) {
        printf("%s\n",str[i]);
    }

}


/* ------------------------------------------------------
    スレッドによる受信データの読み込み
------------------------------------------------------ */
void sub( void *dummy ) {

    while(1) {
        getStrComm( str[imax] );
        imax++;
    }
}


/* ------------------------------------------------------
    受信データの読み込み関数(1行分の文字列)
------------------------------------------------------ */
void getStrComm( char str[] ) {

    int j=0;
    unsigned long nn;
    char sBuf[1];

    while(1) {
        ReadFile( h, sBuf, 1, &nn, 0 ); // シリアルポートに対する読み込み
        if ( nn==1 ) {
            if ( sBuf[0]==10 || sBuf[0]==13 ) { // '\r'や'\n'を受信すると文字列を閉じる
                str[j] = '\0';
                if (j!=0) break;
            } else { 
                str[j] = sBuf[0];
                j++;
            }
        }
    }
}

プログラム3, シリアル通信(スレッド処理)を行うプログラム

 このプログラムは、スレッド処理を実行すらるため、_beginthread()を使用している。よって、コンパイル時に"-WM"オプションをつける必要がある。

C:\home\C>bcc32 -WC  -WM rs232c.cpp

 コンパイルしたプログラムの実行結果を以下に示す。この結果はPICマイコンから送信された文字列("Hello")をPC側で受け取り、コンソール上に出力しています。

C:\home\C>rs232c
Enterキーを押すと受信を受信スレッドを開始します

受信開始!


Enterキーを押すと受信を終了し、結果を表示します

受信データの出力!
Hello
Hello
Hello
Hello
Hello
Hello
Hello
-- Press any key to exit (Input "c" to continue) --

 このプログラムによって、RS-232c によるデータの受信を行うことが出来ます。

 RS-232c は、計測器制御のためのインターフェイスとして利用することができます。そのような場合、受信した計測結果を表示することが求められます。そのような場合には、グラフィック・ライブラリを利用して、結果を表示することが可能です。

/* ------------------------------------------------------
  シリアル通信プログラム
    スレッド処理によるデータ受信
    2次元データの受信(X,Y)
    GrWinによるグラフ化処理
------------------------------------------------------ */

#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib>
#include <GrWin.h>

#define DATA_MAX 1000
#define DATA_STRING 100
#define BAUDRATE 2400

// 大域変数の宣言
int imax;
char str[DATA_MAX][DATA_STRING];
HANDLE h;

// プロトタイプ宣言
void initComm( void );
void sub ( void * );
void getString ( char str[DATA_STRING] );
void split( int dmax, char strBuf[DATA_MAX][DATA_STRING], int x[DATA_MAX], int y[DATA_MAX] );
void splitLine( char sBuf[DATA_STRING], int *x, int *y );
void graph( int dmax, int x[DATA_MAX], int y[DATA_MAX] );
void getMinMax( int dmax, int data[DATA_MAX], float *min, float *max );
void setGraph( void );
void setXlabel( float xmin, float xmax, float ymin, float suby, char xlabel[DATA_STRING] );
void setYlabel( float ymin, float ymax, float xmin, float subx, char ylabel[DATA_STRING] );

/* ------------------------------------------------------
    メイン関数
------------------------------------------------------ */
void main() {

    int x[DATA_MAX], y[DATA_MAX];
    unsigned long dummy;

    // シリアルポートの初期設定
    initComm();

    // 受信スレッドの開始・終了
    printf("Enterキーを押すと受信スレッドを開始します\n");
    getchar(); 
    printf("受信開始!\n\n\n");
    _beginthread( sub, 0, &dummy );
    printf("Enterキーを押すと受信を終了し、結果を表示します\n");
    getchar(); 

    // 受信データの変換・グラフ出力
    split( imax, str, x, y ); // 文字データ→数値データ
    graph( imax, x, y );
}

/* ------------------------------------------------------
    シリアルポートの初期設定
------------------------------------------------------ */
void initComm( void ) {

    DCB dcb;
    COMMTIMEOUTS cto;

    // ファイルハンドラの作成
    h = CreateFile( "COM1",
                            GENERIC_READ | GENERIC_WRITE,
                            0,
                            0,
                            OPEN_EXISTING,
                            0,
                            0 ); // ファイルハンドラを返す
    if ( h == INVALID_HANDLE_VALUE ) {
        printf("Open Error!\n");
        exit(1);
    }

    // シリアルポートの状態操作
    GetCommState( h, &dcb ); // シリアルポートの状態を取得
    dcb.BaudRate = BAUDRATE;
    SetCommState( h, &dcb ); // シリアルポートの状態を設定

    // シリアルポートのタイムアウト状態操作
    GetCommTimeouts( h, &cto ); // タイムアウトの設定状態を取得
    cto.ReadIntervalTimeout = 1000;
    cto.ReadTotalTimeoutMultiplier = 0;
    cto.ReadTotalTimeoutConstant = 1000;
    cto.WriteTotalTimeoutMultiplier = 0;
    cto.WriteTotalTimeoutConstant = 0;
    SetCommTimeouts( h, &cto ); // タイムアウトの状態を設定
}

/* ------------------------------------------------------
    スレッドによる受信データの読み込み
------------------------------------------------------ */
void sub( void *dummy ) {

    int i;
    for ( i=1; i<DATA_MAX; i++ ) {
        getString( str[i] );
        printf("%s\n",str[i]);
        imax=i;
    }
}

/* ------------------------------------------------------
    受信データの読み込み関数(1行分の文字列)
------------------------------------------------------ */
void getString( char str[] ) {

    int i, j=0;
    unsigned long nn;
    char sBuf[1];

    for ( i=1; i<DATA_STRING; i++ ) {
        ReadFile( h, sBuf, 1, &nn, 0 ); // シリアルポートに対する読み込み
        if ( nn==1 ) {
            // '\r'や'\n'を受信すると文字列を閉じる
            if ( sBuf[0]=='\r' || sBuf[0]=='\n' ) { 
                str[j] = '\0';
                if (j!=0) break;
            } else { 
                str[j] = sBuf[0];
                j++;
            }
        }
    }
}

/* ------------------------------------------------------
    受信データ文字列 → 数値データ(x,y)変換(配列)
------------------------------------------------------ */
void split( int dmax, char strBuf[DATA_MAX][DATA_STRING], int x[DATA_MAX], int y[DATA_MAX] ) {

    int i;

    for ( i=1; i<=dmax; i++ ) {
        splitLine( strBuf[i], &x[i], &y[i] );
    }
}

/* ------------------------------------------------------
    受信データ文字列 → 数値データ(x,y)変換(1行)
------------------------------------------------------ */
void splitLine( char sBuf[DATA_STRING], int *x, int *y ) {

    int j, jmax;
    char strX[DATA_STRING], strY[DATA_STRING];

    jmax=0;
    for ( j=0; j<DATA_STRING; j++ ) {
        if ( sBuf[j]=='\t' || sBuf[j]==',' ) {
            strX[j] = '\0';
            jmax = j;
        } else if ( jmax==0 ) {
            strX[j] = sBuf[j];
        } else if ( sBuf[j]=='\0' ) {
            strY[j-jmax-1] = '\0';
            break;
        } else {
            strY[j-jmax-1] = sBuf[j];
        }
        *x = atoi( strX );
        *y = atoi( strY );
    }
}

/* ------------------------------------------------------
    データ(x,y)のグラフ表示
------------------------------------------------------ */
void graph( int dmax, int x[DATA_MAX], int y[DATA_MAX] ) {

    int i;
    float xmin, xmax, ymin, ymax, subx, suby;

    getMinMax( imax, x, &xmin, &xmax );
    getMinMax( imax, y, &ymin, &ymax );
    subx = (xmax-xmin)/10;
    suby = (ymax-ymin)/10;

    setGraph();
    GWindow( xmin-subx*1.5, ymin-suby*1.5, xmax+subx, ymax+suby );
    GWrect( xmin, ymin, xmax, ymax );

    GWsetmrk( 6, suby/3, 13, -1, -1 ); // マークの指定
    GWsetpen( 16, 1, 5, 0 ); // ペンの指定
    for ( i=1; i<=dmax; i++ ) GWputmrk( x[i], y[i] ); // マークの描画

    // 目盛りとラベルの描画
    setXlabel( xmin, xmax, ymin, suby, "X-axis" );
    setYlabel( ymin, ymax, xmin, subx, "Y-axis" );
}

/* ------------------------------------------------------
    最小値と最大値の決定
------------------------------------------------------ */
void getMinMax( int dmax, int data[DATA_MAX], float *min, float *max ) {
    int i;
    *min = data[1];
    *max = data[1];
    for ( i=1; i<=dmax; i++ ) {
        if ( *min>data[i] ) *min=data[i];
        if ( *max<data[i] ) *max=data[i];
    }
}

/* ------------------------------------------------------
    グラフ表示の初期設定
------------------------------------------------------ */
void setGraph() {
    int LLW, LLH;
    GWinit();
    GWopen(0);
    GWvport( 0, 0, 1.2, 1.0 );
    if(GWsize(5, &LLW, &LLH)) {
        if(LLW > LLH)
            LLW = LLH*1.2;
        else
            LLH = LLW;
        GWsize(-5, &LLW, &LLH);
        GWsize(-3, NULL, NULL);
    }
}

/* ------------------------------------------------------
    X軸の目盛りとラベルの描画
------------------------------------------------------ */
void setXlabel( float xmin, float xmax, float ymin, float suby, char xlabel[DATA_STRING] ) {
    char chXmin[20], chXmax[20];
    sprintf( chXmin, "%d",(int)xmin );
    sprintf( chXmax, "%d",(int)xmax );
    GWsettxt( 0, 0, 0, -1, -1, "" );
    GWputtxt( (xmin+xmax)/2, ymin-suby/2, xlabel );
    GWputtxt( xmin, ymin-suby/2, chXmin );
    GWputtxt( xmax, ymin-suby/2, chXmax );
}

/* ------------------------------------------------------
    X軸の目盛りとラベルの描画
------------------------------------------------------ */
void setYlabel( float ymin, float ymax, float xmin, float subx, char ylabel[DATA_STRING] ) {
    char chYmin[20], chYmax[20];
    sprintf( chYmin, "%d",(int)ymin );
    sprintf( chYmax, "%d",(int)ymax );
    GWputtxt( xmin-subx/2, ymin, chYmin );
    GWputtxt( xmin-subx/2, ymax, chYmax );
    GWsettxt( 0, 0.25, 0, -1, -1, "" );
    GWputtxt( xmin-subx, (ymin+ymax)/2, ylabel );
}

プログラム4, シリアル通信(受信のみ)を行うプログラム

 このプログラムは、スレッド処理によって受信したデータをGrWinを使用してグラフ表示しているため、コンパイル時に"GrWin.lib"を含める必要がある。

C:\home\C>bcc32 -w-8060 -WC -WM GrWin.lib rs232c.cpp

 コンパイルしたプログラムの実行結果を以下に示す。この結果はPICマイコンから2次元データををPC側で受け取り、コンソール上での出力とグラフ表示を行っています。

C:\home\C>rs232c
Enterキーを押すと受信スレッドを開始します

受信開始!


Enterキーを押すと受信を終了し、結果を表示します
0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100
11 121
12 144
13 169
14 196
15 225
16 256

-- Press any key to exit (Input "c" to continue) --

 

 

参考文献

  1. 磯部 俊夫, "C言語とRS‐232C/GP‐IB", 工学図書, 1988
  2. 磯部 俊夫, "PC‐9801 RS‐232C活用法", 工学図書, 1988
  3. 熊谷 英樹, "すぐに役立つVisual Basicを活用した計測制御入門", 日刊工業新聞社, 2002
  4. 熊谷 英樹, "Visual Basic .NETではじめる計測制御入門", 日刊工業新聞社, 2004
  5. 土井 滋貴 , 上田 悦子 , 那須 靖弘 , "Win32API完璧マスタ―Visual C++プログラミング" , CQ出版 , 2001