2006年10月11日

PICNIC

-- LAN経由でLinux からLCDに文字を表示 --




別室でも文字情報を表示したい

以前 MATRIX ORBITAL を用いてUSB経由でLinux からVFD に文字を表示する手順を 公開しました。 Matrix Orbital -- Linux で利用できるUSB VFDユニット --です。

あの後、Linux BOX から離れた場所(自宅内の別室)でも文字情報を 表示したいと欲が出てきました。 もう一台Linux BOXとMATRIX ORBITAL を設置して表示することは可能ですが、 お金もかかるしスマートでありません。
インターネットから集めた文字(株価とか...)を表示したいだけなので、PC のような強力なリソースは不要です。 そこで秋葉原のパーツショップで販売されている、LAN 対応の組み込みマイコンボードでできないかどうか検討をはじめました。

『LANによるハードウェア制御』

書店で参考になる書籍を探していたところ、CQ出版社の『LANによるハードウェア制御』 ( http://www.cqpub.co.jp/hanbai/books/37/37351.htm)にめぐり逢いました。

これを購入し、ながめているとH8 系のLAN 対応マイコン組み立てキットがあることがわかりました。 でもこのキットでゆく場合、アセンブラ等でプログラムを組んでROM 等に焼きこむ必要があります。結構大変そうです。

ですがPICNIC という組み立てキットもこの書籍に紹介されています。 こちらでは基本的にファームウェアはいじらずに、そのまま使うようです。 LAN経由で温度センサーのデータをPCで取り込んだり、ボード上のRS-232C やパラレルI/O をPCから制御できます。 そして、16文字x2行の文字表示液晶を組み込んで、それにPC から送信した文字を表示することができるそうです。これだ〜!!

プログラムを書いて自室に設置したPC で動かす必要がありボード単独の動作では なくなりますが、PCの追加ではなく既に動作しているホームサーバで大丈夫です。 ボード側にはプログラム(組み込み系,アセンブラ,...)を自分で書かずにすむのでかなり 簡単にできそうです。

PICNIC の組み立て

秋葉原に突撃し、書籍で紹介されていたPICNIC(7,300円) とAC アダプタ(秋月電子通商 NP12-1S1210 650円), 別売りの推奨液晶キット(SC1602B 1,000円), 基盤の足となるスペーサ(20円x4) を購入しました。

中学生のころにラジオ等のキットを組み立てたことがあり、10年程前にも仕事で 特殊な変換ケーブルを作成したことはあります。25年前に購入し、10年以上は 通電していなかったハンダごてでPICNIC を組み立てました。

途中ハンダづけに失敗しやり直した箇所などがあり、これは動かないかもしれないなー と不安に思いつつ、ACアダプタをつけてイーサケーブルでハブに接続するとLINK LED が点灯しました。
PC のIPアドレスを192.168.0.1 に設定してPICNIC のデフォルトのIP アドレスである192.168.0.200にブラウザで接続すると、 ファームウェアに書き込まれたWeb 画面があらわれました。かなり感動です。

Web 画面からPICNIC のIPアドレスを自宅LAN用に設定し、液晶制御通信用のUDP ポートをデフォルトの0(=液晶無効)から10000に変更しました。 save してPICNICの電源を切り、液晶ボードをPICNICに接続します。 PICNICの電源を再投入すると液晶にIPアドレスが表示されました。 翌日簡単なUDP通信プログラムをsocket API でLinux上で作成し、 PICNIC の液晶に任意の文字を表示するポイントを試行錯誤してサンプルプログラムを 完成しました。

液晶に文字を表示するプログラム

作成したサンプルプログラムを紹介します。
このプログラムはLinux 上で実行します。 例えば次のように引数に文字列を与え実行するとPICNIC のLCD にその文字列を 表示します。

% picnic_lcd hello

PICNIC のIPアドレスと液晶制御ポートはプログラム中のPICNIC_IP_ADDR, PICNIC_LCD_PORT に設定してコンパイルしてください。


/******************************************************************************
"picnic_lcd.c"    PICNIC LCD client


・引数に与えた文字列をUDP で送信し、PICNIC の液晶に表示します
・PICNIC のポートとIPアドレスはプログラム中のPICNIC_IP_ADDR, PICNIC_LCD_PORT に
  設定してください
・Linux(Slackware 10.1, kernel 2.4.32)で動作確認しています
・本プログラムはフリーソフトです. 改造や再配布は自由にどうぞ

                 compile: gcc -o picnic_lcd picnic_lcd.c

                by S.K. (http://www.lares.dti.ne.jp/seiki/)
******************************************************************************/

#include <stdio.h>
#include <errno.h>
#include <unistd.h>     /* usleep() */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PICNIC_IP_ADDR  "192.168.0.200"
#define PICNIC_LCD_PORT 10000

int clear_lcd(int sockfd, struct sockaddr *psv_addr, int svlen)
{
    int cmd_len;
    char cmd[64];

    cmd[0] = 1; /* command mode */
    cmd[1] = 1; /* LCD clear and goto HOME */
    cmd_len = 2;
    if( sendto(sockfd, cmd, cmd_len, 0, psv_addr, svlen) != cmd_len )
        return 1;
    return 0;
}

/*
 * 液晶の指定位置から指定メッセージを表示する
 *
 * 引数 row  1:1行目 or 2:2行目
 *      col  1:1カラム目 〜 16:16カラム目
 *
 * 戻り値 0: 成功したかも(本当に液晶に表示されたかどうかはわからない)
 *        1: 明らかな失敗
 */
int print_lcd(int sockfd, int row, int col, const char *str,
            struct sockaddr *psv_addr, int svlen)
{
    int n, cmd_len, cmd_arg;
    char line[BUFSIZ];

    if( row <= 0 )
        row = 1;
    else if( row >= 3 )
        row = 2;
    if( col <= 0 )
        col = 1;
    else if( col > 16 )
        col = 16;

    /*
     * カーソル移動コマンドは128+座標コード
     * 座標は1行目はカラム番号が座標コードになる
     * 2行目は64+カラム番号
     *
     * 例 128: 1行目の左端に移動(128+0)
     *    192: 2行目の左端に移動(128+64+0)
     */
    cmd_arg = 128+(row-1)*64+(col-1);

    line[0] = 1; /* command mode */
    line[1] = cmd_arg;
    cmd_len = 2;
    n = strlen(str);
    strcpy(line+cmd_len, str);

    if( sendto(sockfd, line, n+cmd_len, 0, psv_addr, svlen) != n+cmd_len )
      return 1;
    return 0;
}

/*
 * ack 受信(ポーリング)を行う再起関数
 * 再起呼出しの際に10msec スリープする
 *
 * 戻り値    正整数: 受信したデータ長
 *               -2: 再起over
 *               -1: その他エラー
 */
int try_chk_ack(int sockfd, struct sockaddr *psv_addr, int svlen, int dep)
{
    int len;
    char recvline[BUFSIZ];

    len = recvfrom(sockfd, recvline, BUFSIZ, MSG_DONTWAIT, 
           (struct sockaddr *)psv_addr, (int *)&svlen);
    if( len == -1 ){
      if( errno == EAGAIN ){
        /* データがまだ到着していない場合 */
        if( dep <= 0 )
            return -2;   /* これ以上は再起しない */
        else{
          usleep(10000); /* 10msec */
          return try_chk_ack(sockfd, psv_addr, svlen, dep -1);
        }
      }
      else
          printf("try_chk_ack() errno: %d\n", errno);
    }
    return len;
}

/*
 * 最大10回、約10msec 毎にPICNIC からのACK を受信する
 * 受信できた場合は直ちに返る
 *
 * 何かを送信するとPICNIC は応答を返すので、送信とペアで本関数でデータを
 * 取り込んでおかないと、何か障害が起こるかもしれない
 *
 * 戻り値   0: ack 受信成功
 *         -1: その他エラー
 *         -2: retry over (PICNIC が動作していない or 遅い)
 */
int chk_ack(int sockfd, struct sockaddr *psv_addr, int svlen)
{
    int len;

    len = try_chk_ack(sockfd, psv_addr, svlen, 10);
    if(len >= 0 )
      return 0;
    return len;
}

main(int argc, char *argv[])
{
    int sockfd, iret;
    struct sockaddr_in sv_addr;

    if( argc < 2 ){
      printf("give message as argument.\n");
      exit(1);
    }

    /* 通信先情報構造体を作成 */
    bzero((char *)&sv_addr, sizeof(sv_addr));
    sv_addr.sin_family      = AF_INET;
    sv_addr.sin_addr.s_addr = inet_addr(PICNIC_IP_ADDR);
    sv_addr.sin_port        = htons(PICNIC_LCD_PORT);

    if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) <0 ){
        puts("socket call error.\n");
        exit(2);
    }

    /* PICNIC の液晶をクリアする */
    iret = clear_lcd(sockfd, (struct sockaddr *)&sv_addr, sizeof(sv_addr));
    if( iret != 0 )
        printf("clear_lcd() error\n");

    /* PICNIC の応答を受信し、PICNICが動作しているか否かを判定する */
    iret = chk_ack(sockfd, (struct sockaddr *)&sv_addr, sizeof(sv_addr));
    if( iret == -2 ){
      printf("PICNIC is not working.\n");
      close(sockfd);
      exit(3);
    }

    /* 引数に与えられた文字列をPICNIC 液晶の左上から表示する */
    iret = print_lcd(sockfd, 1, 1,
                   argv[1], (struct sockaddr *)&sv_addr, sizeof(sv_addr));
    if( iret != 0 )
        printf("print_lcd() error\n");
    chk_ack(sockfd, (struct sockaddr *)&sv_addr, sizeof(sv_addr));

    close(sockfd);
    exit(0);
}





以上



メインメニュー へ戻る