niszetの日記

アナログCMOS系雑用エンジニアが頑張る備忘録系日記

キャラクタLCDで文字列をスクロールするやつの解説(5回目)

ようやく文字列がスクロールします

これまでのあらすじ

過去4回、約1万1千字を費やして、CGRAMに文字の字形データを入れればどのように表示されるかというレベルまで見てきました。 一旦の区切りとして、今回は文字をスクロールさせるための話です。

前回の記事はこちら。

niszet.hatenablog.com

本題。

さて、あとはスクロールするだけです。前回までにCGRAMに対して変数cg0からcg7までのデータを書き込むことでCGRAMの内容を更新することを説明しました。

察しがついている方もいると思うのですがやるべきことは次の通りです。

  1. 一定時間ごとにcg0からcg7までの変数の中身を横に動くように書き換える
  2. 書き換えたcg0からcg7までの変数を使ってCGRAMの内容を更新する
  3. 結果、書き換えた内容が画面に反映される

これだけです。3つ目はLCDが勝手にやってくれるし、2つ目は前回書いたようにCGRAMへ書き込む処理をもう一度行うだけです。ということで、1つめの「変数を書き換えたら文字が動くのはそうなんだろうけど実際どうやれば文字が動くように変更できるの?」が分かれば出来そうですね!

ということで、やっていきましょう。

余談?

あ、そうだ…。今回、美咲フォントを読み出す話の詳細は書いてませんが、処理はArduino-misakiUTF16ライブラリに依存しているので詳細はReadmeを参照してください…。というかそれも含めてサンプルコード書けばいいんですよね…わかる…。

変数の中身を変更して文字がスクロールするように見せる

変数沢山ありますが、やることは同じなのでcg0を中心に見ていきます。字形はbit列によってあらわされていたので、このbit列を横に動かす方法があれば良いわけですね。

C/C++(他の言語にもあるか?)には丁度良いことにシフト演算子(<<, >>それぞれ左に1つ、右に1つ、bitの並びを動かす。右に動かす場合には算術と…とかはいいか。詳しくは仕様を見てね)があるのでこれでbit列を移動させれば良さそうです。

今、cg0に8'b0000_0100というビットの並びが入っていたとすると、cg0 << 18'b0000_1000になります。簡単ですね!

そういえば、8'b0000_0100という書き方はC/C++ではできません(Verilog風の書き方)が、説明の便宜上使っていますのでご注意。

もうちょっと例を考えます。

シフトして、左側に行き過ぎたらどうなるのか。移動前が8'b0001_0000だった場合には移動後に8'b0010_0000となります。

前回書いたようにCGRAMの上位3bitは画面に表示されないので、CGRAMにコピーする元になる変数cg0も気にせずにシフトすれば良さそうです。

一番左に1があっても1bit左にシフトするとその1は消えてしまいます(もちろん0でも)。これがぐるっと回って右からまた1が現れるといったこともないので、気にせずに左に1つシフトすればよさそうです。反対側、一番右は1bit左にシフトすると常に0になります。

この一番下のbitに右隣の文字からデータを持ってきたいですね。

お隣から拝借

となりの字形データもイメージしたいので図を。隣に動かす場合、右のb4から持ってきたい気もしますが、そうすると字がつながってしまいます。

f:id:niszet:20210420162224p:plain

ここは、間に縦に空白があると思って、b5から持ってくると文字がつながらなくて丁度良い感じになります。

実際、画面の表示はこんな感じで文字の表示領域の間に1ドット分の空きがあります。その分を考慮して(そこに柱があるとおもって、そこに隠れていると考える)作るとより自然な動きになります(最初の動画もこの方法でやっています)

f:id:niszet:20210420162523p:plain

さて、cg1のb5からcg0のb0に値を入れたいわけですが…ひとまずb5の値を一番右に持っていくことにします。これはさっきの逆で右に4つシフトすればよいですね。(cg1 >> 4)こんな感じで。b7, b6には何が入っているかわからないですが、不要なのでここは捨てておきたいですね。こういう時はbit同士の演算で0にするとよいですね。

bit同士の演算には&(and)や|(or)が使えます。0と&を取ると相手が何であっても結果は0になり、1と&をとると相手が0なら0、1なら1、相手のbitがそのまま残ります。 なので、cg1のb5を右端に持ってきたあとに8'b0000_0001と&をとればよさそうですね。8'b0000_0001を16進数の0x01に書き換えれば、

(cg1 >> 4) & 0x01

これでよし。あとはこれをcg0の1桁目に入れてあげるだけ。

それにはこの2つをorで計算すればよいです。orの場合は1とorを取ると相手が何であっても結果は1に、0の場合は相手が1なら1、0なら0で相手のbitがそのまま残ります。 今、cg0の1桁目は0、cg1の1桁目以外は0になっているので、それぞれの欲しかったbitだけが残ってくれるということですね。

ということで、実装例としてはこんな感じになります。cg0やcg1は配列で、長さが8だったのでその分をfor文で処理します。

  for (int i = 0 ; i < 8; i++){
    cg0[i] = (cg0[i] << 1)| (((cg1[i] >> 4) & 0x01);
  }

サンプル表示されていた記号の場合を例に今回のことを図示するとこんな感じになります。イメージ掴めるでしょうか?

f:id:niszet:20210420161038p:plain

さて、これで画面の一番右以外は解決です。一番右はフォントデータから字形を読みだして、それを持ってくる必要があります。…なのですが、これはArduino-misakiUTF16ライブラリの解説になってしまうのでここではざっくりと。やることは上に書いたこととそう変わらないのですが。

フォントデータを持ってくるのはgetFontData()です。これにstatic unsigned char font[8];のように宣言した変数fontを与えるとこの中に字形データが入ります。これの一番左をcg7のb0に入るようにコードを書けば良いわけです。

と、いうことでひとまず一連の説明が終わりです。疲れた…(一度に書くものではないな

いずれ、これらのコード全文を公開すると思いますが(整理中)、中身を理解して、実際に手元で書いてみると色々工夫が出来て楽しいのではと思います。

DDRAMは更新しなくても良い

さて、CGRAMだけを更新するだけでいいの?DDRAMは更新しなくていいの?ということを気にする人がいるかもしれません。 今回の使い方ではDDRAMの更新は不要です。液晶画面の文字座標に対して表示する文字コード自体は変わっていないので、あらためてこの座標にはこの文字コードだよと指定する必要はないのです。文字座標に紐づいた文字コードの字形の入っているCGRAMが書き変わることで、その文字座標の文字も一緒に書き変わる、という仕組みです。

実のところこの更新タイミングをちゃんと制御したいという気持ちがあったのでデータシートも見てみたんですが、関連するパラメータはなさそうです。I2Cだとそこまで制御できないだろうというのもありますが。I2Cの話も追ってかきたいところですね。

液晶ディスプレイの話になりますが、画面の更新、リフレッシュをどのくらいの頻度でやるのか(リフレッシュレート)を制御することが可能で、おそらくそのタイミングで書き換えが出来ていると思うのですが、そのあたりの仕様は書かれていなかったと思います。あったらごめんなさい(教えてね)

と、書いている間に動画をもう一つ上げたので…それも解説しましょうか…。