公開日:2021/10/09
title.png

18th Season


#18-3 SPI接続TFT表示器

 今回のテーマは、Moscoviumマイコンで使おうと思って購入したTFT表示器です。まずは、Arduino DUE(+C言語)を使って動作確認したいと思います。

Moscoviumマイコンで使いたい

 FPGAボードのMoscoviumマイコンでグラフィック表示器を使用したくて、1.8インチの小さなTFT表示器を購入しました。

 TFT表示器と言えば、以前購入したTFTシールドがあり、UNOやDUEに装着してお手軽に使用することが出来ました(下図)。

a109_stack.jpga121_due_tft.jpg

 上図のTFTシールドは、8ビットバス接続でした。
 MoscoviumマイコンでこのTFTシールドを使うには、ストローブ信号線も含めて10数本のI/Oポートを接続してください。MoscoviumマイコンのPORT8I8OユニットはI/Oポート数8本のため数が足りず、拡張しないと接続できません。拡張したところで、シールドとして装着したら接続完了するUNOやDUEならいざ知らず、Moscoviumマイコン(FPGAボード)とTFTシールド間を10数本のジャンパ線で接続するとなると、「お手軽に使える」という訳には行かないのです。

 今回購入したのは、そんなニーズに応えるシリアル接続のTFTモジュールです。通信仕様はSPIなので、以降単にSPI-TFTと記します。

 最終的にはFPGAボード+Moscoviumマイコンで使いたいのだけど、まずは、C言語でデバッグのし易いArduino DUEを使って動作を確認したいと思います。今回のSPI-TFTは3.3V定格なので、「UNO(5V定格)よりDUEの方が適している」と言うのがDUEを選択した理由です。

 最終目標は「Moscoviumマイコンを使ってTFT表示すること」なので、DUEで突き詰めてもあまり意味がありません。今回は、SPI-TFTの初期化が出来て、正常に表示出来ていることを確認できたら、OKとします。

情報収集

 まずは、制御に必要な情報を収集して行きましょう。

 SPI-TFTは、下図の様にピンヘッダがはんだ付けされた状態で届きました。少し曲がって取り付けられていたので、ブレッドボードに挿した時の座りが悪いです(中華品質なので致し方なし。その分安いので良しとする)。

a183_spitft.jpg

 サイズは1.8インチ(表示領域の対角線が約4.6cm)と、小さいです。

ドライバチップST7735R

 購入時、販売ページに記されたドライバチップ名はST7735Rでした。

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


  ST7735Rの特徴:

SPI-TFTの端子(ピンヘッダ)

 ピンアサインは、下図の通り裏面のシルクで確認できます。残念ながら裏面なので表から確認できず、接続する時、ピンを間違えない様に注意が要ります。

a183_spitft_bk.jpg

 裏面には、巨大なSDカードスロット(microとかではない、標準サイズ)が付いていて、SCK,MOSI,MISO等、お馴染みのSPI端子名は、SDカードとのインターフェイス用です(ダマされるな!)。

 DUEとの接続対応は以下表の様になります。

SPI-TFT端子名 DUEとの接続 備考
LED− GND バックライトLED
LED+ 3.3V
SD_CS 接続しない。 SDカードI/F
MOSI
MISO
SCK
CS SPI:SS(スレーブセレクト)端子 ST7735RのSPI端子
SCL SPI:SCK(クロック)端子
SDA SPI:MOSI/MISO(データ入出力)端子
※双方向
A0 データ/コマンドストローブ端子
(汎用I/Oポート)
ST7735RのD/CX端子
RESET リセット出力(汎用I/Oポート) ST7735RのリセットRESX端子
NC 接続しない。 未接続端子
NC
NC
VCC 3.3V 電源
GND GND

 I2C信号線かと見まごうSCL,SDA端子が、SPI通信のクロックSCKとデータMOSI/MISO(双方向)です。シリアルデータ信号線SDAが双方向だったり、データ/コマンドストローブ信号D/CXが追加されていたりと、SPIの亜種(?)かも知れませんね。
 ST7735Rのデータシートには、一応、「This chip … accepts Serial Peripheral Interface (SPI)」と記載されているのですが、最初のIntroductionに記載がある以外、その他の場所では「SPI」という言葉は登場しません。データシート中では、「4-line serial (4線式シリアル)」等と記載されていました。

 SPIだと思うと、モードは0(クロックは正極性、フェーズはSCK↑(立ち上がりエッジ)でラッチ)です。8ビットのデータ通信が基本となっていて、コマンドによってパラメータ数(8ビットの整数倍)が異なります。

バックライトLED

 システム電源(VCC,GND)とは別に、バックライトLEDの電源端子(LED+/−)が存在します。アイドル状態でバックライトLEDを消灯して低消費電力状態にしたり、一時的に表示を消したりしたい場合には、適切なI/Oポートとトランジスタなどを使って、バックライトLEDをON/OFFする回路を作り込むこともできます。

※トランジスタ制御にPWM出力を使うと、バックライトの明るさも制御することが出来るようになります。ぼや〜っと、明るくしたり暗くしたりする視覚効果も作り出すことが出来ます。

 面倒なら、前節表の様に3.3VとGNDに繋いでしまえば、光らせることはできます。

 SPI-TFTボードの配線パタンを追いかけてみたところ、LED+には4R7のチップ抵抗が挟まっていました。4R7=4.7Ωですので、これが電流制限抵抗です。バックライトLEDのVf(順方向電圧)がいくらなのか判らないのですが、バックライトLEDに流れる電流値If=(3.3V−Vf)/4.7Ωです。
 一般的な白色LEDのVfは、Vf=2.8〜4[V]程になるみたいです。3.3Vでも十分明るく光ったので、Vf=4Vってことは無いと思いますが、と言って5Vを給電してしまうと、電流制限抵抗が4.7Ωしかないので電流が流れ過ぎてしまいます(要注意)。
 3.3V給電であっても、Vf=2.8V程度だった場合、If≒100mAほど流れる計算ですので、適切な電流制限抵抗を追加して調整した方が良いかも?です。

 バックライトLEDを光らせておかないとTFTに表示した画像を視認することはできないので、忘れずに接続しましょう。

システム設計

 前節で調べた情報に基づいて、引き続きシステム設計を進めます。

ソフトウェアSPI接続

 DUEにはハードウェアSPIも搭載されていますが、以下の理由から敢えてソフトウェアSPIを使います。

 端子割り当てはUNOのSPI端子位置と何となく合わせておきます(↓)。

  const int pin_RES=9;  // tft: RESET
  const int pin_SS=10;  // spi: SS
  const int pin_MOSI=11;  // spi: MOSI
  const int pin_MISO=pin_MOSI;  // spi: MISO(=MOSI), bi-directional
  const int pin_DCX=12;  // tft: D/CX
  const int pin_SCK=13;  // spi: SCK


 本来は、アセンブラレベルでST7735RのACタイミングスペックを満たせているかどうかまで確認すべきですが、今回はそこまで実施せず「動作を確認できたら良し」とします。ST7735RのSPIクロック周波数は15MHz(MAX)程度までOKなので、DUE @84MHzのソフトウェアSPIなら、シビアにタイミング設計しなくても問題なくつながるでしょう(経験則)。

  void spi_transfer (
  int ndat,
  uint8 *tbuf)
  {
    // spi transfer tbuf[ndat]
    // mode 0, MSB first, 8 bit, ndat bytes
    int idx;
    int idx2;
    int dat;

    digitalWrite (pin_SS,LOW);
    digitalWrite (pin_DCX,LOW);
    for (idx2=0; idx2<ndat; idx2++)
    {
      // 8 bits
      dat=tbuf[idx2];
      for (idx=0; idx<8; idx++)
      {
        digitalWrite (pin_MOSI,(dat&0x80)? HIGH: LOW);
        digitalWrite (pin_SCK,HIGH);
        dat<<=1;
        digitalWrite (pin_SCK,LOW);
      }
      digitalWrite (pin_DCX,HIGH);
    }
    digitalWrite (pin_SS,HIGH);
  }


 この辺(↑)のコードは、解説の必要ないと思われます。
 シリアル通信でかつソフトウェアSPIということで、アクセスは遅いです。従って、18ビットカラーモードではなく、16ビットカラーモード(65,536色)を使います。ピクセルデータの転送時、18ビットカラーモードだと3バイト/ピクセルのデータ転送が必要なのに対して、16ビットカラーモードなら2バイト/ピクセルで済んで高速な為です。

双方向シリアルデータ線

 ST7735Rのシリアルデータ線SDAは双方向なので、ST7735Rからデータを読み出すコマンドを実行する場合、コマンド送信中はDUE出力→ST7735R入力の方向ですが、コマンド送信が終了したら逆転して、ST7735R出力→DUE入力となる必要があります。

 以下、ST7735Rからのデータリード用SPIリード関数コードを示し、その下に解説を加えます。

  void spi_read (
  int ndat,
  uint8 *tbuf)
  {
    // spi read, command tbuf[0], receive data to tbuf[1~ndat]
    int idx;
    int idx2;
    int dat;

    digitalWrite (pin_SS, LOW);
    // command 8 bits
    digitalWrite (pin_DCX, LOW);


 ST7735RからのSPIリード関数。まずはコマンド送信なので、DUE出力→ST7735R入力の方向です。

    dat=tbuf[0];
    for (idx=0; idx<8; idx++)
    {
      digitalWrite (pin_SCK, LOW);
      digitalWrite (pin_MOSI, (dat&0x80)? HIGH: LOW);
      digitalWrite (pin_SCK, HIGH);
      dat<<=1;
    }


 8ビットのコマンドを送信しました。

    pinMode (pin_MOSI, INPUT_PULLUP);
    digitalWrite (pin_SCK, LOW);
    digitalWrite (pin_DCX, HIGH);


 コマンド送信終了後、最初のSCK↓(立ち下がりエッジ)でST7735Rがデータを出力してくるので、SCK↓の前に、DUEのMOSI端子出力を禁止(pinMode()関数)します。これで、ST7735R出力→DUE入力となりました。

    // receive data
    for (idx2=1; idx2<ndat; idx2++)
    {
      // read 8 bits
      dat=0;
      for (idx=0; idx<8; idx++)
      {
        dat<<=1;
        digitalWrite (pin_SCK, HIGH);
        dat += ((digitalRead (pin_MISO)==LOW)? 0: 1);
        digitalWrite (pin_SCK, LOW);
      }
      tbuf[idx2]=dat;
    }


 ST7735Rが出力してくるデータを受信して、バッファtbuf[ ]に蓄えます。

    digitalWrite (pin_SS, HIGH);
    pinMode (pin_MOSI, OUTPUT);
  }


 MOSI端子を出力状態に戻して終了します。

 どうでしょう?この動作、DUEのハードウェアSPIではできないのでは?そもそも、MOSIとMISOが単方向信号である前提でしょうし……。ソフトウェアSPIなので柔軟に対処できた良い例だと思います。

レジスタの初期設定値

 今回のテーマの最難関は、ST7735Rのレジスタ初期設定値です。機能動作の設定レジスタならまだしも、TFTパネルに関する設定レジスタを試行錯誤で探索して行くのは辛すぎます。TFTパネルの型番が判らず、データシートも入手できないため、どのレジスタにどんな値を設定したら良いものか、全く手掛かりがありません。

 と、実はST7735Rを使ったTFT表示器を制御するAdafruitのライブラリがあるのです。ここは先輩方の知恵を拝借します。

  Adafruit ST7735ライブラリを検索する(別ウインドウが開きます)

 上のリンクで検索したAdafruit TFTライブラリの中からST7735Rの初期化部分を参考にして、SPI-TFTの初期化関数を作成しました。

 なお、ST7735系には複数のバージョンがあるみたいです。例えば、「ST7735(R無し)」等。ST7735とST7735Rでは、TFTパネルの設定に関するレジスタの仕様が異なっていて、同じ値を設定しても同じ動作にはなりません。
 手元のSPI-TFTの場合はドライバチップが「ST7735R」なので、「ST7735(R無し)」の初期化ルーチンでは正しく設定できませんでした。お手元のドライバチップ型番と合わせて初期化が必要なことに留意ください。

プログラム

 以上検討の結果完成したプログラムを以下にリンクしておきます。

  完成したプログラム:test_st7735r.ino

 プログラムが起動すると、SPI-TFTレジスタの初期設定を実行します。ただし、SPI-TFT内部のグラフィックRAMは初期化しないので、電源投入後、最初の一回はSPI-TFT画面にランダムなごみが表示されます。
 シリアルモニタを開くと、以下の様なコマンドを受け付けています。

  シリアルモニタコマンド仕様:

実験はワクワク?ドキドキ?

 前節のプログラム(test_st7735r.ino)をDUEにコンパイル&書き込んで、以下表の様に接続してください。

Arduino DUE 接続 SPI-TFT
GND LED−
3.3V LED+
D10(SS) CS
D13(SCK) SCL
D11(MOSI/MISO) SDA
D12(D/CX) A0
D9(RES) RESET
3.3V VCC
GND GND


 以下、実験中の様子を示します。

a183_expr.jpg

 最初に電源を入れた時には、SPI-TFTのグラフィックRAMが初期化されていないので、上図の様にランダムなごみが表示されます。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【init】
  OK: initialize done

 【init】コマンドを実行すると、SPI-TFTにリセットをかけて、レジスタの初期設定を実行後、画面を黒(ピクセル値0x0000)で初期化します。たらたら〜と、元のグラフィック画面を消去して行く様子が目で追えると思いますが、これがめいっぱい最高速度のアクセスですので、あんまり速くはありません。
 ハードウェアSPIを使うと、もう少し、速くなるかもしれませんが……(未確認)。


  【sprd,04,4】 …… RDDID (0x04、Read Display ID)コマンド
  OK: sprd=0x4,0x3E,0x44,0xF8,0x7F

 ST7735RのIDレジスタを読み出しました。最初の1バイトはRDDIDコマンドその物の0x04です。2バイト目以降が受信したデータですが、RDDIDコマンドで受信する最初の1バイト(上例では0x3e)はダミーなので、有効データは0x44,0xf8,0x7fの3バイトです。ただし、データシートに値の意味が記載されていないため、なんのこっちゃ、わかりません……(1^^;)。


  【sprd,0b,1】 …… RDDMADCTL (0x0b、Read Display MADCTL)コマンド
  OK: sprd=0xB,0xC8

 MADCTLレジスタの設定値を読み出す、RDDMADCTL(0x0b)コマンドを実行しました。読み出したデータ0xc8は、【init】コマンド内でMADCTLレジスタに設定したのと同じ値ですので、正しく読み出しできた様です。
 以下、tft_init()関数内での該当コード抜粋です。

  // MADCTL
  spi_buf[0]=0x36;  // コマンドコード(MADCTLレジスタアドレス0x36)
  spi_buf[1]=0xc8;  // 書き込みデータ
  spi_transfer (2,spi_buf);



  【boxf,32,40,96,120,7ff】
  OK: boxf(32,40)-(96,120)=0x07ff

 画面の中央に黄色(ピクセル値0x07ff)の長方形を描画しました。


  【drwrb】
  OK: draw rainbow

 画面全体にRGBグラデーションを描画しました(下図)。

a183_rgbgrd.jpg

 上写真の向きで、横128ドット×縦160ドットが一致しています。

バックライトLEDの消費電流計測

 最後に、USB電圧・電流計を使って、実験中のDUEに流れる電流値を測定しました。SPI-TFTのバックライトLEDに電流を流すかどうかをジャンパ線で切り替えると、その時の差分から、バックライトLEDに流れる電流値を知ることが出来ます。

  DUE+SPI-TFT全体に流れた電流測定値:
    バックライトLED消灯時=約80mA
    バックライトLED点灯時=約130mA

 USB端子で測定しているので、5Vです。この時の電流値Icc=130−80=50mAでした。電力一定で3.3Vに降圧されていると仮定して、LEDの順方向電流Ifとすると、5V×Icc=3.3V×Ifですので、If≒76mAとなります。
 ボードの観察で判った電流制限抵抗値4.7Ωと、上の測定結果より、バックライトLEDの順方向電圧Vfを推定すると、以下となります。

  (3.3V−Vf)=If×4.7Ωより、
  Vf=3.3V−If×4.7Ω≒2.94V

 インターネットで調べた白色LEDのVfと矛盾しません。
 やはり、電流制限抵抗4.7Ωはちょっと、小さすぎる感じですね。3.3Vで76mAも流してますから。LED+端子と3.3V電源の間に適切な抵抗を挟んで、電流量を調整した方が良いかも知れません(各自適切に電源設計して見てください)。

※Arduino DUEの公式スペックでは、3.3Vピンソケットに流せる電流値=800mA(ABS.MAX)となっていますので、76mA程度なら定格内ではあります。それより、バックライトLEDのIfスペックが何mA(MAX)で規定されているのか、の方が大事かも?


 以上で、表示や描画を含め、一通りの機能を確認できました。

今回のお買い物

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

 AmazonのEasyWordMallと言うお店で購入しました。中国からの発送で、発注から2週間と少しで届きました。クッション付き封筒に入って、郵便受け📬に届いていました。
 このお店は何回か使っているので、安心感があります(個人の感想です(1^^;))。ただし、最近このお店では、Amazonの国内発送商品と中国発送商品が混在しているので、うっかりバラバラに購入すると、別々に配送料がかかってしまうので要注意です。


 ラスタグラフィックスの基本である、「任意座標に任意の色で点を描画する」ことは出来たので、DUEでの実験は終了です。次はMoscoviumマイコン(FPGAボード)で使ってみることになりますが、準備が必要なので、もう少し回り道しなくてはなりません。


btn_arduino.png

btn_prev.png btn_next.png

home