Arduino環境で ILI9341 LCDモジュールを使う

it

Arduino 環境でのILI9341 LCDモジュールの表示制御をします。
LCDモジュールは多種ありますが、今回比較的中型のILI9341ドライバ搭載モデルを紹介します。
RaspberryPi PicoとESP32-WROOMで表示制御、タッチパネル、SDカードからのJPG表示します。

記事の目的と内容

Arduino環境でLCDモジュール ILI9341を使うための内容を記載します。
当方所有の2.4inchと3.2inchを使います。
・RP2040搭載基板、RaspberryPi Picoでの配線と簡単な表示スケッチ
・ESP32-WROOM搭載基板、NODE-MCUでの配線と簡単なスケッチ
・SDカードリーダモジュールを使った画像表示(jpg)
・タッチパネルを使う

LCD ST7735

主な製品情報

Arduino環境で使用できる液晶カラーモニタとしては中型の2.4inch サイズです。
画面描画、SDカードリーダ、タッチパネルをSPIで制御します。
SPIのMOSI、MISO、SCKは共通で使用し、CSで個別に制御します。

サイズ2.2, 2.4, 2.8, 3.2inch
パネルTN
LCDドライバILI9341
解像度(X, Y)240, 320
動作電圧3.3~5V
SDカードリーダを使う場合は5V
通信方式SPI
タッチドライバ抵抗膜タッチパネル(HR2046またはXPT2046)
(非搭載モデルあり)
タッチ分解能4K x 4K
その他SDカードリーダ(非搭載モデルあり)
※1 サイズすべては所有、確認していませんが、販売しているのを見たことがあるものです。

販売形式では、タッチパネルの非搭載のモデル、SDカードリーダの非搭載のモデルがあります。
購入前に商品情報を確認してください。

ピン配置

外観

前面

背面

2.4inchと3.2inchで同じ画像を表示しました。
解像度は同じ320 x 240 です。
画面は大きいほうが視認性はいいですが、細かく見ると画素の粗さが見えます。

画像では色味に違いが見えますが、TN方式の視野角の狭さが影響しています。
2.4inchで長手方向、3.2inchでは短手方向が影響します。

長手方向を斜めから見ると2.4inchは黒っぽく変化します。
3.2inchはあまり変化がありません。

短手方向を斜めから見ると3.2inchは黒っぽく色味が変化します。
2.4inchはあまり変化がありません。


注意 : タッチパネルについて
画像赤丸箇所にドライバICあるものがタッチパネル付きです。

ILI9341の感圧式タッチドライバにはHR2046が実装されていることが多いです。
HR2046はXPT2046の互換品らしいですが動作不安定のものが多く、可能であれば購入の際には商品画像だけでなく商品紹介や販売先に実装ドライバの種類を確認したいところです。

使用感

2~3inch程度になると画像の表現力や視認性もよくなります。
タッチパネルの操作性も現実的に使えるサイズ感になったと実感します。
ハンディタイプのゲーム機や操作画面として十分使いやすいサイズだと思います。

感圧タッチパネルの分解能は4K x 4Kです。
表示の解像度とタッチパネルの分解能が違うのと縦横比が違うので、簡単な変換計算が必要です。
もともと実装されているタッチICドライバ(HR2046)は読み取りばらつきや不動などのトラブルが多いです。

タッチパネルの少しシートには軽いたわみがあります。
タッチパネルを使用しなければ、タッチパネルシートのないモデルを選択することでたわみのない表示で使用できます。

タッチ付きモデルでは樹脂のタッチペンが付属されていますが、普通の筆圧でも画面に傷が残りやすいので、スマートフォンなどの保護シートを貼って使用することで傷の回避ができます。

下の画像は左が保護シートを貼って作業、右は購入時に貼ってあった養生シートのものです。
養生シートは傷が入りやすく使い込むと破れてきます。

準備

ライブラリ

LCDを制御するためのライブラリにはAdafruit製ライブラリを使用します。

ライブラリ名用途検索確認時バージョン
Adafruit GFX Library by AdafruitグラフィックGFX1.11.3
Adafruit ILI9341 by AdafruitグラフィックILI93411.5.12
XPT2046_Touchscreen by Paul StoffregenタッチパネルXPT20461.4
TJpg_Decoder by Bodmerjpg表示jpg1.0.8

Adafruit ILI9341 by Adafruitライブラリをインストールすると、依存ライブラリが不足している場合不足している依存関係のインストールの問い合わせがあります。
「すべてをインストール」を選択してください。

タッチドライバの交換

所有しているタッチ付きILI9341を3個所有していますが、すべて座標の読み出しにトラブルがありました。
感圧式タッチパネルのドライバICにはHR2046が使われていてXPT2046の互換品のようです。
下図はドライバIC交換前のHR2046の座標の読み取りばらつきです。

XPT2046に交換した後の読み取り座標のばらつきは下図です。
HR2046に比べてばらつきの幅が1/3程度になりました。

不動や動作不良のILI9341のタッチパネル動作も同等程度に改善しました。
交換作業はこちらの記事にしました。

使い方

RP2040系 RaspberryPi Picoを使った表示

説明

RaspberryPi PicoでのILI9341制御をします。
LCDの情報(固定文字列)を表示します。

サンプルはRP2040のSPI0を使用した配線とスケッチです。
SPI1を使用する場合SPI1で配線を行います。
スケッチのSPI1の記述のコメントを解除し、SPI0をコメント化します。

そのほかの描画サンプルについては
スケッチ例 > Adafruit ILI9341 > graphictest を参照してください。

配線

配線はSPI0の例です。
SCK, MOSI はピン配置表を参照しSPI0の決められたピンを選択する必要があります。
CS, Reset, D/Cは任意のピンを使用できます。

スケッチ


#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <SPI.h>

//SPI0の場合
#define TFT_CS          17   // CS
#define TFT_RST         22   // Reset 
#define TFT_DC          28   // D/C
#define TFT_MOSI        19   // SDI(MOSI)
#define TFT_SCK         18   // SCK

//SPI1の場合
// #define TFT_CS          13  // CS
// #define TFT_RST         28  // Reset 
// #define TFT_DC          27  // D/C
// #define TFT_MOSI        15  // SDI(MOSI)
// #define TFT_SCK         14  // SCK

//SPI0の場合
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST);

//SPI1の場合
//Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI1, TFT_DC, TFT_CS, TFT_RST);

//ソフトウエアSPIの場合
//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST, -1);


void setup(void) 
{
  //SPI0の場合
  SPI.setTX(TFT_MOSI);
  SPI.setSCK(TFT_SCK);

  //SPI1の場合
  // SPI1.setTX(TFT_MOSI);
  // SPI1.setSCK(TFT_SCK);
   tft.begin();
  
  tft.fillScreen(ILI9341_BLACK);               //背景の塗りつぶし

  //テキスト表示
  tft.setRotation(3);                         //画面回転
  tft.setTextSize(4);                         //サイズ

  tft.setCursor(0, 10);                       //カーソル位置                      
  tft.setTextColor(ILI9341_GREEN);             //緑
  tft.printf("TAMANEGI\n\n");

  tft.setTextSize(3);                         //サイズ
  tft.setTextColor(ILI9341_RED);               //赤
  tft.printf("2.4inch LCD\n");
  tft.setTextColor(ILI9341_YELLOW);            //黄
  tft.printf("Res=320 x 240\n");
  tft.setTextColor(ILI9341_BLUE);              //青
  tft.printf("ILI9341\n");
}

void loop()
{
}

結果

ESP32系 ESP32-WROOMを使った表示

説明

ESP32-WROOM NODE-MCUでのILI9341制御をします。
LCDの情報(固定文字列)を表示します。

サンプルはESP32のVSPI(SPIO0)を使用した配線とスケッチです。
HSPI(SPI1)を使用する場合SPI1の記述のコメントを解除し、SPI0をコメント化します。

そのほかの描画サンプルについては
スケッチ例 > Adafruit ILI9341 > graphictest を参照してください。

配線

VSPIを使う場合のサンプルです。

スケッチ


#include <SPI.h>
#include <Adafruit_GFX.h>

#define TFT_CS    (5)
#define TFT_RST   (27)
#define TFT_DC    (26)
#define TFT_MOSI  (23)
#define TFT_SCK   (18)

#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <SPI.h>

//SPI0の場合
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST);

//ソフトウエアSPIの場合
//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST, -1);

void setup(void) 
{
  //SPIピン設定
  SPI.begin(TFT_SCK, -1, TFT_MOSI, TFT_CS); 

  tft.begin();
  
  tft.fillScreen(ILI9341_BLACK);               //背景の塗りつぶし

  //テキスト表示
  tft.setRotation(3);                         //画面回転
  tft.setTextSize(4);                         //サイズ

  tft.setCursor(0, 10);                       //カーソル位置                      
  tft.setTextColor(ILI9341_GREEN);             //緑
  tft.printf("TAMANEGI\n\n");

  tft.setTextSize(3);                         //サイズ
  tft.setTextColor(ILI9341_RED);               //赤
  tft.printf("2.4inch LCD\n");
  tft.setTextColor(ILI9341_YELLOW);            //黄
  tft.printf("Res=320 x 240\n");
  tft.setTextColor(ILI9341_BLUE);              //青
  tft.printf("ILI9341\n");
}

void loop()
{
}

結果

タッチパネル

説明

RaspberryPi Picoでのタッチ座標の読み取りをします。
読み取った座標をLCDの解像度にリスケーリングし、白いポインタを描画します。
タッチ座標を読み出し後LCD座標に変換して白ポインタで着色します。

タッチパネルの読み取り座標と、LCDの座標に変換した値をシリアル出力します。

配線

画面の表示とタッチの読み取りはSPI0(MOSI, MISO, SCK)を共通で使用します。
タッチに使用するCSピンは画面とは別のCSピンを指定してください。

Raspberry Pi Pico配線ILI9341
3.3VVCC
3.3VLED
GNDGND
GPIO17(SPI0 CS)CS
GPIO22Reset
GPIO28D/C
GPIO19(SPI0 TX)SDI(MOSI)
GPIO18(SPI0 SCK)SCK
GPIO20T_CS
GPIO16(SPI0 MISO)T_OUT(MISO)
GPIO18(SPI0 SCK)T_CLK
GPIO19(SPI0 MOSI)T_DIN(MOSI)

スケッチ

#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <XPT2046_Touchscreen.h>
#include <SPI.h>


#define COMMON_SCK  18
#define COMMON_MOSI 19
#define COMMON_MISO 16

#define TOUCH_CS 20

#define TFT_DC  28
#define TFT_CS  17
#define TFT_RST 22

#define RADIUS (2)

XPT2046_Touchscreen ts(TOUCH_CS);
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST);

void setup()
{
  Serial.begin(115200);

  //ESP SPI ピン設定
  SPI.setTX(COMMON_MOSI);
  SPI.setRX(COMMON_MISO);
  SPI.setSCK(COMMON_SCK);

  //表示開始
  tft.begin();
  tft.setRotation(1);
  tft.setTextSize(2);
  tft.fillScreen(ILI9341_BLACK);

  //タッチ入力開始
  ts.begin();
  ts.setRotation(3);

}

void loop()
{
  //タッチ状態読み取り
  boolean bTouch = ts.touched();

  //タッチがあればタッチされている座標の表示
  if (bTouch == true)
  {
    const int16_t Offset_x = 150;
    const int16_t Offset_y = 150;
    float RateX = (float)320 / (3700 - Offset_x);
    float RateY = (float)240 / (3700 - Offset_y);

    TS_Point tPoint = ts.getPoint();

    int16_t x = (float)(tPoint.x - Offset_x) * RateX;
    int16_t y = (float)(tPoint.y - Offset_y) * RateY;
    tft.fillCircle(x, y, RADIUS, ILI9341_WHITE);
    Serial.printf("(x,y) = (%d, %d) : (%d, %d)\r\n", tPoint.x, tPoint.y, x, y);
  }
}

結果

タッチの座標を読み出しました。
読み取ったタッチパネルの座標のばらつきは原点に近いほど大きく、遠いほど小さい傾向がありました。
画像は右下が原点ですが、バラツキの大きさが絵の線の太さになってあらわれました。

表示(SDカードからjpgファイルの表示)

説明

SDカードリーダよりjpgファイル”test.jpg”を読み取り、LCDに表示をします。

SDカードのルートフォルダには”test.jpg”ファイルを保存してください。
jpgファイルのサイズは480 x 320で作成します。

配線

RP2040系(Raspberry Pi Pico)を使用した配線を掲載します。
SPI0をLCDとSDカードリーダ共通で使用します。

LCDのSDカードリーダも使用することができます。
電源とGND以外をシルク記載のピンに接続します。

Raspberry pi pico配線ILI9341配線TFカードリーダ
3.3VVCCVCC
GNDGNDGND
GPIO17(SPI0 CS)TFT CS
GPIO22TFT Reset
GPIO28TFT AO(D/C)
GPIO19(SPI0 MOSI)TFT SDAMOSI
GPIO18(SPI0 SCK)TFT SCKSCK
3.3VLED
GPIO21SD CS
GPIO16(SPI0 MISO)MISO
GPIO20CS

スケッチ

#include <SPI.h>
#include <SD.h>

#include <TJpg_Decoder.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_GFX.h>

//TFT SD共通ピン設定
#define COMMON_MOSI   19
#define COMMON_SCK    18

//TFTピン設定
#define TFT_CS        17
#define TFT_RST       22
#define TFT_DC        28

//SDピン設定
#define SD_CS         21  
#define SD_MISO       16
#define FILENAME      "/test.jpg"

#define BLACK   0x0000      //パレット 黒
#define WHITE   0xFFFF      //パレット 白

// JPGの最大サイズ(バッファを静的に確保するようにしているため、決め打ち。取り扱う最大ファイルサイズで変えるようにする)
#define JPG_SIZE_MAX (100 * 1024) //MAX 100KByteを想定

Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST);

struct jpg_file
{
  size_t size;
  uint8_t buf[JPG_SIZE_MAX];
};

jpg_file jpg;

//デコードを行うコールバック関数
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
{
  if (y >= tft.height())
    return 0;

  tft.drawRGBBitmap(x, y, bitmap, w, h);

  return 1;
}

void setup()
{
  //SPIピン設定
  SPI.setTX(COMMON_MOSI);
  SPI.setRX(SD_MISO);
  SPI.setSCK(COMMON_SCK);

  //TFTの初期化と初期設定
  tft.begin();                   //Init ILI9431初期化  
  tft.fillScreen(BLACK);        //背景の塗りつぶし
  tft.setRotation(3);

  Serial.begin(115200);
  //while(!Serial);
  delay(1000);
  
  //SDカードリーダの初期化とファイルの読み取り
  if (!SD.begin(SD_CS, SD_SCK_MHZ(8)))
  {
    Serial.println("SD initialization failed!");
    while(1);
  }

  TJpgDec.setCallback(tft_output);

  File jpgFile = SD.open(FILENAME, FILE_READ);
  if (!jpgFile)
  {
    Serial.printf("Open file failed [%s]\r\n", FILENAME);
    while(1);
  }

  jpg.size = jpgFile.size();

  if(sizeof(jpg.buf) < jpg.size) 
  {
    Serial.println("File size over");
    return;
  }

  //ファイル情報の表示
  uint16_t w = 0, h = 0;
  Serial.printf("file size = %d bytes\r\n", jpgFile.readBytes((char *)jpg.buf, jpg.size));
  TJpgDec.getJpgSize(&w, &h, jpg.buf, jpg.size);
  Serial.printf("Width = %d, height = %d\r\n", w, h);

  TJpgDec.setJpgScale(1);
  TJpgDec.drawJpg(0, 0, jpg.buf, jpg.size);             //画像の表示

  jpgFile.close();
}

void loop()
{
}

結果

SDカード内のjpgファイルの表示をしました。

コメント

タイトルとURLをコピーしました