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カードリーダ(非搭載モデルあり) |
販売形式では、タッチパネルの非搭載のモデル、SDカードリーダの非搭載のモデルがあります。
購入前に商品情報を確認してください。
ピン配置
![](https://tamanegi-digick.com/wp-content/uploads/2024/05/ili9341_pin-1024x438.jpg)
外観
前面
![](http://tamanegi.digick.jp/wp-content/uploads/2022/11/ILI9341-top.jpg)
背面
![](http://tamanegi.digick.jp/wp-content/uploads/2022/11/ILI9341-back.jpg)
2.4inchと3.2inchで同じ画像を表示しました。
解像度は同じ320 x 240 です。
画面は大きいほうが視認性はいいですが、細かく見ると画素の粗さが見えます。
画像では色味に違いが見えますが、TN方式の視野角の狭さが影響しています。
2.4inchで長手方向、3.2inchでは短手方向が影響します。
![](https://tamanegi-digick.com/wp-content/uploads/2024/05/ili9341-2432.jpg)
長手方向を斜めから見ると2.4inchは黒っぽく変化します。
3.2inchはあまり変化がありません。
![](https://tamanegi-digick.com/wp-content/uploads/2024/05/ili9341-2432-h.jpg)
短手方向を斜めから見ると3.2inchは黒っぽく色味が変化します。
2.4inchはあまり変化がありません。
![](https://tamanegi-digick.com/wp-content/uploads/2024/05/ili9341-2432-v.jpg)
注意 : タッチパネルについて
画像赤丸箇所にドライバICあるものがタッチパネル付きです。
![](http://tamanegi.digick.jp/wp-content/uploads/2023/02/ILI9341-HR2046.png)
ILI9341の感圧式タッチドライバにはHR2046が実装されていることが多いです。
HR2046はXPT2046の互換品らしいですが動作不安定のものが多く、可能であれば購入の際には商品画像だけでなく商品紹介や販売先に実装ドライバの種類を確認したいところです。
使用感
2~3inch程度になると画像の表現力や視認性もよくなります。
タッチパネルの操作性も現実的に使えるサイズ感になったと実感します。
ハンディタイプのゲーム機や操作画面として十分使いやすいサイズだと思います。
感圧タッチパネルの分解能は4K x 4Kです。
表示の解像度とタッチパネルの分解能が違うのと縦横比が違うので、簡単な変換計算が必要です。
もともと実装されているタッチICドライバ(HR2046)は読み取りばらつきや不動などのトラブルが多いです。
タッチパネルの少しシートには軽いたわみがあります。
タッチパネルを使用しなければ、タッチパネルシートのないモデルを選択することでたわみのない表示で使用できます。
タッチ付きモデルでは樹脂のタッチペンが付属されていますが、普通の筆圧でも画面に傷が残りやすいので、スマートフォンなどの保護シートを貼って使用することで傷の回避ができます。
下の画像は左が保護シートを貼って作業、右は購入時に貼ってあった養生シートのものです。
養生シートは傷が入りやすく使い込むと破れてきます。
![](https://tamanegi-digick.com/wp-content/uploads/2024/06/ili9341-film.jpg)
準備
ライブラリ
LCDを制御するためのライブラリにはAdafruit製ライブラリを使用します。
ライブラリ名 | 用途 | 検索 | 確認時バージョン |
---|---|---|---|
Adafruit GFX Library by Adafruit | グラフィック | GFX | 1.11.3 |
Adafruit ILI9341 by Adafruit | グラフィック | ILI9341 | 1.5.12 |
XPT2046_Touchscreen by Paul Stoffregen | タッチパネル | XPT2046 | 1.4 |
TJpg_Decoder by Bodmer | jpg表示 | jpg | 1.0.8 |
Adafruit ILI9341 by Adafruitライブラリをインストールすると、依存ライブラリが不足している場合不足している依存関係のインストールの問い合わせがあります。
「すべてをインストール」を選択してください。
![](http://tamanegi.digick.jp/wp-content/uploads/2022/11/ILI9341-library.png)
タッチドライバの交換
所有しているタッチ付きILI9341を3個所有していますが、すべて座標の読み出しにトラブルがありました。
感圧式タッチパネルのドライバICにはHR2046が使われていてXPT2046の互換品のようです。
下図はドライバIC交換前のHR2046の座標の読み取りばらつきです。
![](https://tamanegi-digick.com/wp-content/uploads/2024/06/ili9341-hr2046.png)
XPT2046に交換した後の読み取り座標のばらつきは下図です。
HR2046に比べてばらつきの幅が1/3程度になりました。
![](https://tamanegi-digick.com/wp-content/uploads/2024/06/ili9341-xpt2046.png)
不動や動作不良の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は任意のピンを使用できます。
![](https://tamanegi-digick.com/wp-content/uploads/2024/05/ili9341-lcd-rpi-wiring-1024x375.jpg)
スケッチ
#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()
{
}
結果
![](https://tamanegi-digick.com/wp-content/uploads/2024/05/ili9341-lcd-rpi-result.jpg)
ESP32系 ESP32-WROOMを使った表示
説明
ESP32-WROOM NODE-MCUでのILI9341制御をします。
LCDの情報(固定文字列)を表示します。
サンプルはESP32のVSPI(SPIO0)を使用した配線とスケッチです。
HSPI(SPI1)を使用する場合SPI1の記述のコメントを解除し、SPI0をコメント化します。
そのほかの描画サンプルについては
スケッチ例 > Adafruit ILI9341 > graphictest を参照してください。
配線
VSPIを使う場合のサンプルです。
![](https://tamanegi-digick.com/wp-content/uploads/2024/05/ili9341-lcd-esp32-1024x422.png)
スケッチ
#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()
{
}
結果
![](https://tamanegi-digick.com/wp-content/uploads/2024/05/ili9341-lcd-esp32-result.jpg)
タッチパネル
説明
RaspberryPi Picoでのタッチ座標の読み取りをします。
読み取った座標をLCDの解像度にリスケーリングし、白いポインタを描画します。
タッチ座標を読み出し後LCD座標に変換して白ポインタで着色します。
タッチパネルの読み取り座標と、LCDの座標に変換した値をシリアル出力します。
配線
画面の表示とタッチの読み取りはSPI0(MOSI, MISO, SCK)を共通で使用します。
タッチに使用するCSピンは画面とは別のCSピンを指定してください。
Raspberry Pi Pico | 配線 | ILI9341 |
---|---|---|
3.3V | 赤 | VCC |
3.3V | 赤 | LED |
GND | 黒 | GND |
GPIO17(SPI0 CS) | 黄 | CS |
GPIO22 | 青 | Reset |
GPIO28 | 橙 | D/C |
GPIO19(SPI0 TX) | 緑 | SDI(MOSI) |
GPIO18(SPI0 SCK) | 紫 | SCK |
GPIO20 | 黄 | T_CS |
GPIO16(SPI0 MISO) | 茶 | T_OUT(MISO) |
GPIO18(SPI0 SCK) | 紫 | T_CLK |
GPIO19(SPI0 MOSI) | 緑 | T_DIN(MOSI) |
![](http://tamanegi.digick.jp/wp-content/uploads/2022/11/ILI9341-TOUCH-RP2040-SPI0-wiring-1024x375.png)
スケッチ
#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);
}
}
結果
タッチの座標を読み出しました。
読み取ったタッチパネルの座標のばらつきは原点に近いほど大きく、遠いほど小さい傾向がありました。
画像は右下が原点ですが、バラツキの大きさが絵の線の太さになってあらわれました。
![](https://tamanegi-digick.com/wp-content/uploads/2024/06/ili9341-xp2046-rpi-result.jpg)
表示(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.3V | 赤 | VCC | 赤 | VCC |
GND | 黒 | GND | 黒 | GND |
GPIO17(SPI0 CS) | 黄 | TFT CS | ||
GPIO22 | 青 | TFT Reset | ||
GPIO28 | 橙 | TFT AO(D/C) | ||
GPIO19(SPI0 MOSI) | 緑 | TFT SDA | 緑 | MOSI |
GPIO18(SPI0 SCK) | 紫 | TFT SCK | 紫 | SCK |
3.3V | 赤 | LED | ||
GPIO21 | 黄 | SD CS | ||
GPIO16(SPI0 MISO) | 茶 | 茶 | MISO | |
GPIO20 | 黄 | 黄 | CS |
![](http://tamanegi.digick.jp/wp-content/uploads/2022/11/ILI9341-SD-RP2040-SPI0-wiring-1024x547.jpg)
スケッチ
#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ファイルの表示をしました。
![](http://tamanegi.digick.jp/wp-content/uploads/2023/02/ILI9341-SDjpg-RP2040-result.jpg)
コメント