Javaでシリアル通信を使ってみよう

本チュートリアルの内容

■ 開発環境の確認

前提とする環境

このチュートリアルでは、以下の環境を想定します。

開発の準備

今回、シリアル通信では、RXTX for JavaというJavaのライブラリーを使用します。準備として、以下のサイトからダウンロードしておきましょう。

ただし、64bitのOSで利用する際には、以下のサイトを利用するとよいでしょう。

RXTX for Java(64ビット)のダウンロードサイト
図 RXTX for Java(64ビット)のダウンロードサイト

ダウンロードしたzipファイルを解凍し、jarファイルは任意のディレクトリーに、dllファイル2つは、使用するJDKのBinフォルダにコピーします。ここでは以下に保存することとします。

ページの先頭へ


■ 例題のお題を確認する

ここでは、シリアル通信を使用したチャットアプリを作成してみます。ただし、本格的なチャットアプリではなく、ASCII文字だけを送受信できるものです。

使用イメージ
図 使用イメージ

アプリケーションは、GUIがあるものとします。GUIにはSwingを使用します。画面には、やり取りを表示する領域(JTextArea)、送信するメッセージを書き込むエリア(JTextArea)と、送信用のボタン(JButton)を配置します。

参考までに、パソコン同士をシリアルケーブルで接続する場合には、クロスケーブルを使用します。また、ノートPCで開発をする際には、シリアルコネクタがついていない場合が多いと思いますので、その際には、USB-シリアルコネクタを使用する必要があります。

■ 画面を作成する

アプリケーションのクラス構成としては、フレーム用のクラス、パネルクラスを作成することにします。パネルクラスを分けておくのは、後で、こちらのクラスに、ボタン処理や通信処理のイベント処理を入れるためです。

ここでは、フレーム用のクラスをSComm.java、パネル用のクラスをCOMM.javaとして説明を進めます。COMM.javaは、JPanelのサブクラスとします。SCommは、単純に、以下のようなコードになると思います。必要なライブラリは、swingとawtだけです。

1
2
3
4
5
6
7
8
9
10
public class SComm{
    public static void main(String[] args) {
        JFrame frame=new JFrame("Serial Communication");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        COMM h = new COMM();
        frame.add(h,BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }
}

次に、COMMクラスを作成します。まず、画面に配置するJTextAreaとJButtonを定義します。

1
2
JTextArea txt1,txt2;
JButton btn;

続いて、コンストラクタを以下のように定義します。

1
2
3
4
5
6
7
8
9
10
11
public COMM(){
    txt1=new JTextArea("");
    txt1.setPreferredSize(new Dimension(200,200));
    txt2=new JTextArea("");
    txt2.setPreferredSize(new Dimension(200,20));
    btn=new JButton("送信");
    setLayout(new BorderLayout(5,5));
    add(btn,BorderLayout.EAST);
    add(txt1,BorderLayout.CENTER);
    add(txt2,BorderLayout.SOUTH);
}

さて、ここまでで画面が完成です。作成したプログラムを実行してみましょう。以下のような画面が表示されたでしょうか。

完成画面
図 完成画面

■ 送信部分を作成する

次に、送信部分を作成します。まずは、「送信」ボタンを押した際に、メッセージを入力する領域に入れた文字をやり取りを表示する領域に書込み、メッセージを入力する領域を消去するという処理だけ完成させます。

ボタン押下時のイベント処理用に、ButtonListenerというクラスを追加します。このクラスは、ActionListenerのサブクラスとして作成し、COMMクラスの中に作成します。

JTextArea内に書かれている文字は、getText()メソッドで取り出すことができるので、取り出した文字列を、追加したいJTextAreaにappend()メソッドで追加すればよいことになります。ここでは、送信用の文字列をsStrとして定義して、それをtxt2から取得し、txt1に追加するというようにします。また、ボタンを押したときに、txt2に何も書かれていないときには、何も処理しないようにする、さらに、送信を意味する"-->"という記号を行頭に追加するという処理も付け加えておきます。

1
2
3
4
5
6
7
8
9
class ButtonListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
        String sStr=txt2.getText();
            if(!sStr.equals("")){
            txt1.append("-->"+sStr+"\r\n");
            txt2.setText("");
            }
    }
}

コードの追加においては、必要に応じてライブラリを追加してください。

次に、ボタン押下とイベント処理を結びつけるコードを追加します。これは、コンストラクタの中に以下のように追加することで実現できます。

1
btn.addActionListener(new ButtonListener());

では、実際に動かしてみましょう。送信メッセージを送る領域に、何か文字を書き込んで、「送信」ボタンを押してみます。以下の例は、testと入力してボタンを押したところです。

ボタン処理追加後
図 ボタン処理追加後

では、いよいよシリアル通信の部分を実装しましょう。その前に、使用するRXTXライブラリをEclipseに追加します。追加の仕方は、Eclipseの画面の使用しているプロジェクトで右クリックをし、「ビルド・パス」を選択します。そして、更に、「ビルド・パスの構成」を選択します。開いたウィンドウで、「ライブラリー(L)」タブを選び、画面の右にある、「外部Jar追加(X)」を選択します。そして、開いた画面において、最初に配置した、RXTXComm.jarを選択し、OKを押します。すると、以下の画面のように、ライブラリが追加されます。

外部ライブラリーの追加
図 外部ライブラリーの追加

シリアル通信を行う際には、使用するCOMポートの指定、プロトコルのパラメータの設定をする必要があります。COMポートの指定には、RXTXでは、COMポートの指定にCommPortIdentifierクラス、プロトコルのパラメータの設定には、SerialPortクラスを使用します。COMポートの指定には、CommPortIdentifierのgetPortIdentifierメソッドを使用します。このメソッドの引数として、使用するコムポートを書き込みます。ポートは使用する環境によって異なるので、ここでは、起動時の引数として入力する方法をとることとします。つまり、SComm.javaは以下のように変更します。

1
2
3
4
(追加)
 String commId=args[0]; // コムポート
(変更)
COMM h = new COMM(commId);

また、COMMのコンストラクターにも、引数を追加します。

1
2
(変更)
public COMM(String commId){

次に、実際の通信処理の部分ですが、これについては、COMMのコンストラクターに以下のようなコードを追加することで実現できます。ここで忘れてはならないのは、CommPortIdentifierとSerialPortの変数をクラスの変数として定義しておくことです。また、このポートの設定処理はエラー処理を行う必要がありますので、その点も注意してください。

ここでは、通信速度19200ボー、データビット数8、ストップビット1、パリティなしで、フローコントロールもなしに設定しています。これらの意味は、ウェブなどで検索して調べてみてください。あまり影響はないですが、portId.openの引数は、アプリケーションの名前と、タイムアウトの時間(おそらくミリセカンド)のようです。

1
2
3
4
5
6
7
8
9
10
// シリアル通信設定
try{
    portId = CommPortIdentifier.getPortIdentifier(commId);
    port = (SerialPort)portId.open("serial", 2000);
    port.setSerialPortParams(19200, SerialPort.DATABITS_8,
            SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
    port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
}catch(Exception ex){
    ex.printStackTrace();
}

これで、シリアル通信を行う準備ができました。次に、実際にボタンを押した時のイベント処理にデータを送信するコードを追加します。内容は、先ほど画面表示したsStrを通信の送信バッファに書き込むという内容です。一行ごとに改行されるように改行コードも付けて送信することとします。エラー処理が必要となるので、try/catchでエラー処理もします。

1
2
3
4
5
6
7
try{
    OutputStream out = port.getOutputStream();
    String output=sStr+"\r\n";
    out.write(output.getBytes());
}catch(Exception ex){
    ex.printStackTrace();
}

では、動作の確認をしてみましょう。通信の確認なので、PC一台ではできません。ここでは、他のPCとクロスケーブルで接続し、対向するPCには、市販のシリアル通信ソフトであるComTを入れて動作の確認をしました。

送信側画面
図 送信側画面
受信側画面
図 受信側画面

送信した内容が、無事にComTの画面に表示されるのが確認できるでしょうか。

■ 受信部分を作成する

いよいよ、最後の受信部分の作成です。受信では、シリアルポートのバッファを監視して、データを受信したら、処理をするというイベント処理を実装する必要があります。ということで、まずは、COMMにイベント処理を実装する必要があります。ここでは、SerialPortEventListenerを実装します。

1
public class COMM extends JPanel implements SerialPortEventListener{

次に、イベント処理用のメソッドを追加します。serialEvent(SerialPortEvent event)のメソッドをvoidで追加します。これで、エラーは消えると思いますが、このままでは、何も表示されません。データを受け取るためには、まず、コンストラクターの方を変更します。先ほどは、送信をするのに必要な部分しか書かなかったのですが、ここでは、データを受信したときにメソッドを呼ぶ、データを受信したことを次の処理に知らせるの処理を追加するとともに、受け取ったデータをBufferReader型の変数に格納するという処理も追加します。ここで、BufferReader型の変数(bufReader)もクラス変数として追加します。

1
2
3
4
(コンストラクターへの追加部分)
  port.addEventListener(this);
  port.notifyOnDataAvailable(true);
  bufReader = new BufferedReader(new InputStreamReader(port.getInputStream()));

このようにすることで、対向するPCから送られてきたデータはbufReaderに格納されます。次に、格納されたデータを取り出して表示させる部分のです。実は、このチュートリアルを作成している段階では、あまりうまい方法が見つかっていません。ということで、ここでは、次のような処理を行いました。1)受け取ったデータの文字数を数えて、文字数が0の時には、受信処理終了。2)受け取ったデータを1バイトずつ読みだしていき、改行コード(\r)を受信するまで、StringBuffer型の変数に格納する。3)StringBuffer型の変数を、メッセージ表示領域にappendする。その際に、受信を示す記号を付与し、改行コードもつけておく。という流れで実現しました。この部分については、もっと良いコードがあったら変更する可能性があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// シリアル通信における受信データの取得
public void serialEvent(SerialPortEvent event) {
    StringBuffer strBuf=new StringBuffer();
    int recDat=0;
    if(event.getEventType()==SerialPortEvent.DATA_AVAILABLE){
        while(true){
            try{
                recDat=bufReader.read();
                if(recDat==-1) break; // 文字無しの場合抜ける
                if((char)recDat!='\r'){
                    strBuf.append((char)recDat);
                }
                else
                {
                    break;
                }
            }catch(IOException ex){
                ex.printStackTrace();
                System.exit(1);
            }
        }
        txt1.append("<--"+strBuf.toString()+"\r\n");
    }
}

さて、これにて完成です。実際に、送受信のテストをしてみましょう。先ほどと同じく、ComTを使用するとよいでしょう。以下のような画面が表示されましたか。

テスト画面
図 テスト結果画面
対向PC画面
図 確認画面

■ 応用問題に挑戦してみよう

それでは、応用問題に挑戦してみましょう。


ページの先頭へ