トップページ           電子回路のページのトップ

 液晶表示でトラブル

 私は簡単な用途にのみ、MikroCを使っています。
 8ピンのPIC12Fから18ピンの16F88程度です。
 それ以上の用途ではPIC18FとC18の組み合わせを使っています。
 簡単な用途ですので、MikroCでは液晶表示器を接続したことがありませんでした。
 今回、液晶表示器を使ってみたのですが、トラブルで2日程、悩みました。

 何をしようとしたか

 以前、無線ユニットでデータを送る実験をした事を別の頁で紹介しています。
 PIC16F876のRS232Cユニットに市販の315MHzASKモジュールを接続しただけですが、 受信側にプリアンプを付け、受信アンテナを工夫すれば、見通しが良い場所で30m程度は届きました。
 (送信側にアンプを付けたり、送信アンテナを強化すると電波法に触れます。)
 無線モジュールは送信ユニットにHレベルのデータを入力した時、受信ユニットからHレベルが出力されるようになって います。
 しかし、受信電波が弱くなるとHレベルの立ち上がりが遅れるようで、結果的に受信データパルスがやせ細ります。
 ハードウエアのRS232受信ユニットを使わず、ソフトウエアで受信データのレベルチェックのタイミングを微調整すれば、 さらに有利になるのではないかと思いついた訳です。
 そこで、以前作成したプログラムを修正しようとしました。

 CCSCが動作しない

 最初、PIC18F2420に差し替えてC18でプログラムを作ろうとも思ったのですが、使わなくなった16F876の在庫 が増えてしまいます。
 そこで、久々にCCSCを使おうとしたのですが、古いバージョンのCCSCはXPのパソコンでは動作しなくなって いました。
 仕方が無いので、MikroC(評価版)を使うことにした訳です。
 受信基板には受信データを表示する為の液晶表示器が付いていますのでMikroCで液晶表示をすることになりました。

 MikroCには液晶表示ライブラリーが付属しているが

 MikroCには液晶ライブラリーが付属しているようです。
 しかし、使用条件、動作タイミング等の詳細が不明で使う気になれません。
 液晶表示の関数は簡単に作れますし、一度作れば使い回せます。
 自作すればCPU周辺モジュールに対する理解が深まるというメリットもあります。
 今回はC18用に作成した液晶表示関数を若干、修正して使うことにしました。

 開発環境が壊れた?

 コーディングが済んだのでビルドしてみました。
 細かいエラーを一つずつ潰して、ある時点でリンクに移行する画面が出かかりました。
 ところが、リンク途中に見当はずれのエラーメッセージを1行出してストップしてしまいました。
 一度、この現象が出ると、以後、開発環境は正常に動作しなくなります。
 この場合、一旦、MikroCを終了し(終了時も意味不明のエラーダイアログが出ます。)、再度、起動すれば元 に戻りますが、これでは先に進めません。
 パソコンで動作するC言語の開発環境では「実行」メニューがあるので、ランタイムエラーで開発環境がコケることはよく あります。
 しかし、ファームウエアを開発する環境で、少しばかりヘボなソースを入力した位で、コケてもらっては困ります。
 開発環境が壊れたと思いこみ、MikroCをアンインストールし、再インストールしましたが状況は変わりません。

 原因は関数の二重定義

 液晶表示関係の関数が臭いと見当を付け、呼び出し側を全てコメントアウトし、一つずつコメントを外してみました。
 関数本体と呼び出し側は別ソースにあるので、呼び出さない場合、リンクはされませんが、コンパイルは、されるはずです。
 この時点でコンパイルエラーが発生し、関数が二重定義されているというメッセージが出ました。
 ソースファイルを確認しても、特に二重定義をしている個所はありません。
 そこで、ライブラリーのリファレンスを見ると同一名の関数を3個発見しました。
 自作した液晶関数で2個、RS232C関数で1個でした。
 ある機能を持った関数を作ろうとすると、どうしても似たような名称になってしまいます。
 さらに、液晶関数で、もう2つ二重定義となる関数があるのですが、この関数はライブラリーに無く、原因不明です。
 この2つも名称を変更するとパスします。
 又、最初の時点でコンパイルエラーが出ず、なぜリンクまで進んだのか疑問が残ります。
 このバージョン、又はこのチップのみの問題かもしれませんので、機会があったら試してみる必要があるかもしれません。
 とりあえず、今回は、関数名を変更した結果、リンクが終了したので、このまま進めました。

 ライブラリーでは無く、組み込み済み関数

 MikroCのマニュアルを見るとディレー関係の少数の関数を組み込み関数とし、他の多くの関数をライブラリー関数として 区別しています。
 ライブラリーならコンパイル済みのモジュールをプロジェクトに加え、該当するヘッダファイルをインクルードして、初めて 有効になります。
 マニュアルをよく見ると、「ヘッダファイルをインクルードしなくても使える。」と謳い文句が書かれていました。
 これは結局「組み込み関数」のことではないでしょうか?
 組み込み関数と明記されていれば、もう少し関数名に注意したはずです。
 CCSCでは、これらを「組み込み関数」と明記し、見やすい一覧表もあるので、今まで関数名が重複したことはありません でした。
 C18では別途、ライブラリーのリファレンスPDFがダウンロード出来ます。
 こちらは、ライブラリー毎にヘッダファイルをインクルードするようになっています。
 この方法が一番馴染みやすいと思います。
 MikroCでは出来るだけ手続きを簡略化し、初心者にも馴染み易くしているのかもしれません。
 しかし、コンフィギュレーション設定の方法と同様に、結局は使いづらくなります。
 初心者も永久に初心者では無いはずです。

 実験は中断したが

 自作関数の名称を変更したところ、コンパイル、リンクは無事終了したので、実行してみました。
 液晶表示は問題無いようですが、肝心なソフトウエアRS232Cが機能せず、結局、手持ちのデバッガの使えるPIC18Fに 差し替えて、後で作り直そうということになり、現在、中断しています。
 ただ、液晶表示に関して、若干、確認したいことがあったので、簡単な実験をしてみました。

 文字列定数にポインタが使えるか

 PICはROMとRAMが別空間にあります。
 したがって、文字データがROMにあるかRAMにあるかで、動作が異なります。
 通常、文字列操作はポインタを使って1文字ずつ操作します。(初期化以外)
 しかし、CCSCはROM空間に配置された文字列定数にポインタが使えません。
 CCSCでは引数に文字列が渡されると、コンパイラが繰り返し処理を展開します。
 1文字ずつ操作する必要が無いので、一見、楽ですが、標準とは違うので抵抗があるし、弊害もありそうです。
 C18の場合は文字列定数にポインタが使えます。
 当然ながらROMとRAMではアドレス空間が異なるので、各々、専用のポインタが必要となり、両者に互換性は、 ありません。
 それでは、MikroCでは、どうだろうという事で実験してみました。

 実験回路

実験回路

 実験は無線データ受信基板のハードウエアを利用しました。
 回路は無線データ受信基板の該当部分を抽出したもので、新たに製作した訳ではありません。

 実験プログラム

 このプログラムは液晶表示の実験の為の簡単なサンプルです。
 トラブルを起こした試作プログラムとは関係なく、テストプログラムとして正常に動作します。
 電源投入後、2秒間、起動メッセージを1行目に表示し、以後、2秒周期で1行目と2行目に交互にメッセージ表示を繰り返す ものです。
 (2010/08/22)微妙にソースを修正しました。(前のでも動作に問題なかったですが。)
 修正後の動作も確認しました。

/////////////////////////////////////
//  液晶表示テスト  test01.c       //
//  2010/08/22  PIC16F876  MikroC  //
/////////////////////////////////////

// CLOCK 10MHz
// _CP_OFF, _WRT_ENABLE_ON, _CPD_OFF, _LVP_OFF,
// _BODEN_ON, _PWRTE_ON, _WDT_OFF, _HS_OSC

#define lcd_port PORTB             //使用ポート(下位4bit使用)
#define lcd_rs PORTB.F4            //コマンド/データ選択ビット
#define lcd_stb PORTB.F5           //ストローブ信号

char lcd_addr[4] = {0x80, 0x88, 0xc0, 0xc8};   //表示位置
const char *lcd_msg1 = {"LCD TEST [ROM]"};     //文字列定数(ROM領域)
char *lcd_msg2 = {"LCD TEST [RAM]"};           //文字列変数(RAM領域)
const char *lcd_msg3 = {"              "};     //消去用文字列定数(ROM領域)
char *lcd_msg4 = {"              "};           //消去用文字列変数(RAM領域)
//////////  1文字表示
void lcd_data(char asci){
    lcd_rs = 1;                                         //データの書込操作
    lcd_port = (asci >> 4 & 0x0f) | (lcd_port & 0xf0);  //書込データ上位
    asm NOP;
    lcd_stb = 1;                                        //ストローブ立ち上げ;
    asm NOP;
    asm NOP;
    lcd_stb = 0;                                        //ストローブの立ち下げ
    lcd_port = (asci & 0x0f) | (lcd_port & 0xf0);       //書込データ下位
    asm NOP;
    lcd_stb = 1;                                        //ストローブ立ち上げ
    asm NOP;
    asm NOP;
    lcd_stb = 0;                                        //ストローブの立ち下げ
    Delay_us(40);
}
////////  コマンドの書込
void lcd_func(char cmd){
    lcd_rs = 0;                                         //コマンドの書込操作
    lcd_port = (cmd >> 4 & 0x0f) | (lcd_port & 0xf0);   //書込コマンド上位
    asm NOP;
    lcd_stb = 1;                                        //ストローブ立ち上げ;
    asm NOP;
    asm NOP;
    lcd_stb = 0;                                        //ストローブの立ち下げ
    lcd_port = (cmd & 0x0f) | (lcd_port & 0xf0);        //書込コマンド下位
    asm NOP;
    lcd_stb = 1;                                        //ストローブ立ち上げ
    asm NOP;
    asm NOP;
    lcd_stb = 0;                                        //ストローブの立ち下げ
    Delay_us(40);
}
///////  全消去
void lcd_clr(void){
    lcd_func(0x01);              //消去コマンド
    Delay_ms(2);
}
///////  文字列表示(RAM領域)
void lcd_str(char *str){
    while(*str != 0x00){        //文字列の終わり判定
        lcd_data(*str);         //文字列1文字出力
        str++;                  //ポインタ+1
    }
}
///////  文字列表示(ROM領域)
void lcd_str_rom(const char *stc){
    while(*stc != 0x00){        //文字列の終わり判定
        lcd_data(*stc);         //文字列1文字出力
        stc++;                  //ポインタ+1
    }
}
/////////  初期化コマンドの書込
void lcd_inicmd(char cmd){                              //com: 初期化コマンド
    lcd_rs = 0;                                         //コマンドの書込操作
    lcd_port = (cmd >> 4 & 0x0f) | (lcd_port & 0xf0);   //初期化コマンド
    asm NOP;
    lcd_stb = 1;                                        //ストローブ立ち上げ
    asm NOP;
    asm NOP;
    lcd_stb = 0;                                        //ストローブの立ち下げ
    Delay_ms(5);
}
/////////  初期化
void lcd_ini(void){
    Delay_ms(16);       //16mS
    lcd_inicmd(0x30);   //8bit mode set
    lcd_inicmd(0x30);   //8bit mode set
    lcd_inicmd(0x30);   //8bit mode set
    lcd_inicmd(0x20);   //4bit mode set
    lcd_func(0x28);     //DL=0 4bit mode,5x7dot
    lcd_func(0x0c);     //display on C=D=1 B=0
    lcd_func(0x06);     //entry I/D=1 S=0
    lcd_func(0x1);      //clear
}

/////////  メイン関数
void main(void){
    int i, j;
    char c;

    ADCON1 = 0xd;       //all digital
    TRISA = 0;          //all output
    PORTA = 0;
    TRISB = 0;
    lcd_ini();
    lcd_clr();
    lcd_func(lcd_addr[0]);
    lcd_str_rom("TEST_START");      //***** 表示0 *****
    Delay_ms(2000);
    lcd_clr();
    Delay_ms(2000);
    while(1){
        lcd_func(lcd_addr[0]);
        lcd_str_rom(lcd_msg1);      //***** 表示1 *****
        lcd_func(lcd_addr[2]);
        lcd_str(lcd_msg4);          //***** 表示2 *****
        Delay_ms(2000);
        lcd_func(lcd_addr[0]);
        lcd_str_rom(lcd_msg3);      //***** 表示3 *****
        lcd_func(lcd_addr[2]);
        lcd_str(lcd_msg2);          //***** 表示4 *****
        Delay_ms(2000);
    }
}

 上のプログラムでメイン関数以外は全て液晶表示関係の関数です。
 指定したポートの下位4ビットに接続するように作ってあります。
 コマンド/データ切り替え信号とストローブ信号は任意のビットを指定します。
 BUSYフラグは見ていません。
 リスト中にたくさんあるNOP命令は信号のセットアップ時間と最小パルス幅の規定により、入れています。
 特定のビットをONしてすぐにOFFするとパルス幅が不足して、誤動作する可能性があります。
 コンパイラの最適化によっては最悪「無かった事に」される可能性があります。
 液晶表示用関数はC18用に作ったものをMikroC用に若干、修正したもので、変更点はディレー関数だけです。
 あと、関数名の二重定義でエラーになったので、4個の関数名を変更しています。
 内2個は組み込み関数(マニュアルではライブラリーとなっているが)と名前が重複したためです。
 残り2個は、なぜ二重定義になるのか不明ですが、先に進まないので、名前を変えました。
 C18の時と同様に、文字列表示の関数がROM用とRAM用の2種類あります。
 液晶表示関係の関数だけ単独のソースファイルにまとめ、ハードウエアの位置指定をヘッダファイルに書けば使い回しが 出来ます。

 関数の違いによるメモリ使用量

実験回路

 サンプルプログラムは[***** 表示0 *****]、[***** 表示1 *****]、[***** 表示3 *****]で文字列定数表示用の関数を 使用し、文字列定数を渡しています。
[***** 表示2 *****]、[***** 表示4 *****]で変数表示用の関数を使い、初期化された文字列変数を渡しています。
 [***** 表示0 *****]では、このまま、どちらの関数も使えます。
 ただし、文字列変数用の関数を使用すると、ROMもRAMも多く使用します。
 [***** 表示1 *****]から[***** 表示4 *****]も両方の関数が使えます。
 ただし、文字列定数表示関数には文字列定数を、文字列変数表示用関数には初期化された変数を渡さないと型違いのエラー になります。
 初期化された変数を使用した時、ROMデータをRAMにコピーするのでRAMの使用量が増えます。
 コピーする処理の為、ROMの使用量も増えます。
 サンプルプログラムで表示データと関数を変えた場合、どのように使用メモリが変化するか試してみました。
 変数名にconst修飾子を付ければ文字列定数に、逆に文字列定数からconstを外せば変数になるはずです。

 全て文字列定数とした場合
 ROM使用量:524ワード
 RAM使用量:34バイト

 変数と定数が混在した場合(サンプルプログラム)
 ROM使用量:560ワード
 RAM使用量:62バイト

 全て初期化された変数とした場合
 ROM使用量:570ワード
 RAM使用量:97バイト

 表示動作は、どれでも同じです。

 テスト結果

 ROM空間にポインタが使えるようです。
 当然ながら、RAM領域のポインタと互換性は無く、混同するとエラーになります。
 上の結果のように文字列定数を表示した方がメモリ使用量が少なくて済みます。
 ただし、変数領域の文字列を表示しなければならない場合もあると思います。
 ROM用とRAM用の文字列表示関数を作って、適材適所に使用すれば、メモリの節約になります。


トップページ  「電子回路」のトップ