公開日:2020/08/15
title.png

12th Season


#12-3 DUE+TFTで日本語表示

 今回はいよいよ、DUEにしかできない芸当をテーマにしてみます。
 以前、UNO+TFTシールドで7ビットASCII文字列の描画を実行しました(↓)。

  「#11-1 TFTシールドに文字を描画する

 そこまでは、UNOで十分でしたが……、今回挑戦するのは日本語表示です。
 もちろんUNOであっても、「常用漢字、数10種類」位に限定すれば可能かもしれませんが、使用するのはDUEですので、第一・第二水準漢字の表示に対応させたいと思います。

結局、何文字あるんだ?

 必要な漢字の文字数ですが、シフトJIS(S-JIS)等の文字コードからわかるでしょうか?S-JISコードは、7ビットASCII文字や8ビット半角カタカナ文字との共存が可能な様に空きコード領域を使っているため、コードが飛んでいて数を数えるにはあまり適していません。
 一方、S-JISやJISコードの元になった「区点コード」というコード体系が有って、このコード空間だと、あまり未定義領域が無く、比較的有効文字が詰まっています。S-JISコードの元になっていることもあり、S-JISコード ⇔ 区点コード変換は機械的に実行することが出来ます。

  区点コード表はこちら

 コードに文字が割り当たっていない空き領域もそこそこ散見されますが、そこだけ別扱いにして処理を複雑なものにするくらいなら、空き領域も全部ひっくるめて取り扱った方が吉です。つまり、用意すべき文字数は、空き領域も含めて94×94=8,836文字となります。

 戦略としては、以下の通りとなります。

フォントを作る

 UNO+TFTシールドでは、7ビットASCII文字の表示に8×8ドットフォントを使いました。このサイズで漢字を表現するのは不可能ですので、日本語フォントは16×16ドットのビットマップフォントとします。これを「全角フォント」と呼びます。
 テキストデータを表示する時には、日本語の全角フォント以外にも通常の7ビットASCIIコード領域に含まれる英数字と記号の表示が必要となります。UNO+TFTシールドの時に作ったフォントは8×8ドットでサイズが合わないので、このフォントも新たに作成する必要があり、サイズは8×16ドットとします。これを「半角フォント」と呼ぶことにします。

※忘れてはいけない拡張8ビットASCIIコード(?)に含まれる半角カタカナにも、ちゃんと対応しておきたいと思います。半角カタカナ ← 正確にはS-JISエンコードの一部と言った方が良いのかな?

 TFTシールドの表示領域は240ドット×320ドットなので、全角文字換算では15桁×20行=300文字の表示領域となります。400字詰め原稿用紙1枚にも満たず、それほど多くは無い印象です。ただ、

 等(↑)が、およそ300文字とのことで、表意文字である「漢字」を使える日本語の場合、少ないようで意外と多いのが300文字と言う表示領域かも知れませんね。

 全体の姿が見えました。全角フォント16×16ドットを8,836文字と、半角フォント8×16ドットを224文字(0x20〜0xffまで)作成したら、良いのです。

  ここでお断りしておきますが、このページでは、Arduino用の日本語ビットマップフォントは配布しません。  

 各自でご用意いただく必要があります。
 普通の(圧縮無し)ビットマップフォントですので、もし、文字フォントを手作りしている方がいるなら、それをデータに変換してもらえばOKです。

 全角フォントでは、16×16ドット/8ビット=32バイト/文字ですので、ざっと、8,836文字×32バイト≒277Kバイトものメモリ領域を必要とします。UNO(Flash 32Kバイト)では全く足りず、MEGA(Flash 256Kバイト)でも入りませんが、DUE(Flash 512Kバイト)なら、その他のアプリケーションプログラム分を差し引いてもおつりが来そうです。まさに、「DUEにしかできないこと」なのでした。

フォントの選定

 さすがに趣味で数1,000文字の漢字フォントを手作りしている方は少ないでしょうから、Olive+のスクリプトを使って、Windowsのフォントデータを吸い上げて使おうと思います。

 Windowsからフォントデータを吸い上げるスクリプトの前に、まずは、フォントの選定を行います。どんなフォントでもOKなのですが、以下の様なフォントを推奨します。

 手元の環境では、上の条件を満たすフォントは「MS明朝」と「MSゴシック」の2つだけでした。一番視認性が良いのは「MSゴシック」なのですが、お勧めは「MS明朝」です。明朝体の方が文字に飾りがつく分、一部複雑な漢字の細部がつぶれて怪しいのですが、それに目を瞑るとフォントとしては一番綺麗だからです。

 ただ、その他のベクタフォントでも、調整するとある程度のレベルでは使えます。以下のスクリプトを使って、フォント化した時のイメージを確認しながら使用するフォントを決定します。

  ビットマップフォント確認スクリプト:kuten_table.olv

 先頭付近のパラメータで仕上がりを調整します。

  var THSH=0.5;  // 二値化閾値(0〜1)
  macro FNAM="MS 明朝";  // フォント名


 これ以外の定数は変更しないでください。
 スクリプト(kuten_table.olv)を実行するには、WindowsにOlive+のインストールが必要です。ダウンロードコーナなどからダウンロードしてインストールしておいてください。

 以下、実行結果の一部抜粋です。実際には、94×94文字の区点コード表全体が表示されます。

a123_bmfont.png

 上の例だと、左側がグレイスケール表示となっていて綺麗です。閾値0.5(=1/2、中央値)で二値化すると右側の様になり、一部細い(薄い)線が消えてしまって、ダメダメです。このような場合は、スクリプト先頭付近のTHSHに0.8など、0.5より大きな値を設定すると、ある程度制御が可能です。
 逆に、細部が真っ黒につぶれてしまうような場合は、THSH=0.3など、0.5より小さな値を設定します。THSHで調整しきれなかった場合、そのフォントはあきらめましょう(1^^;)。
 意外に漢字は何とかなるのですが(多少崩れても判読できる)、ひらがな・カタカナの濁点と半濁点が判別できなくなってしまうのが悩ましい所でした。

 さすがのDUEでも、複数フォントを同時に持つのは無理なので、綺麗なフォントを1つ選んで吸い出す必要があります。

Windowsから吸い出す

 いくつかお手元のフォントで試してみて、フォントとパラメータが決まったら、以下のスクリプトで実際のフォントデータを生成します。上の実験で決定したパラメータ(フォント名とTHSH)を、スクリプトに反映する事をお忘れなく。

  全角フォント生成スクリプト:fontgen_sjis.olv

  半角フォント生成スクリプト:fontgen_ascii.olv

 スクリプトを実行すると、1文字づつグラフィック画面に描画して、16×16ドット領域のピクセルデータを吸い出しながらフォントデータ32バイト(uint16データ×16個)を生成して行きます。
 特に、全角フォントの生成は結構実行時間がかかる(8,836文字分繰り返すため)ので、時間のある時に実行して見てください。

※区点コード空間でスキャンして、全8,836文字分のフォントデータを生成するので、実行環境で表示可能な文字であれば、第一・第二水準漢字の範囲外であってもフォント化され、Arduinoでも表示可能です。

 最後までスクリプトが実行し終わると、以下のファイルが生成されています。

 上の2ファイルを、最後にリンクするプログラム(tft_kprint.ino)と同じディレクトリに格納すると、Arduino IDEでコンパイルができるようになっています。

フォントデータの利用について

 本ページで作ったフォントデータは本ページでは配布しませんので、各自、前節のスクリプトを使って生成する必要があります。配布しない理由は以下の通りです。

 と、いう訳ですので、以下、ご注意ください。

  本ページのスクリプトで作ったフォントデータを、Arduinoその他を使ったシステムに組み込んで、商用利用、または配布する場合、元となったフォントの著作権者に対して利用規約をご確認ください。  

 いずれにせよ、CのプログラムもOlive+のスクリプトも無保証のプログラムですので、使用者各自の責任において利用してください。

描画関数を作る

 漢字描画はtft_kprint()関数とします。UNO+TFTシールドで最初に作った、8×8ドットフォントの表示tft_print()関数も使い道があると思われるので、同時に使用可能な仕様にしたいと思います。

  日本語描画関数目標仕様:

 以下の関数で、次に描画しようとしている文字がS-JIS 2バイトコードなのか1バイトコードなのかを判定して、全角フォント描画または半角フォント描画を場合分けします。

  bool tft_is_sjis (
  const char *str)
  {
    // test 1st character of string str
    // return: true (str is a S-JIS 2 bytes code) / false (not)
    int chr;

    chr=(*str)&0xff;
    if ((0x81<=chr && chr<=0x9f) || (0xe0<=chr && chr<=0xfc))
    {
      // possibility
      chr=(*(str + 1))&0xff;
      if (0x40<=chr && chr<=0xfc && chr!=0x7f)
        return (true);  // yes S-JIS
      else
        return (false);  // not S-JIS
    }
    else
      return (false);  // not S-JIS
  }


 描画その物に関しては、8×8ドット描画ルーチンが16×16ドットに拡張されるだけですので、説明は省略します。8×8ドット文字描画ルーチンに関してはこちら

拡大描画関数も作っておこう

 フォントサイズが16×16ドットになって、8×8ドットフォントに比べると幾分細かくなっていますので、拡大描画tft_ksymbol()関数を作っておこうと思います。何か、ユーザの注意を惹きたい場面で大きな文字を表示したいケースがあるかもしれませんので。

  拡大描画関数目標仕様:

 こちらも、普通の描画tft_kprint()関数と大差ないので、詳細は完成版プログラムでご確認ください。

Arduino IDEでの日本語ハンドリング

 時に、Arduino IDEでS-JISコードが使えるか?と言うと、使えません。Arduino IDEで日本語を入力すると、UTF-8(unicode)でエンコードされてしまうためです。従って以下の様な構文はエラー無くコンパイルできますが、正しい文字は表示されません。

  tft_kprint ("あいうえお");

 Arduino IDEが文字列"あいうえお"をUTF-8でエンコードするのに対して、受け取るtft_kprint()関数はS-JISエンコードを期待しているから、ここで意思疎通が阻害されてしまうのです。正しくは、以下のようにコーディングしなければなりません。

  const uint8 str_1=
  {
    // "あいうえお"
    0x82, 0xa0, 0x82, 0xa2, 0x82, 0xa4, 0x82, 0xa6,
    0x82, 0xa8, 0x00
  };
  tft_kprint (str_1);


 uint8配列str_1には、文字列"あいうえお"をS-JISエンコードしたデータが格納されています。C言語文字列として矛盾しないよう、'\0'(0x00)で終端してあります。
 面倒ですが、止むを得ません。unicode空間でまともにフォントデータを保持できるとは思えないので、S-JISであり、区点コードに変換可能なことが絶対条件ですから。どう考えてもUTF-8には妥協できませんでした。


 と、言う事は、任意の文字列をS-JISエンコードの文字列データに変換する手段が必要となります。こちらも、Olive+のスクリプトで提供します(以下)。

  S-JIS文字列データ生成スクリプト:sjis2uint8.olv

 実行した時に開くウインドウに、任意の文字列を打ち込んでOKボタンを押すと、上のコード例の様なuint8配列データを生成して表示します。それをコピー&ペーストしてご活用ください。

※文字列中に改行コードを含みたい場合には、入力ウインドウ内で「CTRL+ENTER」を入力して改行できます。


 なお、Arduino IDEのシリアルモニタも、ダメです(使えません)。同じくUTF-8エンコードの為です。本ページでは、Tera Termを使い、「KanjiCodeメニュー ⇒ Shift_JIS」を選択して、実験しました。

実験しよう

 いよいよ、実験です。完成したプログラムを以下にリンクしておきます。

  完成プログラム:
     tft_kprint.ino    日本語描画プログラム
  tft_kprint_font_z.cpp   全角フォント(fontgen_sjis.olvで各自生成)
  tft_kprint_font_h.cpp   半角フォント(fontgen_ascii.olvで各自生成)

 上の3ファイルを同一のディレクトリに放り込み、Arduino IDEでtft_kprint.inoをコンパイルして、Arduino DUEに書き込んでください。

 以下のコマンドを非同期シリアル通信経由で受け付けるようになっています。

  コマンド仕様:

 今回、Arduino IDEのシリアルモニタは使えませんので、Tera Term等、S-JISコードの送受信に対応した通信ソフトウェアをご準備ください。

コンパイル&書き込み

 それはそうと、今回、デバッグが甚く大変でした。それと言うのも、以下のコンパイル&書き込みメッセージを見ていただくとわかります。

最大524288バイトのフラッシュメモリのうち、スケッチが323572バイト(61%)を使っています。
Atmel SMART device found
Erase flash
done in 0.045 seconds

Write 325860 bytes to flash (1273 pages)
  : (中略)
done in 62.563 seconds

Verify 325860 bytes of flash
  : (中略)
Verify successful
done in 57.362 seconds
Set boot flash true
CPU reset.

 合計で2分も待たされました。1回コンパイルして書き込むだけで、です。現時点までの実験中、ベリファイでエラーになったことは無かったし、せめて、ベリファイの実行を抑止すれば半分の時間で済みます(↓)。

  編集するファイル:
    <User>\AppData\Local\Arduino15\packages\arduino\hardware\sam\1.6.12\
      programmers.txt

  変更する箇所:
    tools.bossac.upload.params.verify=-v
      ↓
    tools.bossac.upload.params.verify=

 「-v」を消去したところ、ベリファイの実行を抑止することが出来ました。これでも書き込み時間1分ですから、UNOの書き込み時間に比べると、ものすごく長いのですが、2分から半分になりましたので、良しとしておきます。

※DUEのFlashメモリが経たって来て、不合理に動作が怪しくなった時には、上の「-v」を復活させて、ベリファイさせた方が無難です。うまく付き合っていきましょう。

実験を再開します

 以下、実験中の様子です。

a123_expr.jpg

 Arduino DUEにTFTシールドを装着して以上終了ですので、「実験中の様子」と言っても、画面に漢字を表示したところをご覧に入れているだけなのですが……。第一水準漢字の中からランダムに選んだ漢字300文字をグラデーションカラーで描画した時の様子です。

※この画面は、【demo】コマンドを実行すると、最後に表示されます。コマンド実行の度にランダムな漢字を表示します。「一部の常用漢字」に限った対応ではないことを実感していただけると思います。


 実験を進めましょう。

  実験サンプル:【 】内はTera Termからの送信データ
  【init】
  OK: init done

  【kprn,あいうえお】
  OK: kprint

 TFT画面の最上段に「あいうえお」という文字が表示されたら成功です。


  実験サンプル:【 】内はTera Termからの送信データ
  【ksym,0,32,96,96,愛】
  OK: ksymbol 愛

 今度は、拡大描画tft_ksymbol()関数のテストです。大きな「愛」が描画されましたか?


 UNOで作った8×8ドットASCII文字描画関数と同様、【who】コマンドを実行した時に、描画に掛かった時間を表示するようになっていますので、Tera Termの「Fileメニュー ⇒ Send file」から、適切な日本語テキストファイル(S-JISエンコード)を送信して見てください。

  【who】 …… 適切な日本語テキストファイルを送信後に実行
  DBG: kprint 1585435[us]/3774[chr]=420[us/chr]
  OK: tft_kprint ver.1.00

 描画時間は、約420μs/文字となりました。ここの「文字」は、半角1文字分を示しています。全角1文字=半角2文字相当なので、全角文字の場合は、約840μs/文字となります。
 8×8ドットのASCII文字をUNOで描画した時は、441μs/文字でしたので、全角文字だと、ちょっとビハインドが有りますが、半角文字だと、8×16ドットに大きくなっているにもかかわらず、UNOと同程度の時間で描画できているようです。


 こちら(↓)は、別なフォントを組み込んで表示させてみた例です。

a123_fonts.jpg

 左が明朝体、右は行書体です。明朝体はビットマップフォントを持っていたので、非常に可読性が高く、綺麗に見えます。行書体の方はベクタフォントからビットマップフォントへ変換したため、多少、複雑な漢字の細部がつぶれて判読し難いですが、ずいぶんと雰囲気が違って見えます。


 最後に、組み込んであるデモを実行してみましょう。

  【demo】
  OK+ demo start
  OK+ symbol
  OK+ symbol, shadow
  OK+ kanji gradation
  OK: demo finished




 今回も、拙作Olive+が大活躍でした🍀。TFTシールドへの日本語表示ができたので、例えばESP32などと組み合わせて、WEB上のニュースサイトからニュースのヘッドラインを拾ってきて表示する様なアプリケーションを考えてみましょう。英語のサイトだけではなく、日本語のサイトも対象にできることになります。あっ、でも無理かぁ……、WEBサイトは日本語でもunicodeですね、失敬(1−3−)。


btn_arduino.png

btn_prev.png btn_next.png

home