カテゴリ
リンク

当サイトは、玄箱PRO (KURO-BOX/Pro)を中心とした組み込み、Linuxと電子工作を扱っています。
会社案内

情報セキュリティおよび個人情報の取り扱いについて


コメントとトラックバックは、spamを予防するために、編集担当が公開の作業をするまで非公開になっています。コメントはそれぞれ投稿した人のものです。

■はじめに
  lcdproc を秋月グラフィック液晶キットや KURO-RS に対応させるには、ドライバを書く必要があ りました。ここでは、lcdproc 用のドライバの書き方の概略を紹介します。
  前編では、キャラクタ・モードの秋月グラフィック液晶キット用ドライバ akiglcd と KURO-RS 用 インプット・ドライバについて紹介します。後編では、秋月グラフィック液晶キットをグラフィック ・モードで使い日本語表示できるようにした sed1520 ドライバの改造を紹介します。
■lcdproc の動作の概略
  lcdproc は、ネットワーク (TCP) で受け取ったクライアントからのコマンドを解釈して、表示す る画面をつくり、ドライバを通して液晶デバイスに表示します。画面の更新は1秒間に約10回行いま す。これによって、右上のアイコンの点滅(ハートビート)や、タイトルのスクロールなどを行って います。また、ドライバにキー・スイッチの状態を問い合わせ、結果をクライアントに渡します。

■ドライバの関数定義(すべてのドライバ)
  ドライバは、ダイナミック・ライブラリの形で、LCDd とリンクされます。そのため必要なドライ バ以外はリンクされません。またグローバル変数は、LCDd の本体や、ほかのドライバと共通になる ので、ドライバ内で使うグローバル変数は、static 宣言します。反対にほかのドライバや、LCDd 本 体で使う変数や関数には、宣言の前に MODULE_EXPORT をつけます。acinclude.a4, server/drivers/Makefile.am に記述を追加し、configure を実行します。

■定義の必要なグローバル変数(すべてのドライバ)
 定義が必要なグローバル変数は、以下の四つです。
MODULE_EXPORT char * api_version = API_VERSION; 
MODULE_EXPORT int stay_in_foreground = 0; 
MODULE_EXPORT int supports_multiple = 0; 
MODULE_EXPORT char *symbol_prefix = "akiglcd_";
 API_VERSION は、LCDd と ドライバのバージョンを確認するための変数です。必ず、このように書 きます。stay_in_forground はターミナルに表示するような場合には、バック・グラウンドで実行で きません。そのような場合に '1' にします。通常は '0' です。 supports_multiple は、複数のデ バイスを並べた表示をドライバでサポートする場合に'1'にします。簡単にはサポートせず'0'にする のがよいでしょう。symbol_prefix はドライバの名前を決めます。この名前が LCDd 本体からドライ バの関数を呼び出すのに使われます。ドライバの名前(ここでは akiglcd ) +'_' です。

■表示(出力)ドライバに必要な関数
  液晶に表示を行うのに必要な関数は以下のとおりです。akiglcd の部分は各ドライバの名前で置き 換えます。
MODULE_EXPORT int akiglcd_init(Driver *drvthis);

 ドライバの初期化時に呼ばれる関数です。通信の初期化や、設定ファイルの読み 込み、画面のクリア、必要なメモリの確保などを行います。

MODULE_EXPORT void akiglcd_close(Driver *drvthis)
 ドライバの終了時に呼ばれる関数です。ファイル・ディスクリプタを閉じたり、メモリの開放を行 います。
MODULE_EXPORT int akiglcd_width(Driver *drvthis);
 液晶画面の横の文字数を返します。
MODULE_EXPORT int akiglcd_height(Driver *drvthis);
 液晶画面の縦の文字数を返します。
MODULE_EXPORT void akiglcd_clear(Driver *drvthis);
 液晶画面の表示を全部消します。
MODULE_EXPORT void akiglcd_string(Driver *drvthis, int x, int y, char *str);
 液晶画面の (x, y) の位置に文字列 str を表示します。x, y は左上が (1, 1) です。
MODULE_EXPORT void akiglcd_chr(Driver *drvthis, int x, int y, char c);
 液晶画面の (x, y) の位置に文字 c を表示します。x, y は左上が (1, 1) です。  いずれも、drvthis という引数があります。ここの中に、動作に必要な内部情報(設定ファイルの 情報や、ファイル・ディスクリプタなど)を記憶します。

■ダブル・バッファと _flush() 関数
  上記の関数を用意すると液晶に表示を行うことができますが、ちらついてしまいます。それは、 LCDd が、まず画面を消して、表示する文字列を順にドライバに渡していくからです。書き換えてい る様子がそのまま見えるので、ちらついてしまいます(興味があれば、akiglcd.c の先頭で, #define NO_FRAME_BUFFER をつけてコンパイルし、LCDd -d akiglcd を実行しどのように表示される か試してみてください)。
  ちらつきをなくすには書き換えの様子が見えないように、表示画面全体ができあがってから一気に 書き換える方法があります。これをダブル・バッファといいます。残念ながら液晶モジュール内には 、それを実現できるだけのメモリがありません。
  そこで擬似的ではありますが、一度、ドライバ内部に画面のイメージを作り、_clear(), _chr(), _string() 関数などではイメージのみを操作するようにします。そして、_flush() 関数、
MODULE_EXPORT void akiglcd_flush(Driver *drvthis);
が呼び出されたときに、画面をクリアせずに一気に転送します。画面をクリアしないので、表示に変 化がない部分は文字が消えないためちらつきが減ります。変化がある部分は、新しい文字か空白を書 くことで表示が更新されます。

■表示(出力)ドライバの関数
  以下の関数は必須ではありませんが、定義しておくとよい関数です。
MODULE_EXPORT int akiglcd_icon(Driver *drvthis, int x, int y, int icon);
 ハートマークや、矢印などを表示するためのアイコンを (x, y) に表示する関数です。(x, y) は 左上が (1,1) です。akiglcd.c を使う場合には、次のファイル font4.txt をフォントとしてあらか じめ書き込んでおき、特別な文字を表示する形にしています。
MODULE_EXPORT void akiglcd_backlight(Driver *drvthis, int state);
 バックライトのオン/オフ制御です。
MODULE_EXPORT void XXX_num (Driver *drvthis, int x, int num);
 時計表示などで、大きな数字を表示するための関数です。akiglcd ドライバでは用意していません 。
MODULE_EXPORT void XXX_vbar(Driver *drvthis, int x, int y, int len, int promille, int options);
 縦方向のバーを表示するための関数です。(x, y) の位置から上に向けて、len * promile/1000 文 字分のバーを表示します。akiglcd ドライバでは用意していません。
MODULE_EXPORT void XXX_hbar(Driver *drvthis, int x, int y, int len, int promille, int options);
 横方向のバーを表示するための関数です。(x, y) の位置から右に、len * promile/1000 文字分の バーを表示します。akiglcd ドライバでは用意していません。

■akiglcd ドライバ (秋月グラフィック液晶にキャラクタ・モードで表示するドライバ)
 内部に 20×4文字分のバッファを framebuf[] として確保し、 akiglcd_chr(), akiglcd_clear(), akiglcd_string() 関数では、そこに文字を書いていきます。
MODULE_EXPORT void 
akiglcd_clear(Driver *drvthis) 
{ 
    PrivateData *p = (PrivateData *) drvthis->private_data; 

    memset(p->framebuf, ' ', p->width * p->height); 
}

akiglcd_clear() では、空白文字で 20×4 文字埋めている。

MODULE_EXPORT void 
akiglcd_chr(Driver *drvthis, int x, int y, char c) 
{ 
    PrivateData *p = (PrivateData *) drvthis->private_data; 

    y--; 
    x--; 
    p->framebuf[(y * p->width) + x ] = c; 
}

akiglcd_chr は、framebuf[] に1文字コピーしている。

MODULE_EXPORT void 
akiglcd_string(Driver *drvthis, int x, int y, char *str) 
{ 
    PrivateData *p = (PrivateData *) drvthis->private_data; 
    int i, len = strlen(str); 
    unsigned char *q; 

    x--; 
    y--; 
    if (y>=(p->height)) 
        return; 

    if ((len + x)>(p->width)) 
        len = len - (len+x-p->width); 
    if (len < 0) 
        return; 

    q = p->framebuf + (y*p->width) + x; 

    for (i = len; i>0; i --, q ++ , str ++) { 
        *q = *str; 
    } 
}

akiglcd_string は、framebuf[] に文字列をコピーしている。コピー前には表示 幅を超えないようにチェックをしている。

MODULE_EXPORT void 
akiglcd_flush(Driver *drvthis) 
{ 
    PrivateData *p = (PrivateData *) drvthis->private_data; 

    const char poscmd[] = "x1bN0000r"; 

    akiglcd_write(p->fd, poscmd, sizeof(poscmd)-1); 
    akiglcd_checkret(p->fd, '@'); 

    akiglcd_write(p->fd, p->framebuf, p->width*p->height); 
}

akiglcd_flush() 関数では、左上に座標を移動後、20×4文字一気に書き込んでい る。

 ここで akiglcd_write() 関数というのを使っています。秋月グラフィック液晶キットは、表示す る文字のフォントを I2C バスで PIC とつながった ROM から読み出して表示しています。どうやら ROM からの読み出しに時間がかかるらしく、2文字以上連続して送ると文字が表示されないことがあ ったので、その対策として akiglcd_write() 関数を用意しています。  ここで、タイミングを usleep() で決めていますが、引数が '1' になっています。1[us]で十分で はないのですが、手持ちのUSB serial + 玄箱PROではうまく動作しているので、そのままにしていま す。短すぎる場合は、10*1000 (10ms) 単位で増やしてみてください。
int 
akiglcd_write(int fd, const void *buf, int count) 
{ 
    int i; 

    for (i=count; i>0; i --) { 
        if (i > 1) { 
            write(fd, buf, 2); 
            buf +=2; 
            i --; 
        } else { 
            write(fd, buf, 1); 
            buf ++; 
        } 
    usleep(1); 
    } 

    return i; 
}

■入力ドライバに必要な関数
  KURO-RS のような入力のみのデバイスには、入力ドライバを用意します。入力ドライバに必要な関 数は以下のとおりです。
MODULE_EXPORT int kurors_init(Driver *drvthis);
 ドライバの初期化時に呼ばれる関数です。通信の初期化や、設定ファイルの読み込みなどを行いま す。
MODULE_EXPORT void kurors_close(Driver *drvthis);
 ドライバの終了時に呼ばれる関数です。ファイル・ディスクリプタを閉じたり、メモリの開放を行 います。
MODULE_EXPORT const char *kurors_get_key(Driver *drvthis);
 押されたキーの名前を返す関数です。上矢印キーに相当するのは "UP", 下矢印は "Down" です。 また、新しく "Play" や "Center" といったキーの名前を使ってもかまいません。

kurors ドライバ
  kurors ドライバでは、kurors_get_key() が呼ばれると、シリアル・ポートのバッファにたまった データをチェックしています。チェックには、二つのフェーズ WAIT_Y と WAIT_REC があります。 WAIT_Y は、KURO-RS が次のリモコン・コードを受付状態になったことを示す 'Y'を待ちます。 WAIT_REC では、開始記号'S'、リモコン・コード(240バイト)と, 終了記号'E'を待っている状態です 。データが 242 バイトたまると、押されたリモコンのコードの識別関数 rec_analyze を呼び出し、 押されたキーにあわせた名前を返します。
MODULE_EXPORT const char * 
kurors_get_key(Driver *drvthis) 
{ 
    PrivateData *p = (PrivateData *) drvthis->private_data; 
    char s, rbuf[1], tBuf[BUFSIZ]; 
    int rSize; 
    char *ret = NULL; 

    switch (p->status) { 
        case WAIT_Y: 
        if (read(p->fd, rbuf, 1) == 1) { 
            if (rbuf[0] != 'Y') { 
                debug(RPT_INFO, "kurors: rec error, could not get 'Y'"); 
                exit(-1); 
            } 
            p->status = WAIT_REC; 
        } 
        break; 
    case WAIT_REC: 
        rSize = read(p->fd, tBuf, BUFSIZ); 
        if (rSize < 0) { 
            debug(RPT_INFO, "kurors: read error"); 
            exit(-1); 
        } 
        p->sumSize += rSize; 
        memcpy(&(p->rBuf[p->rIdx]), tBuf, rSize); 
        p->rIdx += rSize; 

        if (p->sumSize == 242) { 
            if((p->rBuf[0] != 'S') || (p->rBuf[241] != 'E') ) { 
                debug(RPT_INFO, "kurors: error"); 
                exit(-1); 
            } 
            ret = analyze_rec(p->rBuf); 
            // Wait next key press 
            s = 'r'; 
            write(p->fd, &s, 1); 
            p->status = WAIT_Y; 
            p->sumSize = 0; 
            p->rIdx    = 0; 
        } 
        break; 
     default: 
        break; 
    } 
    return ret; 
}
 kurors_get_key() で待ちつづけると表示に影響が出るので、この関数でシリアル・バッファのデー タをいつまでも待たないようにしています。具体的には kurors_init() で、
    t.c_cc[VTIME]=0; 
    t.c_cc[VMIN]=0;
として、バッファが空の場合は read() が、すぐに返ってくるようにしています。  kurors_analyze() では、構造体 rsa の中のリモコン・コード(家電協フォーマットに近いもの)と の比較をし、一致すれば key の中身を返しています。
struct rsaction_ { 
    char *code; // remote controller code 
    char *key;   // key pad 
}; 

struct rsaction_ rsa[] = { 
    /* SANSUI RS-F2 */ 
    /* number buttons */ 
    {"01011101101000101000001001111101", "Enter"}, /* 1 */ 
    {"01011101101000100100001010111101", "Escape"}, /* 2 */ 
    {"01011101101000101100001000111101", "Up"}, /* 3 */ 
    {"01011101101000100010001011011101", "Down"}, /* 4 */ 
                     <続く>

<光永 法明>

 カテゴリ 

 

トラックバック(0)

このブログ記事を参照しているブログ一覧: lcdproc のドライバの書き方 (前編)

このブログ記事に対するトラックバックURL: http://www.eleki-jack.com/mt/mt-tb.cgi/1221

コメントする

おすすめ書籍
Powered by
Movable Type 4.1