体験!マイコンボードで組込みLinux

第14回組込みLinuxでLCDを制御してみよう

LCDについて

LCDの概要

現在はLCDが普及して入手しやすい状況になってきました。入手しやすくなったのはいいことですが、グラフィックLCDに関してはコントローラが多種多様なので、コントローラごとに制御ソフトウェアを開発する必要があります。

Android端末並みに豪華な表示インターフェースが必要な場合はグラフィックLCDを使いますが、単純に制御用コントローラで文字情報を表示するだけならばキャラクタLCDでじゅうぶんです。キャラクタLCDのコントローラとしては日立製HD44780がデファクトスタンダードとなりました。

現在、普及しているキャラクタLCDもHD44780互換コントローラを搭載しており、HD44780用の制御ソフトウェアさえ開発しておけば、LCDのメーカーに関係なく同じソフトウェアで表示制御ができます。

LCDのスペック

今回、対象とするLCDは図1に挙げる16文字が2行(計32文字)の表示能力をもったもので、以下のサイトから単価500円で購入できます。

LCDキャラクタディスプレイモジュール(16×2行バックライト無)
URL:http://akizukidenshi.com/catalog/g/gP-00040

上記サイトでは、スペックの概要が記載されており、購入をすると詳細なデータシートが添付されています。

図1 LCDモジュール
図1 LCDモジュール

対象とするLCDのデータバス本数は8本ですが、その半分の4本でも制御することは可能なので、通常は配線コストを下げることができる4本でのデータバスで制御されることが多いです。HD44780はかなり昔のコントローラなので電源電圧が5Vとなっており、現在に普及している互換コントローラも多くは5V電源となっています。今回、対象とするLCDも5V電源となっています。

SH7706LSRとの接続

電源電圧の整合

一昔前まではデジタル回路の電源電圧は5V系統で統一されていましたが、現在では3.3V系統にシフトしてきました。SH7706LSRは3.3V系統にシフトした時期にリリースされたマイクロコンピュータなので内部電源1.8V、外部インターフェース電源は3.3Vです。

3.3V系統にシフトした時期以降のコントローラは3.3V電源なのでSH7706LSRとの接続に問題はないですが、今回対象となるLCDのような3.3V系統にシフトした時期以前のコントローラは5V電源なのでなので、そのままでは接続ができません。

SH7706LSRのような3,3V系統のマイコンボードに5V電源系統のような異なる電源系統どうしを接続する場合、一番標準的な方法としては、図2のような電圧変換回路を間にはさむ必要があります。

図2 3.3V系と5V系の接続
図2 3.3V系と5V系の接続

後継互換コントローラの実際

オリジナルのHD44780は完全に5V電源になっています。今は現物のHD44780が使われることはなく、後継の互換コントローラとなっています。それらの電源スペックの主流は5V電源となっていますが、実はコントローラ内部は3.3V系統インターフェースに対応している場合が多く、今回対象とするLCDも3.3V系統インターフェース対応の5V電源コントローラです。なので、LCDの電源を3.3VにしてSH7706LSRとLCDを直接に接続して制御を行うことができます。

コントローラ自体が3.3V系統インターフェースに対応しているにもかかわらず、なぜ5V電源を要求しているかというと理由があります。LCDモジュールの中で5Vの電圧の要求している部分は、コントローラではなく物理的な液晶そのものを駆動させている部分なので5V電圧でないといけないわけです。

SH7706LSRとの接続方法について

SH7706LSRとの接続は、図3のようにSH7706LSRとLCDの電源を両方に3.3V電源に統一します。

図3 SH7706LSRとの接続(3.3Vに統一)
図3 SH7706LSRとの接続(3.3Vに統一)

液晶駆動用に別途、負電源発生回路を追加して約-2Vの電圧を液晶駆動用端子追加します。そのことにより、液晶そのものを駆動させている部分にのみ最大5.3Vの電圧がかかるようになります。もっとも、別途5V電源が供給できる場合は、図4のようにSH7706LSRは3.3V電源系統でLCDは5V電源系統でも直結は可能です。

図4 SH7706LSRとの接続(3.3V/5V併用)
図4 SH7706LSRとの接続(3.3V/5V併用)

ただし、注意しなければいけない点は、SH7706LSRからLCDへのアクセスは問題ないですが、LCDからSH7706LSRへのアクセスするとLCD系統からの5V電圧が3.3V定格であるSH7706LSRにかかりマイコンボードが破壊する可能性があります。そのため、制御ソフトウェアではLCDへの書き込みのみでLCDからの読み込みをしないようにコーディングして、LCDのアクセス方向を決めるR/W端子をWrite専用に固定する必要があります。

負電源(マイナス電源)発生回路

負電源発生回路は本格的なものとなると複雑になってしまいますが、今回は簡易なものでじゅうぶんなので、図5のような非常に簡素な回路でもLCDで対応可能となっています。

図5 負電源発生回路
図5 負電源発生回路

原理としては単純であり、パルス出力側のコンデンサ(C1)に正電圧により一旦、正電荷をチャージします。C1にチャージされた電荷はショットキーダイオードを通して負電圧出力側のコンデンサ(C2)にも転送されます。

両方のコンデンサ間の経路はD1経由とD2経由がありますが、C1からC2の経路ではD2のダイオードのみ電流を通します。そのため、C2ではGND経由からのみ正電荷をチャージされるので、C2では相対的にGND側がプラスで反対側(負電圧出力端子)はマイナスになるので、負電源が供給されるわけです。

SH7706LSRではリアルタイムクロック出力端子があるので、それをパルス出力端子とすれば、図6のようにさらに回路を簡素化することができます。

図6 負電源発生回路(SH7706LSR簡易版)
図6 負電源発生回路(SH7706LSR簡易版)

SH7706LSRとの接続例

SH7706LSRとLCDの接続は汎用I/Oポートで行うので、任意のI/Oポート端子でかまいません。ここでは図7のように接続します。

図7 SH7706LSRとLCDの接続
図7 SH7706LSRとLCDの接続

基本的に端子は任意でいいですが、負電源回路へのパルス出力端子は TCLK端子固定でなければなりません。

LCDデバイスドライバ

ソフトウェアとハードウェアの分担

マイクロコンピュータと周辺コントローラの間でのバスインターフェース接続では、図8のように負論理であるチップセレクト端子がロー(有効)のときのみデータが有効になり、データのアクセスが行われます。

図8 標準的なインターフェース
図8 標準的なインターフェース

図8のような標準インターフェースでは、そのようなアクセス処理はマイクロコンピュータのハードウェアが自動的に行うので、特にデバイスドライバのほうで処理を行う必要はありません。

しかし、HD44780は非常に古風なインターフェースであり、図9のようにE端子(チップセレクト)がハイからローに変化するときにデータが有効になるような独特なバスインターフェースのため、SH7706プロセッサのバスコントローラでHD44780を扱うことができません。そのため、通常はハードウェアが行うようなバスインターフェースの処理もデバイスドライバで面倒を見なければいけません。

図9 HD44780のインターフェース
図9 HD44780のインターフェース

LCDに文字を出力する概要

HD44780の詳細な使い方については液晶購入時に同時に添付されるデータシートを参照してください。図7の接続回路に対応したLCDデバイスドライバのソースコードはリスト1の通りとなります。

リスト1 LCDデバイスドライバのプログラム

  1    #include <linux/module.h>
  2    #include <linux/init.h>
  3    #include <linux/device.h>
  4    #include <linux/ctype.h>
  5    #include <linux/poll.h>
  6    #include <linux/delay.h>
  7    #include <asm/io.h>
  8    
  9    #define PFC_PDCR        0xa4000106UL
 10    #define PFC_PECR        0xa4000108UL
 11    #define PORT_PDDR       0xa4000126UL
 12    #define PORT_PEDR       0xa4000128UL
 13    #define TMU_TOCR        0xfffffe90UL
 14    
 15    static int      lcd_major;
 16    
 17    // PTD6
 18    static void lcd_enable(int enable) {
 19            if(enable) {
 20                    ctrl_outb(ctrl_inb(PORT_PDDR) | 0x40, PORT_PDDR);
 21            } else {
 22                    ctrl_outb(ctrl_inb(PORT_PDDR) & ~0x40, PORT_PDDR);
 23            }
 24    }
 25    
 26    // PTD7
 27    static void lcd_rs(int rs) {
 28            if(rs) {
 29                    ctrl_outb(ctrl_inb(PORT_PDDR) | 0x80, PORT_PDDR);
 30            } else {
 31                    ctrl_outb(ctrl_inb(PORT_PDDR) & ~0x80, PORT_PDDR);
 32            }
 33    }
 34    
 35    // D4=PTE1 D5=PTE3 D6=PTD5 D7=PTD1
 36    static void lcd_data(int data) {
 37            if(data & 0x10) {
 38                    ctrl_outb(ctrl_inb(PORT_PEDR) | 0x02, PORT_PEDR);
 39            } else {
 40                    ctrl_outb(ctrl_inb(PORT_PEDR) & ~0x02, PORT_PEDR);
 41            }
 42            if(data & 0x20) {
 43                    ctrl_outb(ctrl_inb(PORT_PEDR) | 0x08, PORT_PEDR);
 44            } else {
 45                    ctrl_outb(ctrl_inb(PORT_PEDR) & ~0x08, PORT_PEDR);
 46            }
 47            if(data & 0x40) {
 48                    ctrl_outb(ctrl_inb(PORT_PDDR) | 0x20, PORT_PDDR);
 49            } else {
 50                    ctrl_outb(ctrl_inb(PORT_PDDR) & ~0x20, PORT_PDDR);
 51            }
 52            if(data & 0x80) {
 53                    ctrl_outb(ctrl_inb(PORT_PDDR) | 0x02, PORT_PDDR);
 54            } else {
 55                    ctrl_outb(ctrl_inb(PORT_PDDR) & ~0x02, PORT_PDDR);
 56            }
 57    }
 58    
 59    static void lcd_out8(int data) {
 60            lcd_enable(1);
 61            lcd_rs(0);
 62            lcd_data(data);
 63            udelay(83);
 64            lcd_enable(0);
 65            udelay(4000);
 66    }
 67    
 68    static void lcd_out4(int rs, int data) {
 69            lcd_enable(1);
 70            lcd_rs(rs);
 71            lcd_data(data);
 72            udelay(83);
 73            lcd_enable(0);
 74            udelay(83);
 75            lcd_enable(1);
 76            lcd_data(data  77            udelay(83);
 78            lcd_enable(0);
 79            if(rs == 0) {
 80                    udelay(4000);
 81            } else {
 82                    udelay(83);
 83            }
 84    }
 85    
 86    static ssize_t lcd_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) {
 87            ssize_t n, d;
 88            unsigned char   c;
 89    
 90            lcd_out4(0, 0x01);
 91            d = 0;
 92            for(n = 0;n < count;n++) {
 93                    copy_from_user(&c, buf + n, 1);
 94                    if(c >= ' ') {
 95                            if(d == 16) lcd_out4(0, 0x80 + 0x40);
 96                            if(d  97                            d++;
 98                    }
 99            }
100            return count;
101    }
102    
103    static const struct file_operations lcd_fops = {
104            .owner  = THIS_MODULE,
105            .write  = lcd_write,
106    };
107    
108    #define CHRDEV "lcd"
109    static int __init lcd_init (void) {
110            lcd_major = register_chrdev(0, CHRDEV, &lcd_fops);
111            ctrl_outw(0x5404, PFC_PDCR);
112            ctrl_outw(0x0044, PFC_PECR);
113            ctrl_outb(0x01, TMU_TOCR);
114            lcd_rs(0);
115            lcd_enable(0);
116            lcd_data(0);
117            udelay(4000);
118            lcd_out8(0x30);
119            udelay(4000);
120            lcd_out8(0x30);
121            udelay(4000);
122            lcd_out8(0x30);
123            udelay(4000);
124            lcd_out8(0x20);
125            lcd_out4(0, 0x28);      // Function set
126            lcd_out4(0, 0x0c);      // Display on
127            lcd_out4(0, 0x06);      // Entry mode
128            lcd_out4(0, 0x01);
129            printk(KERN_INFO "LCD Device Driver\n");
130            return 0;
131    }
132    
133    static void __exit lcd_cleanup (void) {
134            unregister_chrdev(lcd_major, CHRDEV);
135            ctrl_outb(0x00, TMU_TOCR);
136            printk(KERN_INFO "LCD Device Driver Exit\n");
137    }
138    
139    module_init(lcd_init);
140    module_exit(lcd_cleanup);
141    
142    MODULE_LICENSE("GPL");

LCD制御で行う処理の概要は、LCD初期化時にデータシート記載のとおりに初期化のおまじないを行います(リスト1の114~128行目⁠⁠。初期化が済めばあとはデータバスにデータをLCDに対して送るだけとなります。RS端子がローの場合はLCD表示制御となり、RS端子がハイの場合は文字出力となります。

LCDに対して文字を出力するには、RS端子をハイにしておいて、データバスにASCIIコードを出力するだけで、図9のようにE端子をハイからローにすると実際にLCDに文字が表示されます。今回の回路がデータバスが4本のため、1回目に8ビットデータの下位4ビットを送付し、2回目に8ビットデータの上位を送付します(リスト1の68~84行目⁠⁠。

デバイスドライバインターフェース

今回はデータ出力処理のみなので、デバイスドライバインターフェースはlcd_write関数を書き込み関数として登録するだけです(リスト1の103~106行目⁠⁠。

OSからデータ書き込みリクエストがあるとlcd_write関数が呼び出されます。リスト1の90行目で表示位置を先頭にし、データ数だけcopy_from_user関数でユーザー領域から特権領域へデータをコピーしてASCIIコードをLCDに送信します。表示能力は32文字なのでリスト1の96行目では32バイト分以下のASCIIコードをLCDに送信しています。

LCDの1行あたりの文字数は16文字なので16文字目でリスト1の95行目ではLCDでの改行処理をしています。ASCIIコードでスペースより小さいコードは制御コードなので、リスト1の94行目で制御コードを出力しないようにしています。

その他補足事項

最下位層であるLCD端子の個別制御はそれぞれ次の3つの関数で行っています。

CDのE端子の制御はリスト1の18~24行目で行なっています。

LCDのRS端子の制御はリスト1の27~33行目で行なっています。

4本のデータ端子の制御はリスト1の36~57行目で行なっています。

LCDの制御は汎用I/Oポートで行うので、初期化処理にはSH7706プロセッサで対象となる端子を汎用I/Oポートに設定する処理を、リスト1の111~112行目で行っています。また、今回は負電源回路へのパルス出力が必要なので、パルス出力処理が有効になるようにリスト1の113行目で処理を行なっています。

LCDデバイスドライバのコンパイルと実行

LCDデバイスドライバのコンパイルには、すでにコンパイル済みのカーネルソースコードが必要となります。コンパイル済みのカーネルソースコードは任意の場所でいいですが、コンパイルをするMakefileリスト2で -C オプションの後にコンパイル済のカーネルソースコードの場所を記述しなければなりません。

リスト2 Makefile

 1     TARGET:= lcd.ko
 2     
 3     all: ${TARGET}
 4     
 5     lcd.ko: lcd.c
 6             make ARCH=sh CROSS_COMPILE=sh3-linux- -C ../linux-2.6.28.10 M=`pwd` modules
 7     
 8     clean:
 9             make ARCH=sh CROSS_COMPILE=sh3-linux- -C ../linux-2.6.28.10 M=`pwd` clean
10     
11     obj-m:= lcd.o
12     
13     clean-files := *.o *.ko *.order *.mod.[co] *.markers *~

コンパイルは以下のように行います。

$ make
make ARCH=sh CROSS_COMPILE=sh3-linux- -C ../linux-2.6.28.10 M=`pwd` modules
make[1]: Entering directory `/home/general/linux-2.6.28.10'
  CC [M]  /home/general/lcd/lcd.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/general/lcd/lcd.mod.o
  LD [M]  /home/general/lcd/lcd.ko
make[1]: Leaving directory `/home/general/linux-2.6.28.10'
$

SH7706LSRボードでLCDデバイスドライバを組み込む場合は insmodコマンドで引数にLCDデバイスドライバを指定します。

比較的新しいLinuxカーネルの場合は自動でデバイスファイルを生成してくれますが、古いカーネルの場合はリスト3のようなスクリプトをデバイスドライバ組み込み後に実行をします。

リスト3 デバイスファイルを生成するシェルスクリプト

1 /bin/sh
2 
3 r item in `cat /proc/devices`;
4 
5 if [ $item = lcd ]; then
6   mknod /dev/lcd c $major 0
7 fi
8 major=$item
9 ne

次回は

次回はSH7706LSRボードでセルフコンパイル可能なSH3用のgccの移植について解説をします。

おすすめ記事

記事・ニュース一覧