公開日:2020/05/09
title.png

10th Season


#10-9 QVGA TFT液晶を使う

 QVGA(240×320[ドット])表示のTFT液晶シールド(以下単に、TFTシールド)を入手したので、今回はこの娘を使ってみます(下図)。

a109_shield.jpg

TFTの仕様調査

 まずは、仕様調査ですね。TFTシールドは結構ちゃんとした箱に入って届いたのだけど、箱の裏に印刷されたQRコードから、HPにアクセスしてみました。

a109_box.jpg

  URL:https://wiki.keyestudio.com/index.php/Ks0257_keyestudio_2.8_Inch_TFT_LCD_Shield

 TFTパネルのドライバチップはILITEKのILI9325みたいですね。UNOのシールドになっているので、スタックして使えます。下図の様に、ぴったりはまります。

a109_stack.jpg

 最近購入して使っている色々なセンサなどのモジュールは、ビニルなどの袋に入って届いて、説明書も何もついていないことが多いです。それに比べたら雲泥の差!HPからサンプルコードとライブラリを入手することができました。

 サンプルコードの「Paint!」ツールをコンパイルして使ってみました。コンパイルしてシリアルモニタを開いたところ、以下の様なメッセージが表示されました。

  Paint!
  Found ILI9328 LCD driver

 ドライバチップとして、ILI9328が検出されたようです。HPにはILI9325と記載されていたのですが、同じメーカの近い型番なので、恐らく後方互換品なのでしょう。両方の型番でデータシートを検索して見比べてみましたが、ぱっと見では差分が解りませんでした。

  ILI9328のデータシートを検索する(別ウインドウが開きます)

 シリアルモニタに表示されるのは、この最初のメッセージだけで、それ以外は何も表示されません。あとはTFT画面のタッチパネルで操作します。

a109_paint.jpg

※ILI9328には上下や左右を反転して表示する機能もあるので、上のx,y軸は「サンプルコードの設定では」と言う条件付きです。

 このTFTパネルには、4線抵抗方式タッチパネルが付いているのも、選定した時のポイントの一つでした。タッチペンを使って、画面に図形を描いてみたいと思います。

 写真では画面左端に見えている6色のパレットから色を選んで画面をタッチすると、タッチした場所にその色で描画される仕組みです。
 どうやら、画面の一番下をタッチすると、書き込んだ図形を消去できるサンプルコードになっているみたいなのですが、まず、どこが「下」なのかわかりません。サンプルコードにシリアルモニタへのデバッグ出力を追加しながら、調査を進めたところ、どうやら上写真に記載した方向にx軸とy軸が設定されていました。
 QVGAなのですが、一般に使われている横長ではなくて、横240ドット×縦320ドットの縦長ディスプレイでした。勿論、どの向きに設置するかで、図形を描画する時の座標計算を調整するだけなので、横長で使用することも可能だと思います。

 上下がわかったところで、画面一番下の辺りをタッチしてみたのですが、一向に画面がクリアされませんでした。原因は、サンプルコード内で設定されている以下の定数でした。

  #define TS_MINX 150
  #define TS_MINY 120
  #define TS_MAXX 920
  #define TS_MAXY 940


 数値は、analogRead()関数で読み出したデジタル値で、値の範囲は0〜1,023[LSB]です。TFT画面の上端をタッチした時の読み出し電圧値がTS_MINY、下端をタッチした時の読み出し電圧値がTS_MAXYとして、タッチ座標に換算しているので、手持ちの液晶画面で校正(キャリブレーション)して使わないと、画面の端までタッチできないケースがあるみたいです。
 シリアルモニタにanalogRead()関数で読み出した値を表示しながら、手持ちのTFTシールドで(手動!)校正したところ、画面の下端タッチで画面がクリアされるようになりました。

a109_draw.jpg

 ということで無事、タッチペンを使って画面に図形を描くことができました(上図)。簡単なサンプルコードによるお絵描きソフトウェアなので、選択できる色数は6色しか無いのですが、実際には65,536色(※)の色を表示することができます。後でデモ動画をご覧に入れますので、そちらで確認してください。

※TFTパネルとしては、26万色発色可能なのですが、RGB各成分に6ビット、計18ビットの中途半端なデータを転送する必要があります。あまり利点が感じられなかったため、本ページのプログラムで表示可能な色数は65,536色となっています。
※蛇足ですが、総ドット数240×320=76,800ドットのQVGA画面では、どんなに頑張っても、同時に表示可能なのは76,800色(約7.6万色)です。26万色の同時表示はできません。「△26万色表示可能」 ⇔ 「○26万色発色可能」です。


 サンプルコード実行中に、USB電圧・電流計(下図)を使って消費電流を測ったところ、UNO込みで約270mAでした。

ar74_usbvam.jpg

 UNOの電流値は70mA程度なので、TFTシールドだけで200mAくらい流している計算です。結構な大喰らいですね(1^^;)。

回路構成等考察

 下は裏面の写真です。

a109_back.jpg

 UNOのシールドになっているので、裏面のシルクに表示された端子名とUNOのピン配置を突き合わせると、以下の様になります。

Arduino ATmega328 TFT
バス SD,タッチ
D0 PD0(PCINT16/OC3A/RXD0)    
D1 PD1(PCINT17/OC4A/TXD0)
D2 PD2(PCINT18/INT0/OC3B/OC4B) D2
D3 PD3(OC2B/INT1/PCINT19) D3
D4 PD4(XCK0/T0/PCINT20) D4
D5 PD5(OC0B/T1/PCINT21) D5
D6 PD6(OC0A/PCINT22/AIN0) D6
D7 PD7(PCINT23/AIN1) D7
D8 PB0(ICP1/CLKO/PCINT0) D0 X+
D9 PB1(OC1A/PCINT1) D1 Y−
D10 PB2(SS0/OC1B/PCINT2)   SD_CS
D11 PB3(MOSI0/OC2A/PCINT3) SD_DI
D12 PB4(MISO0/PCINT4) SD_DO
D13 PB5(PCINT5/SCK0) SD_SCK
D14(A0) PC0(ADC0/PCINT8/MISO1) RD  
D15(A1) PC1(ADC1/PCINT9/SCK1) WR
D16(A2) PC2(ADC2/PCINT10) RS X−
D17(A3) PC3(ADC3/PCINT11) CS Y+
D18(A4) PC4(ADC4/PCINT12/SDA0) RESET  
D19(A5) PC5(ADC5/PCINT13/SCL0)  

 TFTドライバチップILI9328にアクセスするための信号が、データバス(D7〜D0)とバスストローブ(CS,RS,WR,RD)、リセット信号の12+1本です。それとは独立してSDカードI/FとつながるSPIの信号線が4本あり、4線抵抗方式タッチパネルのインターフェイス(X+,X−,Y+,Y−)がバス信号線とマルチプレクスされています。

 D0,D1はUARTのTXD,RXDとしてUSB⇔UART変換チップとの通信で使われているため、空き端子はD19のみです。D18が空いていれば、I2Cが使えたのだけど、RESET端子が割り当たっています。
 ともあれ、ほとんどの信号線が使われているので、他のセンサ等のモジュールは接続できませんね。

バス信号

 裏面写真に2つ見えているICは、マークからNXP社の74HC245だと思われます。その他、AMS1117-T33というレギュレータチップが見えます。

  NXP 74HC245のデータシートを検索する(別ウインドウが開きます)

  AMS1117のデータシートを検索する(別ウインドウが開きます)

 唯一、基板パタンが追いかけられる裏面の配線をたどってみると、どうやら、UNOの5V電源がAMS1117に入って3.3Vに降圧しているようです。74HC245にもこの3.3Vが供給されています。ILI9328も3.3Vのチップなので、ここまでは問題ない様にみえます。
 UNOの5V信号は、と言うと、どうも74HC245に直結されているようです。74HC245のデータシートによると、入力信号電圧Vi=0〜Vccとなっており、Vcc=3.3Vが繋がっているのでVi=0〜3.3[V]です。「UNOの5Vは直結できない」と解釈するのが一般的でしょう。

 ちょっと微妙と言えば、Viの規定は「絶対最大定格(Absolute Maximum Ratings)」ではなくて、「推奨動作条件(Recommended operating Conditions)」になっていることです(NXP社データシート)。Viの規定が絶対最大定格なら完全にアウトだけど、この場合はどうなんだろ?

※ちなみに、FAIRCHILD社の74HC245には絶対最大定格の規定が有って、Vi=−0.5〜Vcc+0.5V(ABS.MAX)なので、5V信号の直結はアウトです。嫌な予感がするのだけど、もしかして、誰かが74LVC245(レベル変換能力有)で設計してたのを、どっかのバカがパクッた(流用した)時に意味も解らず74HC245に変えてしまった、ンじゃないよね?

 ちょっとグレイだけど、市販品だからねぇ。「大丈夫」なのでしょう。そう思うしかない……。

KEYESTUDIOのHPには「This LCD shield is fully compatible with the Arduino UNO R3, so that you can easily stack it on the board for simple use.」 ⇒ 「UNOにシールドとして積み重ねて使える」と明記されている。


 も一つ微妙なのが、RESET信号。これ、ArduinoからILI9328をリセットするための信号線だと思うのだけど、I2Cの信号線に割り当てられてしまっているのです……。つまるところ、I2Cを使おうとすると、ILI9328(TFT)がリセットされちゃうんじゃ……?まあ、TFTと一緒にI2Cは使えませんよ、って事なのかな?
 TFTシールドをハードウェアリセットしたい時って、どんな時なんだろ?Arduinoのリセットを繋いでおくのじゃダメだったのかな?普段使わない様な機能の為にI2Cを潰してしまったのは、もったいないな……。I2Cが使えれば、I2C対応センサモジュールとかを接続して使えたのに。

SDカードI/F(SPI)

 KEYESTUDIOのHPには、SDカードからWindowsのBMPファイルを読み出して表示するサンプルコードがあるのだけど、ちょっとこれは使ってみるのを躊躇しました。だって、UNOの5V信号が直接SDカードソケットに直結されているから。

 裏面の配線に見えているのはSD_CS信号だけなのだけど、こいつは配線を追っていくと、SDカードソケットのCSピンに直結されています。他の信号(SCK,MOSI,MISO)は、見えている面(裏面)に配線が出ていないので、TFTシールドをUNOから取り外して、電源と5V信号線(SPI端子)だけを繋いだ状態で、SDカードソケットの端子にプローブを当てて電圧を測定してみました。
 結果、やっぱり5V信号が直接SDカードスロットに接続されていました。普通は、L/Sを挟まないまでも抵抗分圧して3.3Vに落としてから接続するとかしているものなのだけど、そうなっていない模様です。

 どういう事だ〜?SDカードって3.3Vインターフェイスじゃなかったっけ?ソケットの中にL/Sが入っているとは思えないし、SDカードに5Vかかっちゃうよ。

 ちょっと、このSDカードI/Fは怖くて使えないなあ。SDカードが壊れてしまう……。

4線抵抗方式タッチパネル

 2枚の透明導電膜を使った抵抗式のタッチパネルが付いていて、その信号線が4本(X+,X−,Y+,Y−)、バス信号とマルチプレクス(端子共有)されています。この4本については基本的には問題なしです。近いうちにテーマを設定して、タッチパネルを試しに使ってみたいと思います。
 この4本の端子は、Arduinoからデジタル信号を出力できる端子を接続してください。同時に、Xのどちらか1本と、Yのどちらか1本はアナログ電圧を読み出せるアナログ端子です。
 今回のTFTシールドではX−とY+がアナログ端子につながっているので、この2本でアナログ信号を読み込んで制御します。


 問題は、バス信号とSDカードI/Fの信号レベル問題ですよ。「シールド」として使用するのをあきらめて、間にL/Sを挟もうとすると、この4本の信号線が問題になって来るんです。
 手持ちのL/Sは、オープンドレイン+プルアップタイプなので、このタイプのL/Sを挟んでしまうと必要なアナログ信号を読み出せなくなってしまいます。まあそもそも、このL/Sはスピードが遅いので、高速なバスアクセスに使うのは適さないのですけどね。

 と、言う事で、結論は以下の通りです。

 本ページでは近い将来に壊れて使えなくなるリスクを許容した上で使いますが、皆様は各自の責任でご判断ください。せめて、遊びつくして飽きるまで壊れないで耐えてほしい、と願うばかり……。

 確実に定格内で使いたい場合は、Pro Mini @8MHz,3.3Vや、328duino @3.3V等の3.3V品を使うのが良いと思います。

気を取り直して使ってみよう

 腹が括れたところで、気を取り直して使って行きたいと思います。
 前節KEYESTUDIOのHPからダウンロードしたサンプルコードやライブラリが揃っているので、皆様はそちらを使えば何の苦もなく、TFT画面にグラフィックやテキストを描画することが可能です。制御方法などに興味がない場合は、読み飛ばしてしまって宜しいと思います。

 それでは何もやることが無くでつまらないので、自分でプログラムを作って直接ILI9328を制御してみたいと思います。

アセンブラを使う

 ILI9328データシートより、バスのアクセスに関して以下の規定を引用しておきます。

a109_acfig.png
※ILI9328データシートから引用

a109_actab.png
※ILI9328データシートから引用

 「#3-1 LCD1602に表示する」にて、4ビットバスのLCD1602に高速アクセスするためにインラインアセンブラを使ったのですが、今回も同じアプローチを取ります。
 復習ですが、Arduino UNOは16MHz動作で1命令当たり1/16MHz=62.5nsで動作可能なMCUを搭載しています。これに対して、上に示したタイミング規定の内5nsとか10nsとか言う値の規定は、信号を同時に(1命令で)操作しない限り満たすことが可能です。1命令で62.5nsですから50nsの規定も問題なしです。
 どうやら気にしてプログラムしないとならないのは、読み出しデータの出力遅延時間(Read data delay time)tDDR=100ns(MAX)と、RDストローブのパルス幅PWLR,PWHR=150ns(MIN)辺りですね。
 tDDRのところさえ気にして作れば、それ以外のところは無理にアセンブラで記述する必要は無いのだけど、TFTシールドにアクセスする一番ローレベルの基礎部分なのでゴリゴリとアセンブラで組んでみました。

  // DAT(H) read, RD=0 pulse
  __asm__ __volatile__ (
    // RD=0
    "cbi %[reg_portc],%[bit_rd]\n\t"
    // tDDR>100ns=1.6/16MHz + 1(for ATmega328 latch timing)
    "in %[pcd],%[reg_portc]\n\t"
    "ori %[pcd],%[msk_rd]\n\t"
    "nop\n\t"
    // data read
    "in %[datb],%[reg_pinb]\n\t"
    "in %[datd],%[reg_pind]\n\t"
    // RD=1
    "out %[reg_portc],%[pcd]\n\t"
    // PWHR>150ns=2.4/16MHz
    "nop\n\t"
    : [pcd]"=&r"(pcd),
      [datb]"=&r"(datb),
      [datd]"=&r"(datd)
    : [reg_portc]"I"(reg_portc),
      [reg_pinb]"I"(reg_pinb),
      [reg_pind]"I"(reg_pind),
      [bit_rd]"I"(bit_RD),
      [msk_rd]"M"(1<<bit_RD)
    );


 tDDRは、cbi命令でRDストローブをアサートしてから、データバスに有効データが出力されるまでの時間ですので、in命令でデータバスを読み込むまでの間にtDDR=100ns以上待つ必要があります。また、ATmega328のI/OポートにはI/Oポートの信号をサンプリングするF/Fが1段入っているので、cbi命令 → in命令間に、tDDR/(1/16MHz)+クロック=2.6クロック以上開ける必要があります。
 従って通常は、1クロックで実行されるnop命令を3個挟むのですが、データバスの読み込みとは無関係な処理を2命令分実行して、無駄を減らしています。……こういう最適化はGCCには実施できない。

※もし、328duino(第一形態) @25MHzなどのクロックアップ版で使用する場合には、1クロックの時間が40nsとなり、tDDRの待ち時間に4クロック(4命令)以上開ける必要が生じます。Write low-level pulse width(PWLW=50ns)なども、違反しないように留意する必要が出てきます。

 RDストローブのパルス幅(PWHR)についても、違反しそうなところにnop命令を挟んで調整しておきました。


 バスアクセスは以下図の様なプロトコルで行います。

a109_buscyc.png
※ILI9328データシートから引用

 典型的なアドレス/データマルチプレクスI/Oバスとなっているようです。RS信号をLOWにアサートしたサイクルでアクセス先のI/Oレジスタアドレスを指定し、その後連続して任意ワード数のデータアクセスを実行します。データサイズは1ワード=16ビットが基本となっているので、8ビットバスで使用する今回の例では、1ワードのアクセスが8ビット×2回に分割されています。
 上の図では、書き込み3ワード、読み出し2ワード(+1ダミーリード)のアクセスを実施した例となっている様です。グラフィックRAM(GRAM)へアクセスすると内部のアドレスカウンタが自動的に更新されるので、この様に任意サイズのデータを連続アクセス可能な仕様となっています。

 ILI9328にアクセスするための関数仕様を以下に示しておきます。

 連続アクセスはGRAMに対しては有効ですが、通常のI/Oレジスタに対しては意味をなさないので、I/Oレジスタに対してはショートカット関数でアクセスすれば十分だと思います。

レジスタの初期設定値がわからない

 アクセス関数が完成したので使っては見たものの、画面に何も表示されませんでした。画面真っ白(1x_x)。
 「Driver Output Control」とか、「LCD Driving Control」とか、如何にも、な名前のI/OレジスタにTFTパネルに合った設定値を書き込まないと表示できないみたいですが、その設定値がわかりません。データシートには「この値を設定すると、こうなる」って説明はあるのですが、「こういうTFTパネルの場合はこの値を設定する」っていう具体的なガイドが無いから……。

 データシートしか情報が無い状態だったら相当苦労したでしょうが、幸いこのTFTシールドには最初の節で動作を確認できたサンプルコードがあります。サンプルコードを実行した状態でILI9328 I/Oレジスタの設定値をダンプするコードを追加して、シリアルモニタに表示しました。
 これで、表示に必要なレジスタとその設定値がわかりました。以下に、設定が必要だと思われる設定値を示します。

  // initialize tft (ili9328)
  // Driver Output Control
  tft_write (0x01,0x0100);
  tft_write (0x60,0xa700);
  // LCD Driving Control
  tft_write (0x02,0x0700);
  // Entry Mode
  tft_write (0x03,0x0030);
  // Display Control
  tft_write (0x07,0x0133);
  tft_write (0x08,0x0202);
  // Power Control
  tft_write (0x10,0x1690);
  tft_write (0x11,0x0227);
  tft_write (0x12,0x001a);
  tft_write (0x13,0x1800);
  tft_write (0x29,0x002a);
  // Gamma Control
  tft_write (0x35,0x0206);
  tft_write (0x36,0x0808);
  tft_write (0x37,0x0007);
  tft_write (0x38,0x0201);
  // Base Image Display Control
  tft_write (0x61,0x0007);
  // Panel Interface Control
  tft_write (0x92,0x0000);
  tft_write (0x95,0x0100);
  tft_write (0x97,0x0000);


 まずは、表示に必要十分な設定値が判りましたので、データシートを読みながら「ここの設定値を変えたら、どうなるのかな?」ってな具合で試していけば、自分好みの設定に変更することも可能でしょう。
 tft_init()関数で初期化したところ、以下の様なコードによって画面のほぼ中央に白点を打つことができました。

  tft_write (0x20, 120);  // horizontal position
  tft_write (0x21, 160);  // vertical position
  tft_write (0x22, 0xffff);  // write to gram


 直線を引いたり、円を描いたり、箱を描いたり、と言ったラスタグラフィックスは、点の集合で図形を描画する物なので、任意の座標に任意の色で点を打てさえすれば、自由に図形を描くことができます。つまり、ここまで来たら後はどうにでもなる、と言う事です。……筆者の得意分野でもある(1^−^)v。詳しくはこちら

 こうして完成した、今回の実験に使用するプログラムをリンクしておきます。

  実験プログラム:test_tft.ino

 コンパイル&書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。

  コマンド仕様:

実験、と言うよりおもちゃで遊ぶ感覚(楽しい)

 では、遊んでみましょう。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【rd,0】
  OK: read[0x00]=0x9328 …… TFTドライバチップのID(ILI9328)が読めました。

  【init】
  OK: initialize done

 TFTパネルの初期設定を実行しました。グラフィックRAMもクリアしたので、黒い画面が表示されています。液晶なので、バックライトが光っていて「真っ黒」ではないと思いますが。

  【pset,120,160,ffff,ffff,ffff,ffff,ffff,ffff,ffff,ffff】
  OK: pset 8

 画面の中央付近に短く白い線が表示されたはずです。線の方向が水平(横、x軸)方向です。

  【rgb,63,0,32】
  OK: rgb(63,0,32)=0x801f …… 紫っぽい色のピクセル値が表示されました。

  【pset,0,0,801f】
  OK: pset 1 …… 画面の左上隅に、紫っぽい色のドットが表示されたはずです。

  【pget,0,0】
  OK: pget(0,0)=0x801f …… 紫っぽい色のピクセル値が読み出せました。

 1ドットなので見難いと思いますが、その位置が原点(0,0)座標です。縦長画面の左上隅ですね。
 【rd】、【wr】コマンドでI/Oレジスタにアクセスすることができるので、ILI9328のデータシートを見ながら設定値を変えてみて、変化を確認することができるはずです。全レジスタ値を読み出すには【dmpr,0,9f】コマンドを実行してみてください。

 最後に、デモ動画をお目にかけます。

  【demo】
  OK: demo



※液晶画面をスマホで撮影するのって、難しいし、綺麗に取れませんね。実際に目で見た印象だと、もう少し綺麗です。

 ファーストコンタクトとしてはこんな所で十分でしょう(1^−^)。

今回のお買い物

 今回のお買い物は、以下の通りです。

 こんな時期(購入時2020年4月)だし、「通販なんてどうかな?」と、ためらいはしたのだけど、そんな折にAmazonからメールが届きました。

  「置き配サービスをデフォルトに設定します」

 ああ、「置き配」ね、指定場所に配達してくれるという……。さすがに、一戸建ての玄関前にポンっと置いて帰られても不安なのだけど、筆者宅は集合住宅で宅配ボックスが装備されているので、そこに届けてもらえるのは良き。

 こんな時期だから、「外出して街でお買い物」ってのは不安だし自粛すべきだけど、かといって本当に家の中に引き篭もっていては経済が回らないし……。けど、配達員さんと対面で受け取る宅配も、今は自粛すべきかも?と、ぐるぐる考えてしまっていたのだけど、「置き配」なら良いかと、通販で購入しました。
 感染していても無症状の人もいるみたいだから、自分が人から感染しない様に注意することと同じくらい、自分が人に感染させない注意も重要ですよね。非対面で荷物を受け取ることができたので、配達員さんのリスクも軽減されたはずです。


 まんまとAmazonの商売に乗っけられてしまったような気もする……。でも今回のおもちゃ(TFT)は色々と面白いから、暫く家籠り🏠🏢できるよ。


btn_arduino.png

btn_prev.png btn_next.png

home