LCD1602(下図)は、ASCIIコード文字のフォントROMを持っていて、ASCIIコードを送るだけで対応した文字を表示してくれます。
上図は、「#10-3 LCDサブシステムを何とかせねば」で時計を表示した時のサンプルです。文字フォントは、横5ドット×縦8ドットで構成されており、7ビットASCIIコード文字と、手元のバージョンではS-JIS(Shift JIS)コードの半角カタカナ文字を搭載していました。
この他に、外字フォントを8文字分格納可能な外字RAM(CGRAM)を持っているので、今回は、この外字フォントを設定して使ってみたいと思います。
外字フォントも、横5ドット×縦8ドットで構成されています。これを、上の行から順に1バイトのデータに変換して、外字RAM(CGRAM)に書き込みます。
ビット5〜0 | データ | ||||
0 | 1 | 0 | 1 | 0 | 0x0a |
1 | 1 | 1 | 1 | 1 | 0x1f |
1 | 1 | 1 | 1 | 1 | 0x1f |
1 | 1 | 1 | 1 | 1 | 0x1f |
1 | 1 | 1 | 1 | 1 | 0x1f |
0 | 1 | 1 | 1 | 0 | 0x0e |
0 | 0 | 1 | 0 | 0 | 0x04 |
0 | 0 | 0 | 0 | 0 | 0x00 |
上図の様な「ハート形」っぽいマークなら、図柄の右に記載した16進数データがフォントデータとなります。一番下の行はカーソル用だということになっています。カーソルを表示する時、一番下の行に0x1fがORされるため、少なくとも1ドット以上の空白(0データ)を残しておかないとカーソルが見えなくなってしまいます。
外字フォントを書き込むにはCGRAMアドレスを指定して、上で作った8バイトのデータを順次書き込むことになります。「#3-1 LCD1602に表示する」で作成したlcd_write()関数を使うと、以下の様になります。
int cgnum;
const char *cgpat;
// set cgram address
rtncod=lcd_write (lcd_INST,lcd_SET_CRA + (cgnum<<3));
// write to cgram
for (idx=0; idx<8 && rtncod==ErrNo; idx++, cgpat++)
rtncod=lcd_write (lcd_DATA,(*cgpat));
変数cgnumは外字の番号で、手持ちのLCD1602では0〜7の8文字が設定可能となっています。ポインタcgpatで、フォントデータ8バイトの格納先を示しておく必要があります。
使用可能な外字はたったの8文字と言うことで使い道が微妙なのですが、特殊な記号とか簡単な漢字とか、うまく使うとインパクトのある表示にすることができると思います。
LCDサブシステムに組み込んでみましょう。
まず、そもそも、設定した外字を表示するにはどうするのか?と言う事ですが、LCD1602では外字領域として、ASCIIコードの0x00〜0x0fまでの16文字分が割り当てられています。設定可能な外字が8文字分と言うことで、前半8文字と後半8文字はミラーになっていて、例えば、0x01と0x09は同じ外字が表示されます。
LCDサブシステムとしての問題点は、制御コードと外字コード領域が重なってしまっているため、外字を表示しようとすると制御コードに引っかかって、別な機能が発動してしまうことです。
例えば、FFコード('\f'、フォームフィード)と言う制御コードはLCDサブシステムのバックログとLCD表示をクリアする制御コードに割り当たっているのですが、このコードは16進数で0x0cなので外字領域にあります。LCDサブシステム(lcd_subsys.ino)では制御コードの処理が優先されるため、外字(0x0c)を表示しようとしてコードを送るとLCDがクリアされてしまいます。
そこでLCDサブシステムとしては、外字コード領域を0x10〜0x1fへシフトして対応する事にしました。
後は、LCDサブシステムに表示しようとするI2Cマスタ側Arduinoから外字フォントを設定するためのプロトコルを定義しておく必要があります。外字フォント設定用の制御コードとしてBSコード('\b')を割り当てることにしました。以下の様なデータをI2C経由で受信すると、受け取ったデータに従って外字フォントを設定します。
"\b<cgnum><cgpat0>〜<cgpat7>":I2Cの1転送シーケンスで一気に送る(10バイト)。
外字フォント設定(CGRAM書き込み)に対応したLCDサブシステムのプログラムを以下にリンクしておきます。
LCDサブシステム:lcd_subsys.ino
このプログラムは、次節でリンクするLcdSubsysライブラリのexamplesにも同じものが含まれています。
外字フォントを表示したい場合には、例えば以下の様にして0x10〜0x1fのコードを送信すればOKです。
Lcds.println ("\x10\x11");
ここまでは、LCDサブシステムのプログラム(lcd_subsys.ino)が最新版になっていれば、旧バージョンのLcdSubsysライブラリでも使用可能でした。
今回追加で必要となるのが、外字フォントをCGRAMに書き込むメソッドです。
C言語の場合、'\x<2桁16進数>"の形式で文字列中にバイナリデータを書き込む書式があるので、これを使います。以下の様な記述によって外字フォントを設定する事が可能です。
Lcds.cgram (0, "\x0a\x1f\x1f\x1f\x1f\x0e\x04\x00");
// または、
Lcds.cgram (0, F("\x0a\x1f\x1f\x1f\x1f\x0e\x04\x00"));
F("〜")を使うと、文字列データがRAMを圧迫しないのでメモリ(RAM)の節約になります。
新バージョンのプログラムを以下にリンクしておきます。
LcdSubsysライブラリ:LcdSubsys.zip
インストール方法などについてはこちらをご覧ください。
これまで、LCDサブシステムの制御機としてArduino Microを使っていました。ところが先日、MicroのUSBコネクタが取れてしまったのです。
Microに搭載されているATmega32U4は内蔵RAMが2.5Kバイトと、UNO等のATmega328に比べて大きいのが特徴です。LCDサブシステムはホストから受信した表示データをバックログに記憶して表示するシステムなので、RAM容量が大きいということは、それだけ多くのデータを保持することができるということになります。
ピッタリな使い道だったのですが、壊れてしまいまいした(1;_T)。
USB端子が取れてしまったので、USBからの給電とプログラム(スケッチ)書き込みができなくなってしまいました。「もう捨てるしかないか」とも思ったのですが、ちょっと待って。「Arduino as ISP」と言う書き込み手段があったではないですか。
SPI通信を使って、プログラムを書き込んだりヒューズ値にアクセスしたりする方法です。この解説は「#9-4 ATmega328のヒューズ書き込み」に有ります。
UNOをISP(In System Programmer、書き込み装置)として使って、CGRAMに対応した新しいlcd_subsys.inoをMicroに書き込んでみましょう。
Arduino UNOとMicroを以下表の様に接続します。
Arduino UNO | 接続 | Arduino Micro |
5V | → | VCC |
GND | ⇔ | GND |
D10 | → | RST |
D11(MOSI) | → | D16(MOSI) |
D12(MISO) | ← | D14(MISO) |
D13(SCK) | → | D15(SCK) |
Arduino UNOをUSBでPCと繋いだら、Arduino IDEを使って以下手順でMicroへlcd_subsys.inoを書き込みます。
USBからの書き込みができなくなってしまったので、頻繁に再書き込みが発生する「プログラムのデバッグ」に使うのは難しくなってしまったけど、他のArduino(Pro Mini等)でデバッグしたlcd_subsys.inoを書き込んで制御用に使うだけなら十分使えるでしょう。
Microのヒューズ値変更が必要です。ヒューズ値は、プログラムを書き込むたびに変更する必要は無く、1回書き換えればその状態が保持されます。上の接続を維持したまま、作業を進めます。
ターゲットとしてATmega32U4に対応した、最新版のser_prog.inoをArduino UNOへコンパイル&書き込み、UNOのシリアルモニタを開きます。
実験サンプル:【 】内はシリアルモニタからの送信データ
OK: serial programming mode
OK: ATmega32U4 detected
RDY
【dmpb】
OK+ fuse: 0xcb 0xd8 0xff
OK+ lock: 0xff
OK+ cali: 0x65
OK: sign: 0x1e 0x95 0x87 0xff
【wrfh,df】
OK? to write 0xDF to fuse.h? send 'y' or 'n' > Yes, go
【y】
OK: write fuse.h=0xdf
変更したヒューズHの値はブートローダに関する設定です。ArduinoISP.inoでプログラムを書き込んだ場合、ブート領域が消去されてしまっているので、元の設定ではリセット解除後、何も書き込まれていないブート領域に分岐してしまって正しく動作しません。
書き換えたヒューズ値によって、リセット解除後アプリケーション領域から実行開始するので、書き込んだプログラムが実行されるようになります。
lcd_subsys.inoをPro MiniとMicroでコンパイルしてRAMの空き容量を比較してみたところ、以下の様になりました。
ボード |
RAM使用量 [バイト] |
RAM空き容量 [バイト] |
Arduino Pro Mini (ATmega328) | 547 | 1,501 |
Arduino Micro (ATmega32U4) | 691 | 1,869 |
搭載するRAM容量としては、ATmega328が2Kバイト、ATmega32U4が2.5Kバイトなので、ATmega32U4の方が512バイト程大きいのですが、コンパイル後の空き容量としては368バイト程の差にしかなりませんでした。USB関係のファームウェアで使っている分か何かでしょう(?)。それでも、1行40文字のバックログだと9行以上多く記憶できるだけの差分ですので、壊れたMicroの再就職先としては良いのではないでしょうか?
いよいよ、LCDサブシステムとLcdSubsysライブラリを使って、LCD1602に外字を表示してみる実験です。「#10-3 LCDサブシステムを何とかせねば」辺りの記事を参考に、Arduino UNOとLCDサブシステムを接続して実験します。
方法は自由ですが、何らかの手段を使って外字フォントを作成します。筆者は下図の様にExcelの機能と数式を使って外字フォントを作成しました。
これを以下の様にして、プログラムに埋め込んでおきます。
// 定義
Lcds.cgram (0,F("\x0a\x1f\x1f\x1f\x1f\x0e\x04\x00"));
Lcds.cgram (1,F("\x0e\x1f\x15\x1f\x1f\x1b\x0e\x00"));
Lcds.cgram (2,F("\x00\x04\x06\x1f\x06\x04\x00\x00"));
Lcds.cgram (3,F("\x00\x04\x0c\x1f\x0c\x04\x00\x00"));
Lcds.cgram (4,F("\x08\x0c\x0e\x0f\x0e\x0c\x08\x00"));
Lcds.cgram (5,F("\x02\x06\x0e\x1e\x0e\x06\x02\x00"));
Lcds.cgram (6,F("\x01\x02\x04\x08\x1f\x00\x1f\x00"));
Lcds.cgram (7,F("\x10\x08\x04\x02\x1f\x00\x1f\x00"));
// 表示
Lcds.println ("0:\x10 1:\x11 2:\x12 3:\x13");
Lcds.println ("4:\x14 5:\x15 6:\x16 7:\x17");
全体の実験用プログラムをリンクしておきます。
実験用プログラム:test_lcd_cgram.ino
これをUNOへコンパイル&書き込みます。デザインした外字がLCDサブシステム(LCD1602)に表示されたら成功です(以下図)。
壊れてしまったMicroに再び息を吹き込むことができました。元々、LCD表示だけにArduinoを1機丸々使うという贅沢なLCDサブシステムだったので、壊れて本来の機能をフルに発揮できなくなってしまったMicroの使い道としては、良き😋。