前回、日本語フォントFONTJPユニットを設計しました。
と、言うのも、1.8インチの小さなSPI-TFT表示器(下図)をMoscoviumで使おうと、購入したのがきっかけです。SPI-TFTのお試しはArduino DUEを使って済んでいます(「#18-3 SPI接続TFT表示器」)ので、今回はいよいよ、MoscoviumマイコンでSPI-TFTに表示してみるテーマとなります。
この小さなSPI-TFT表示器に、8×8[ドット]の日本語(漢字)フォントを表示してみようと思います。
昔々、8ビットパソコンで使っていた漢字フォントは16×16[ドット](フチあり)でした。
今、Windows上にて、テキストエディタで使っているフォントは、1文字のサイズが12×12[ドット](フチなし。隣接文字とくっつかない様に使うためには、実質13×13[ドット])です。12×12[ドット]だと、複雑な漢字はデフォルメされています。
それより小さいフォントは無理だろうと思っていたところ、フリー🆓の8×8[ドット]日本語(漢字)フォントを見つけました(下リンクを参照してください)。
Little Limit(別ウインドウが開きます)
謝辞: しっかり第二水準までカバーした極小フォントを提供してくださっている「Little Limit」の作者(門真 なむ)様に感謝いたします。
こちらの「美咲フォント」はフチあり(実質7×7[ドット]で構成)なので、ピッタリ8×8[ドット]で並べても隣接文字とくっついてしまうことは無く、非力な組み込み用マイコンで扱うのに優しい仕様です。
例文でその実力を確かめてみましょう。
カギ括弧「○」の中の漢字、読めますか?感の良い方なら、「多分あの漢字かな?」って、判るかもしれませんが、確信を持つには少し厳しいですね?
今度はどうでしょう?同じ漢字が含まれているのですが、なんとなく読めてしまったのではないでしょうか?特に、ファンタジィ系のRPGやゲームなどがお好きな方なら一撃でしょう。
そうでない方でも、「性」の文字は判別できるので、「○性」から類推して、本性、特性、気性、属性、男性、女性、中性、両性、真性、仮性、苛性、魔性、天性、急性、慢性、酸性、塑性、個性、……等が思い浮かびます。更に、「火の○性をもったドラゴン」の文脈だと、「特性」か「属性」くらいでしょうか?最後は、文字の形(部首)から、「属性」と推定できます。
偉いもので、人間の脳はこのような判定を一瞬でこなし、情報を補完して「読めてしまう」みたいです。
では、もう少し長いサンプルをどうぞ(↓)。
PC上の表示だと思いますので、上のサンプルは倍角表示(1ドットを2×2[ドット]で表示)としました。フォント自体は8×8[ドット]の「美咲フォント」を使っています。
読めましたか?そもそも古文で、かな遣いも違っているし、現代文の脳が補完し難いサンプルをあえて選んでみました。読めなかった方は、「源氏物語の冒頭」を検索して原文を知ってから戻ってみてください。
なお、上図のサンプルは、ほぼ、1.8インチSPI-TFT表示器で1ページに表示可能な文字数(16×20文字)となっています。また、上のサンプル中、壺(=0x9ae2、52区)、憚(=0x9cdd、56区)の二文字が第二水準漢字です。第一水準までのROMデータを使う場合には表示されません。
ひっくり返すと、この2つの漢字を使わなければ、その他の文字は全て第一水準だけで表示できるのだから、第一水準だけのフォントROMを使っても、それほど大きなビハインドはありません。「壺」は「壷(=0x92d9、36区、第一水準)」で代用しても良いし、「憚る」は「はばかる」でも違和感は無いでしょう。
Arduino等のマイコンボードを使った組み込みシステムで、今回の様な小さな(1.8インチ)表示器であっても、漢字を表示できる利点は大きいです。日本人相手に、英語だのローマ字だので気持ちを伝えようとしても無理があるからです。
一般的には、「表示器の表示領域が狭い」とか、「日本語フォントROMが大きすぎる」とかの制約があって、組み込みシステムで使うのは難しいのですが、そういう障害を乗り越えた先に存在する日本語表示アプリケーションって、尊いと思いませんか?
中には、脳による補完を期待した上でも、さすがに判読が厳しい複雑な漢字もありますが、表示させてみて判読できないような漢字は平仮名に変更するとか、読み仮名(よみがな)を付けるとか、表現を変えて難しい漢字自体を回避するとか、調整すれば十分実用に耐え得ると思われます。
非同期シリアル通信で受信した文字列データを、そのままSPI-TFT画面に表示する通信ソフトウェアを作ろうと思います。以下、目標仕様です。
TFTへの文字描画に関しては、「#12-3 DUE+TFTで日本語表示」のプログラム(tft_kprint.ino)が参考になりますし、SPI-TFTをSPI通信で制御する方法については「#18-3 SPI接続TFT表示器」のプログラム(test_st7735r.ino)で試行済みなので、これらをMoscoviumのアセンブラに移植するだけです。
シフトJIS(S-JIS)コードは、7ビットASCIIコードに対して、コード範囲0x80〜0xffの部分に半角カタカナ文字と、マルチバイトコードの拡張を取り込んだ文字セットとなります。
よって、文字列データから1文字を取り出そうとする場合、まず、1バイト取って来て、このコード範囲がS-JIS第一バイトコード範囲に入っているかどうかを調べます。入っていなかった場合、取って来た1バイトはASCIIコードまたは半角カタカナ文字と見なして処理します(1バイトコード)。
S-JIS第一バイトコード範囲に入っていた場合、更に次の1バイトを取って来て、2バイトのコードとします。第一バイトが上位、第二バイトが下位となります。
通常は、この処理をソフトウェアで実行します。しかし、アセンブラでプログラムして見ると意外と面倒です。コード範囲を判定しなければならないので、上限値と下限値に対して2回、比較と分岐を実行する必要があり、更にコード範囲が2つあるので×2倍の手間がかかります。……更に上位・下位バイトに×2倍の、計8回比較しなければならない。
そこで、FONTJPユニットには、文字コードレジスタfnjpcodに書き込んだコードがS-JISコード範囲に入っているかどうか?の判定フラグ(SJS1,SJS2)がfnjpctlレジスタに用意されています。fnjpcodレジスタの上位バイトがS-JIS第一バイトコード範囲にある場合にはSJS1=1、下位バイトがS-JIS第二バイトコード範囲にある場合にはSJS2=1となります。よって、16ビットのfnjpcodレジスタ値がS-JIS 2バイトコード範囲にある場合、SJS1=SJS2=1です。
例えば汎用レジスタr0に1バイト目の文字列データを読み込んだ場合、以下の様な手順で1バイト/2バイトコードを判定できます。
// ASCII or S-JIS code
lsfti r0,8 // 読み込んだ1バイトを上位側に書き込む
ldwi r7,fnjpcod
stw [r7],r0
ldwi r7,fnjpctl
ldw r2,[r7]
andi r2,fnjp_sjs1 // SJS1フラグをチェック
beq tkp_not_sjis
// S-JIS code, read 2nd byte
ldb r2,[r1] // 2バイト目を読み込んで、下位バイトにセット
addi r1,1
add r0,r2
ldwi r7,fnjpcod
stw [r7],r0
tkp_not_sjis:
// print 1 character
pushw r1
jalwr7 tft_kprint_chr
※このサンプルコードでは上位バイトのコード範囲しか判定していません。下位バイトも判定したい場合は各自対応してください。
判定の過程で、文字コードをfnjpcodレジスタに書き込んでいるので、判定終了後、直ちにフォントデータの読み出しが可能となります。
以上、プログラム(stft_kprint.asm)の一部抜粋ですので、全体像はソースコードをご確認ください。
以下に、完成したプログラムをリンクしておきます。
完成プログラム:stft_kprint.asm
PCや他のArduino等の機体をホストとして、SPI-TFTへの日本語表示端末(サブシステム)として利用できるようなプログラムです。
前節で完成したプログラム(stft_kprint.asm)は、受動的に動作するソフトウェアですので、ホスト(PC)側からテキストデータを送ってデモンストレーションを実行しようと思います。以下、デモンストレーション内容について検討します。
完成したスクリプトをリンクしておきます。
完成スクリプト:fontjp_kpr_demo.olv
次節の実験で使います。スクリプトを実行するには、Windowsに拙作Olive+のインストールが必要です。
Moscoviumで走らせるプログラム(stft_kprint.asm)をアセンブルしてMIFファイルを作り、日本語フォントFONTJPユニットを搭載したMCOC115JP0408のデザインデータをFPGAボード(Sipeed Lichee Tang Primer)に書き込んでください。I/O制約ファイルはMCOC_full.adcです。
デザインデータは上のリンクからダウンロードできます。FPGAボードへの書き込み手順はこちらを参照してください。
デザインを書き込んだFPGAボードと、USB⇔UART変換モジュール(FTDI)、SPI-TFT表示器を以下表の様に結線します。
FTDI | 接続 | FPGAボード | 接続 | SPI-TFT |
TXD | → | R7(uart_rxd) | ||
RXD | ← | T7(uart_txd) | ||
VCC(ジャンパ:3.3V側) | × | |||
GND | ⇔ | GND | ⇔ | GND |
LED− | ||||
3.3V | → | VCC | ||
C15(port_out[7]) | → | CS | ||
C16(port_out[6]) | → | SCL | ||
B10(port_out[5]) | → | SDA | ||
C9(port_out[4]) | → | A0 | ||
H13(port_out[3]) | → | RESET | ||
下の回路図 | LED+ |
汎用I/Oポートの出力端子(port_out[ ])が5本必要です。8本ある出力端子の残り3本は、オンボードのRGB LEDに割り当ててある(MCOC_full.adc)ので、すべて使い切ってしまいました。早晩増設しないと足りなくなりますね。
※「#18-7 Moscovium色々拡張」の記事で、I/Oポート数を倍増させる対応としました。
SPI-TFTのバックライトLED電源は、以下図のような回路に接続してください。
stft_kprint.asmの制御コード(0x16)を使って、バックライトLEDの輝度を制御できるようになります。面倒な方は、DUEの時みたく3.3VとGNDに直結でも良いです。3.3V直結だと少し電流値が大きいので、50Ω程の電流制限抵抗を追加しておくのが推奨です(上図R3相当を追加)。バックライトLEDが少し暗くなりますが、表示は十分に視認できます。
最初、普通にNPNトランジスタを使ってスイッチングする回路(引き込み側にスイッチ)を組んだのですが、どうやらSPI-TFTの基板上でLED−端子がGNDに落ちている様で、うまく制御できませんでした(わざわざGNDと端子を分離しておいて、紛らわしいよ!)。そこで、PNPトランジスタ(手持ちのS8550)を使って、上図の様な回路(注入側にスイッチ)としました。
PNPトランジスタなので、E16端子=LOWでバックライトLEDが点灯し、E16=HIGHで消灯します。FPGAボードのE16端子は汎用16ビットタイマTIM162ユニットのPWM出力(tmr0_pmwa)となっており、単なるON/OFFだけでなく、中間輝度で光らせることもできます。最後にご覧いただくデモ動画で、その様子もご覧いただけます。
実験中の様子を示します。
シリアル(SPI)接続だと油断していたのだけど、意外にジャンパ線の数が多い……。追加のストローブ信号(D/CX)、リセット信号(RESX)、バックライトLEDの制御信号など、ちょいちょい、増加してるから。でも、許容範囲。
以下、表示サンプルです。
まず、左側の画像から。文字色を変えたり、背景色を変えたりして描画できます。文字の左右・上下反転や回転、倍角表示などはハードウェアのサポートがあるので、ソフトウェアの負担はほとんどありません。
中央の画像は、ランダムに選択した漢字をグラデーション表示した物です。右側の画像(DUE+TFTシールド)と比較してみてください。一画面に表示されている漢字の数はほぼ同数ですが、SPI-TFT(1.8インチ)の方は8×8[ドット]フォント、TFTシールド(2.8インチ)の方は16×16[ドット]フォントです(ランダムなので、表示されている漢字は異なります)。
以下、動作中の様子を動画にしてみました。
編集なし(キャプションは追加してます)、撮影したままの動画ですので、描画速度なども実感していただけると思います。
DUEで試行した時より、少し描画が速い気がします。DUEでは、遅い標準関数(digitalWrite()等)を使ってソフトウェアSPIを実装したのですが、Moscoviumマイコンではアセンブラ+ループアンローリングで実装したので、その分速くなった様です。DUEでも、こちらの例の様に工夫すれば速くなります。
何気に、「縦書き表示」の描画でも、通常表示の描画と速度が変わらないことに気が付くはずです。フォントの回転にはハードウェアサポートがあるためです!
だいぶ電流を絞ったのですが、バックライトLEDの明るさも、十分でありましょう?何より、電気の無駄遣いになりますので、電流制限抵抗の追加を推奨します。