前回お迎えしたM5StickC Plus(下図)のTFTディスプレイに、日本語を表示してみようと思います。
M5StickC Plusはオールインワンで、専用のライブラリもそろっています。基本的には、回路設計も、直接周辺モジュールを制御する必要もないので、整備されたライブラリを活用して、楽にプログラミングして行く方針としました。まずは、M5StickC Plusのライブラリで日本語表示できないのか?調べてみます。
Arduino IDEでインストールしたM5StickC Plusライブラリの中に、日本語フォントらしきものは見つけました。しかし、使い方が判りません。
ライブラリが一通りそろっていて、ライブラリ関数を叩くだけで、ほとんどの機能にアクセスできるのは嬉しいのだけど、使い方のドキュメントみたいなものが無いので、結局、ライブラリのソースを追いかけることになります。追いかけてみると大量のソースファイルが関係している上に、マクロによる場合分けの、どの部分が活きていて、どの部分がマスクされているのか判らないので解析もままなりません。
他人のライブラリは中身を知らずにブラックボックスで使える内は楽だけど、その為にはドキュメントが揃っていないとダメだ〜。
中途半端に他人のライブラリを使うくらいなら、一から自分で作ってしまった方がストレスを感じなくて良いので、いつもはそうしています。しかし、今回のM5StickC Plusは、ボード自体がオールインワンな上にライブラリもそろっているとあって、ライブラリを活用して、なるべく楽にアプリケーションを作成しようと決めました。
なので、ライブラリの日本語表示ができない(使い方が不明)なら、そこ「だけ」は自分で起こします。TFTディスプレイの初期化関数とか描画関数とか、そういう基本的な部分はライブラリを活用したいと思います。そのくらいは、まともなドキュメントが無くてもライブラリのサンプルソースやヘッダファイルを眺めるだけで粗方の使い方は解ります。
まずは、ライブラリを使って、普通の英数フォントを表示してみることから始めてみましょう。
「ファイルメニュー ⇒ スケッチ例 ⇒ M5StickCPlus ⇒ Basics ⇒ HelloWorld」等のサンプルコードを参照すると、使い方はすぐに分かりました(↓)。
M5.begin ();
M5.Lcd.setTextSize (3);
M5.Lcd.setRotation (3);
M5.Lcd.print ("hello, world");
まあ、簡単(1^^)。素晴らしい。
使い方は簡単だったので、「setTextSize()」を0〜5まで振ってみました(下図)。
どうやら、setTextSize (0)とsetTextSize (1)は、同じ大きさで描画されました。setTextSize (2)は最小サイズの×2倍角、以降、setTextSize (N)は×N倍角描画みたいですね。
setTextSize (1)の標準フォントは、6×8[ドット]くらいでしょうか?ものすごく小さいです。老眼には辛すぎる大きさです。ルーペ🔍で拡大して確かめてしまいました。
後、上の写真で、なんとなく判るかもしれませんが、TFTディスプレイの上下左右端の1〜数ドットは、プラスティック筐体の窓と重なっている部分があって、欠けてしまう場合があります。アプリケーションを作る時は少し余裕をもって、中央寄りを使うようにした方が良いですね。
前節の予備実験で、TFTディスプレイの高精細っぷりを目の当たりにしてしまいました。小さいフォントだと、小さすぎて読めません。
フォントサイズを大きくすると、文字は細部まで綺麗に再現可能ですが、その代わりフォントデータ量が増大してしまいます。データ量の増加は現実的な範囲内で抑えつつ、なるべく綺麗なフォントを使いたいと思います。
M5StickC PlusのTFT画面サイズは、240×135[ドット]です。画素数をそれぞれ素因数分解してみると、横/縦とも、素因数3×5=15を含みます。
共通の素因数3×5=15を使って、フォントサイズを15×15[ドット]にすると、TFT画面にちょうど16桁×9行表示できます。縦画面で使っても横画面で使っても切りが良く、とても親和性が高いので、フォントサイズは15×15[ドット]に決定します。
フォントにも、使用する時のライセンス条項のくっついたものがあるので、フリーのフォントで15ドット(以下)のフォントを探して見ました。すると、「JFドット東雲ゴシック14」というフォントを見つけました(以降、「JF東雲14」と略します)。ライセンスはパブリックドメインです。
JFドット東雲ゴシック14フォントを検索する(別ウインドウが開きます)
次節でフォントデータを生成するためには、BDF形式のフォントファイル(.bdf)が必要ですので、探してみてください。Windows用のフォント(.ttf)は対象外です。
描画してみると判りますが、JF東雲14フォントは14×14[ドット]フォントで、枠内全体を使って文字がデザインされています。つまり余白が無いので、そのまま14×14[ドット]で描画すると、上下左右に隣接する文字とくっついて見栄えが悪くなってしまうのです。
横/縦方向に1ドットづつ余白を追加すると、余白付きフォントとしては15×15[ドット]で使うことができます。なんと!丁度、探していた15×15[ドット]という、中途半端なドット数のフォントを見つけてしまいました(1^−^)v。
※フォントの中には、フォントサイズの中に余白を含んでいる物もあります。そういうフォントでは、目いっぱい15×15[ドット]で使えば良いです。上の議論は、フォントサイズの中に余白の無いフォントの場合です。
これから作成するプログラムで描画する時に余白1ドットを追加するので、フォントデータとしては14×14[ドット]分のデータ領域で充足します。と言っても、横方向は8(ビット)の倍数にしておかないとデータのハンドリングが面倒なので、1文字のデータサイズは、2バイト×14ドット=28バイト/文字となります。
第一・第二水準漢字(1区〜84区)に対応しようとすると、94点×84区×28バイト/文字≒216Kバイトとなります。M5StickC PlusのArduinoでは、プログラム用のFlashメモリとして1,310,720バイト(=1,280Kバイト)使用できるみたいなので、容量的には問題ありません。
M5StickC Plusのプログラムに組み込んでROM化するため、フォントデータを作成しましょう。
日本語フォントとしては、全角フォントと、それに対応した半角フォントが必要となります。M5StickC Plusライブラリが持っている英数(半角)フォントは6×8[ドット]なので、サイズが合いません。全角フォントとバランとの取れた半角フォントを持つ必要がありますので、半角フォントもJF東雲14の半角フォントを使うべきでしょう。
過去の記事と同様に、JIS規格の区点コード表に従ってフォントデータを保持します。区点コード表はこちらです。
区点コード:
シリアル区点コードを使って、0x0300〜0x03ffの範囲が半角フォントの定義領域とします。ASCIIコード+0x0300で、フォントデータを引き出します。0x0300〜0x03ff範囲は、現時点のJIS規格では未定義のコード領域であり、文字の割り当てが無いので、有効活用します。
C言語でフォントを使うため、いよいよJF東雲14フォントをC言語のデータ配列形式に変換します。これには、Olive+のスクリプトを使うことができます。
フォントデータ生成スクリプト:m5cp_jpn_font.olv
※スクリプト(.olv)の実行には拙作Olive+をWindowsにインストールしてください。
必要なのは、前節で検索したBDF形式のJF東雲14フォントファイル(.bdf)と、Olive+です。配布元次第ではファイル名が異なっているかもしれませんが、当方がダウンロードしたJF東雲14フォントのファイル名は以下の通りでした。
// constants
macro IFILE="shnmk14.bdf"; // 全角フォントファイル
macro IFILER="shnm7x14r.bdf"; // 半角フォントファイル
スクリプト(m5cp_jpn_font.olv)先頭付近のマクロに、上の様にしてファイル名を指定してください。スクリプトを実行すると、「m5cp_jpn_font.cpp」と言うファイルが完成するので、後ほどリンクするプログラムのフォルダにコピーしてからコンパイルしてください。
JF東雲14フォントは、第一・第二水準漢字(1区〜84区)までカバーしているようです。
以下の記事では、Arduino DUEを使い、TFTシールドに日本語を表示しました。
「#12-3 DUE+TFTで日本語表示」
TFTシールドのドライバチップはILI9328でした。ドライバチップのデータシートを探し、制御方法(バスI/F)を調べ、と、調査だけでも大変でした。
今回のM5StickC PlusのTFTディスプレイは、ドライバチップにST7789を搭載しています。ですが、今回はライブラリを活用するので、以下メソッドで終わりです。
M5.Lcd.drawBitmap (<開始x>,<開始y>,<サイズx>,<サイズy>,<データ配列>)
ライブラリでは、上位ビットからR5-G6-B5形式の16ビットパックドピクセルを採用しているので、<データ配列>はuint16配列です。<サイズx>×<サイズy>個のピクセルデータを用意すればOKです。以下、該当箇所のコードを抜粋しておきます。
// draw character
fnt=&tft_prn_font_kz[codkt*tft_FNT_SIZ];
idx=0;
for (iy=0; iy<tft_CHR_SIZ; iy++)
{
if (iy>=tft_FNT_SIZ)
dat=0;
else
{
dat=(*fnt);
fnt++;
}
for (ix=0; ix<siz; ix++)
{
if (dat&0x8000U)
tft_bitmap[idx]=tft_prn_fg;
else
tft_bitmap[idx]=
(tft_prn_fg==tft_prn_bg)? ~tft_prn_fg: tft_prn_bg;
dat <<= 1;
idx++;
}
}
// draw
M5.lcd.drawBitmap (tft_prn_lx,tft_prn_ly,siz,tft_CHR_SIZ,
&tft_bitmap[0]);
ちなみに、TFTシールド(ILI9328)のピクセルは上位ビットからB5-G6-R5の順だったので、RとBのビット位置が違っていました。最初、赤く文字を描画しようとしたら青く描画された、って言う、それだけのこと……。
以下に日本語を含む文字列描画関数の目標仕様をまとめておきます。
tft_kprint()関数目標仕様:
アプリケーションに合わせてサイズを変えながら、自由に文字列を配置できるので、ユーザインターフェイスの設計を容易にしてくれる事でしょう。
以下表に制御コードの一覧を示しておきます。
制御コード一覧表: | ||
制御コード | 機能 | 動作説明 |
\r | CR(復帰) | カーソルが同じ行の左端に移動する。 |
\n |
LF(改行、 カーソル下移動) |
カーソルが1行下の行に移動する。 横位置は変化しない。 |
\v | カーソル上移動 |
カーソルが1行上の行に移動する。 横位置は変化しない。 |
\b | カーソル左移動 | カーソルが同じ行の1文字左に移動する。 |
\t | カーソル右移動 |
カーソルが同じ行の1文字右に移動する。 ※TABコードと同じだが、動作は異なるので注意。 |
\f | 画面クリア | 画面をクリアして、カーソルを(0,0)に設定する。 |
\x10 \x<HEX2> |
カーソル横座標指定 |
カーソルの横座標を<HEX2>に設定する。 <HEX2>は2桁の16進数で、0〜255。 TFTディスプレイの横サイズでクリッピングされる。 |
\x11 \x<HEX2> |
カーソル縦座標指定 |
カーソルの縦座標を<HEX2>に設定する。 <HEX2>は2桁の16進数で、0〜255。 TFTディスプレイの縦サイズでクリッピングされる。 |
\x12 \x<HEX2H> \x<HEX2L> |
前景色指定 |
文字の描画ピクセル値を <HEX2H>×256+<HEX2L>に設定する。 <HEX2H>,<HEX2L>は2桁の16進数で、0〜255。 |
\x13 \x<HEX2H> \x<HEX2L> |
背景色指定 |
背景の描画ピクセル値を <HEX2H>×256+<HEX2L>に設定する。 <HEX2H>,<HEX2L>は2桁の16進数で、0〜255。 |
\x14 \x<HEX2X> \x<HEX2Y> |
フォントサイズ指定 |
フォントサイズを横<HEX2X>、縦<HEX2Y>に設定する。 <HEX2X>,<HEX2Y>は2桁の16進数で、0〜255。 いずれも、フォントサイズ15を下回る数値を指定しても、 15と見なす。 |
なお、制御コードで設定したカーソル座標などの情報は、ライブラリ内static変数に保持されるので、次に再設定されるまで効果を維持しています。
では、完成したプログラムを以下にリンクしておきます。
完成プログラム: | |||
m5cp_kprint.ino | 実験用のサンプルプログラム | ||
m5cp_jpn.cpp | 日本語文字列描画関数等 | ||
m5cp_ps.h | ヘッダファイル | ||
m5cp_jpn_font.cpp | フォントファイル(※) |
※本ページでは配布しないので、スクリプトを使って各自生成してください。
今回は複数のソースリストに分かれているので、INOファイルと同じ名前のディレクトリ「m5cp_kprint」を作り、その中にすべてのソースファイルを放り込んだら、Arduino IDEでINOファイル(m5cp_kprint.ino)を開いてコンパイルしてください。
コンパイルしてM5StickC Plusに書き込むと、シリアルモニタから以下のコマンドを受け付けています。
シリアルモニタコマンド仕様:
tft_kprint()関数は、S-JISコードを対象としているのですが、Arduino IDEではS-JIS文字列を扱えないので、描画する文字列をどうやって指定するのか?と言う課題があります。Arduino DUEで日本語表示した時の解決策については、こちらをご確認ください。
今回、もう少し手軽に使える妙案を思いついたので、別案を試してみました。
C言語では、文字列定数の中に「\x<HEX2>」の書式で、バイナリデータを埋め込むことができる機能があります。S-JISで符号化された文字列をバイナリデータと見なして「\x<HEX2>」形式に変換すれば、unicodeなArduino IDE(GCC)でも、S-JIS文字列を使ったC言語ソースファイルのコンパイルができます。
S-JIS文字列を「\x<HEX2>」形式に変換するのは、Olive+スクリプトの力を借りましょう。
変換スクリプト:sjis2ccstr.olv
※スクリプト(.olv)の実行には拙作Olive+をWindowsにインストールしてください。
スクリプトを実行すると、文字列を入力するウインドウが開きます。
所望の文字列を入力後、OKボタンを押すと、変換後の「\x<HEX2>」形式文字列が表示されるので、以下の様にしてC言語ソースリストに組み込んで使います。
tft_kprint (
"JIS\x91\xe6\x88\xea\x81\x45\x91\xe6"
"\x93\xf1\x90\x85\x8f\x80\x8a\xbf"
"\x8e\x9a\x91\xce\x89\x9e"
/* JIS第一・第二水準漢字対応 */);
S-JISの2バイトコード部分だけが「\x<HEX2>」形式のバイナリデータに置き換わっています。S-JIS半角カタカナコードにも対応していますので、ご使用いただけます。
M5StickC PlusをUSBケーブルでPCに接続してください。オールインワンなので、これだけで準備完了です。
こちらの実験用プログラムをコンパイルしてM5StickC Plusに書き込んでください。
以下、実験中の様子です。
以下の実験は、S-JIS形式で通信可能なTera Term等のソフトウェアで実施してください(こちらも参照)。
実験サンプル:【 】内はTera Termからの送信データ
【init】
OK: init
【kprn,\r\n こんにちは、世界\r\n】
OK: kprint
画面を初期化して、「こんにちは、世界」を描画してみました。これが標準サイズ(15×15[ドット])のフォントです。若い方には良いでしょうが、老眼には少々堪える小ささです。……あまり複雑では無い文字ならば、読めなくはない……、ギリ。
【kprn,\x14\x16\x16 こんにちは、世界\r\n】
OK: kprint
フォントサイズを22×22[ドット]に設定(\x14\x16\x16、制御コードはこちら)して、描画しました。うん、この位の大きさがちょうど良いな。
【rgb,63,32,16】
OK: rgb(63,32,16)=\x12\xfc\x08
【kprn, こんにちは、世界\r\n】
OK: kprint
文字色を薄い橙色に設定して、描画してみました。前のコマンドで設定したフォントサイズは、再設定し直さない限り残っているので、今回もフォントサイズ22×22[ドット]で表示されたはずです。
【demo】
OK+ demo start
OK+ draw normal size font
OK+ draw larger size
OK+ different aspect ratio
OK+ overlay
OK+ random kanji gradation
OK: demo finished
組み込みデモを実行してみました。デモの様子を以下の動画にしてありますので、ご確認ください。
整備されたライブラリを使うのは簡単でよろしい(1^^)♪。今回、日本語フォントや描画関数だけは自分で起こしたのですが、今までみたいに低レベルのドライバから起こすことを考えたら、すごく楽をさせていただきました。