ArduinoでTFT LCDディスプレイ(ST7789)を使ってみる

はじめに
ArduinoでTFT LCDディスプレイ(ST7789)を使ってみました。ご紹介するTFT LCDディスプレイはSPI通信ですが、CS(チップセレクト)のない製品でした。そのため複数のSPIデバイスを繋ぐことができません。単体で動かす場合でしたら問題なく表示できました。RGBフルカラーにも対応してます。この記事では、Arduino UnoでTFT LCDディスプレイに文字や図形を表示する方法や、注意点などまとめました。
ゴール
下記動画のように、ArduinoとTFT LCDディスプレイを繋いでサンプルプログラムを実行するまでをゴールとします。
SPI接続なのにCS端子がないTFTを買ってしまって…ようやく動かせました。5V供給しないと落ちちゃったり、SPI_MODE2じゃないとダメだったり、苦労しました😅
つかうもの
この記事で使うものをご紹介いたします。
TFT LCDディスプレイ(ST7789)
スペックは次のとおりです。
項目 | 値 |
---|---|
サイズ | 1.3インチ |
表示モード | ノーマリーブラックIPS |
入力データ | SPIインタフェース |
ドライブIC | ST7789VW |
外形寸法 | 27.78(W)* 39.22(H)* 3.0 +/- 0.1(T)mm |
解像度 | 240RGB×240ドット |
液晶表示エリア | 23.4(W)* 23.4(H) |
ドットピッチ | 0.0975(H)×0.0975(V)mm |
使用温度 | -20〜70℃ |
重量 | 6.1g |
注意点としまして、この製品はSPI接続ですが、CS端子がないのです。そのため複数のSPIデバイスを接続したい場合に問題になります。実際、こちらの記事で紹介した SPI接続するSDカードモジュール を追加したところ、TFT LCDディスプレイは正常に表示しませんでした。
ですから、複数のSPIデバイスを接続したい場合は、チップセレクトのある商品を選びましょう。下記のディスプレイはST7735チップですが、ST7789と似た感じでプログラミングできます。ST7735の使い方は にまとめました。
TFT LCDディスプレイとArduino Unoの配線
TFT LCDディスプレイとArduino Unoは次のとおり配線しました。BKLピンは使いません。

ただしST7789は3.3Vの信号電圧であるため、出力信号が5VピークのArduino Unoに直結して繋ぐのは故障の原因にもなるので推奨されません。 真似する場合は自己責任で行なってください。安全を考えるのであれば、ロジックレベル変換モジュールや分圧抵抗で信号電圧を3V3へ変換して接続することをおすすめします。
TFT LCDディスプレイ | Arduino | ESP32 | 説明 |
---|---|---|---|
BLK | 未接続 | 未接続 | バックライト制御 |
DC | 8 | 2 | データ/コマンド制御 |
RES | 9 | 4 | リセット信号入力 |
SDA | 11 | 23 | シリアルデータ入力 |
SCL | 13 | 18 | シリアルクロック |
VCC | 5V | 5V | 電源 |
GND | GND | GND | Ground |
ライブラリのインストール
ST7789搭載のTFT LCDディスプレイを動かすには、最低限
Adafruit_GFX
Adafruit_SPITFT
Adafruit_ST77xx
Adafruit_ST7789
gfxfont
Adafruit_I2CDevice
Adafruit_SPIDevice
が必要になります。下記GitHubリポジトリからインストールするなりしてください。
- https://github.com/adafruit/Adafruit-GFX-Library
- https://github.com/adafruit/Adafruit-ST7735-Library
- https://github.com/adafruit/Adafruit_BusIO
私は最近、VS
CodeにPlatformIOを入れてArduino開発してます。そのため下記のような構成でライブラリを配置してます。基本ライブラリマネージャーは使っておらず、ソースコードをそのままダウンロードして配置させてます。ここでは
display_test.ino
にサンプルプログラムを記述しました。
.
├── lib
│ ├── README
│ └── TFT_ST7789
│ ├── Adafruit_GFX.cpp
│ ├── Adafruit_GFX.h
│ ├── Adafruit_I2CDevice.cpp
│ ├── Adafruit_I2CDevice.h
│ ├── Adafruit_SPIDevice.cpp
│ ├── Adafruit_SPIDevice.h
│ ├── Adafruit_SPITFT.cpp
│ ├── Adafruit_SPITFT.h
│ ├── Adafruit_SPITFT_Macros.h
│ ├── Adafruit_ST7789.cpp
│ ├── Adafruit_ST7789.h
│ ├── Adafruit_ST77xx.cpp
│ ├── Adafruit_ST77xx.h
│ ├── examples
│ │ └── demo
│ │ └── demo.ino
│ ├── gfxfont.h
│ └── glcdfont.c
├── platformio.ini
├── src
│ └── main.cpp
デフォルトではmain.cppのソースコードがビルドされますが、上記のようなツリー構造の中の
display_test.ino
を対象としてビルドしたい場合は、platformio.ini
を次のように記述することで可能です。

サンプルのスケッチ(ソースコード)
下記URLのサンプルコードをそのまま動かしてみました。 https://github.com/jumejume1/tft240x240-spi/blob/master/tft.ino
/**
* 下記URLのサンプルコードを元に注釈を入れています。
* https://github.com/jumejume1/tft240x240-spi/blob/master/tft.ino
*
* @date 2022-11-21
* @brief Modified by https://101010.fun
*/
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <Arduino.h>
#include <SPI.h>
// TFTの電源電圧は5Vに繋ぐ(3.3Vだと落ちる)
// TFT SCL -> 13
// TFT SDA -> 11
#define TFT_CS 10 // CS端子がないので実際には接続しないし使わない
#define TFT_RST 9
#define TFT_DC 8
= Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
Adafruit_ST7789 tft
float p = 3.1415926;
void setup() {
.begin(115200);
Serial(500);
delay.init(240, 240, SPI_MODE2); // SPI_MODE2指定しないと動かない
tft(1000);
delay.println(F("Initialized"));
Serial
uint16_t time = millis();
.fillScreen(ST77XX_BLACK);
tft= millis() - time;
time
.println(time, DEC);
Serial(500);
delay
// large block of text
.fillScreen(ST77XX_BLACK);
tft(
testdrawtext"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur "
"adipiscing ante sed nibh tincidunt feugiat. Maecenas enim massa, "
"fringilla sed malesuada et, malesuada sit amet turpis. Sed porttitor "
"neque ut ante pretium vitae malesuada nunc bibendum. Nullam aliquet "
"ultrices massa eu hendrerit. Ut sed nisi lorem. In vestibulum purus a "
"tortor imperdiet posuere. ",
);
ST77XX_WHITE(1000);
delay
// tft print function!
();
tftPrintTest(4000);
delay
// a single pixel
.drawPixel(tft.width() / 2, tft.height() / 2, ST77XX_GREEN);
tft(500);
delay
// line draw test
(ST77XX_YELLOW);
testlines(500);
delay
// optimized lines
(ST77XX_RED, ST77XX_BLUE);
testfastlines(500);
delay
(ST77XX_GREEN);
testdrawrects(500);
delay
(ST77XX_YELLOW, ST77XX_MAGENTA);
testfillrects(500);
delay
.fillScreen(ST77XX_BLACK);
tft(10, ST77XX_BLUE);
testfillcircles(10, ST77XX_WHITE);
testdrawcircles(500);
delay
();
testroundrects(500);
delay
();
testtriangles(500);
delay
();
mediabuttons(500);
delay
.println("done");
Serial(1000);
delay}
void loop() {
.invertDisplay(true);
tft(500);
delay.invertDisplay(false);
tft(500);
delay}
void testlines(uint16_t color) {
.fillScreen(ST77XX_BLACK);
tftfor (int16_t x = 0; x < tft.width(); x += 6) {
.drawLine(0, 0, x, tft.height() - 1, color);
tft(0);
delay}
for (int16_t y = 0; y < tft.height(); y += 6) {
.drawLine(0, 0, tft.width() - 1, y, color);
tft(0);
delay}
.fillScreen(ST77XX_BLACK);
tftfor (int16_t x = 0; x < tft.width(); x += 6) {
.drawLine(tft.width() - 1, 0, x, tft.height() - 1, color);
tft(0);
delay}
for (int16_t y = 0; y < tft.height(); y += 6) {
.drawLine(tft.width() - 1, 0, 0, y, color);
tft(0);
delay}
.fillScreen(ST77XX_BLACK);
tftfor (int16_t x = 0; x < tft.width(); x += 6) {
.drawLine(0, tft.height() - 1, x, 0, color);
tft(0);
delay}
for (int16_t y = 0; y < tft.height(); y += 6) {
.drawLine(0, tft.height() - 1, tft.width() - 1, y, color);
tft(0);
delay}
.fillScreen(ST77XX_BLACK);
tftfor (int16_t x = 0; x < tft.width(); x += 6) {
.drawLine(tft.width() - 1, tft.height() - 1, x, 0, color);
tft(0);
delay}
for (int16_t y = 0; y < tft.height(); y += 6) {
.drawLine(tft.width() - 1, tft.height() - 1, 0, y, color);
tft(0);
delay}
}
void testdrawtext(char *text, uint16_t color) {
.setCursor(0, 0);
tft.setTextColor(color);
tft.setTextWrap(true);
tft.print(text);
tft}
void testfastlines(uint16_t color1, uint16_t color2) {
.fillScreen(ST77XX_BLACK);
tftfor (int16_t y = 0; y < tft.height(); y += 5) {
.drawFastHLine(0, y, tft.width(), color1);
tft}
for (int16_t x = 0; x < tft.width(); x += 5) {
.drawFastVLine(x, 0, tft.height(), color2);
tft}
}
void testdrawrects(uint16_t color) {
.fillScreen(ST77XX_BLACK);
tftfor (int16_t x = 0; x < tft.width(); x += 6) {
.drawRect(tft.width() / 2 - x / 2, tft.height() / 2 - x / 2, x, x,
tft);
color}
}
void testfillrects(uint16_t color1, uint16_t color2) {
.fillScreen(ST77XX_BLACK);
tftfor (int16_t x = tft.width() - 1; x > 6; x -= 6) {
.fillRect(tft.width() / 2 - x / 2, tft.height() / 2 - x / 2, x, x,
tft);
color1.drawRect(tft.width() / 2 - x / 2, tft.height() / 2 - x / 2, x, x,
tft);
color2}
}
void testfillcircles(uint8_t radius, uint16_t color) {
for (int16_t x = radius; x < tft.width(); x += radius * 2) {
for (int16_t y = radius; y < tft.height(); y += radius * 2) {
.fillCircle(x, y, radius, color);
tft}
}
}
void testdrawcircles(uint8_t radius, uint16_t color) {
for (int16_t x = 0; x < tft.width() + radius; x += radius * 2) {
for (int16_t y = 0; y < tft.height() + radius; y += radius * 2) {
.drawCircle(x, y, radius, color);
tft}
}
}
void testtriangles() {
.fillScreen(ST77XX_BLACK);
tftuint16_t color = 0xF800;
int t;
int w = tft.width() / 2;
int x = tft.height() - 1;
int y = 0;
int z = tft.width();
for (t = 0; t <= 15; t++) {
.drawTriangle(w, y, y, x, z, x, color);
tft-= 4;
x += 4;
y -= 4;
z += 100;
color }
}
void testroundrects() {
.fillScreen(ST77XX_BLACK);
tftuint16_t color = 100;
int i;
int t;
for (t = 0; t <= 4; t += 1) {
int x = 0;
int y = 0;
int w = tft.width() - 2;
int h = tft.height() - 2;
for (i = 0; i <= 16; i += 1) {
.drawRoundRect(x, y, w, h, 5, color);
tft+= 2;
x += 3;
y -= 4;
w -= 6;
h += 1100;
color }
+= 100;
color }
}
void tftPrintTest() {
.setTextWrap(false);
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(0, 30);
tft.setTextColor(ST77XX_RED);
tft.setTextSize(1);
tft.println("Hello World!");
tft.setTextColor(ST77XX_YELLOW);
tft.setTextSize(2);
tft.println("Hello World!");
tft.setTextColor(ST77XX_GREEN);
tft.setTextSize(3);
tft.println("Hello World!");
tft.setTextColor(ST77XX_BLUE);
tft.setTextSize(4);
tft.print(1234.567);
tft(1500);
delay.setCursor(0, 0);
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(0);
tft.println("Hello World!");
tft.setTextSize(1);
tft.setTextColor(ST77XX_GREEN);
tft.print(p, 6);
tft.println(" Want pi?");
tft.println(" ");
tft.print(8675309, HEX); // print 8,675,309 out in HEX!
tft.println(" Print HEX!");
tft.println(" ");
tft.setTextColor(ST77XX_WHITE);
tft.println("Sketch has been");
tft.println("running for: ");
tft.setTextColor(ST77XX_MAGENTA);
tft.print(millis() / 1000);
tft.setTextColor(ST77XX_WHITE);
tft.print(" seconds.");
tft}
void mediabuttons() {
// play
.fillScreen(ST77XX_BLACK);
tft.fillRoundRect(25, 10, 78, 60, 8, ST77XX_WHITE);
tft.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_RED);
tft(500);
delay// pause
.fillRoundRect(25, 90, 78, 60, 8, ST77XX_WHITE);
tft.fillRoundRect(39, 98, 20, 45, 5, ST77XX_GREEN);
tft.fillRoundRect(69, 98, 20, 45, 5, ST77XX_GREEN);
tft(500);
delay// play color
.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_BLUE);
tft(50);
delay// pause color
.fillRoundRect(39, 98, 20, 45, 5, ST77XX_RED);
tft.fillRoundRect(69, 98, 20, 45, 5, ST77XX_RED);
tft// play color
.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_GREEN);
tft}
私が購入したTFT LCDディスプレイですと、SPI_MODE2
で通信しないと動作しませんでした。SPI_MODE
は、データを読み取るタイミングを決める設定で、クロックの立ち上がりや立ち下がりなどを決める要素になります。詳しくは
こちら
をご覧ください。
数字をカウントアップして表示させる(ソースコード)
実用的に使うであろう数値などのデータだけ表示させるサンプルプログラムを書いてみました。
/**
* @date 2022-11-21
* @copyright https://101010.fun
* @author Toshihiko Arai
*/
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <Arduino.h>
#include <SPI.h>
// TFTの電源電圧は5Vに繋ぐ(3.3Vだと落ちる)
#define TFT_CS 10 // CS端子がないので実際には接続しないし使わない
#define TFT_RST 9
#define TFT_DC 8
= Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
Adafruit_ST7789 tft
int number = 0;
void setup() {
.begin(115200);
Serial(500);
delay.init(240, 240, SPI_MODE2); // SPI_MODE2指定しないと動かない
tft(1000);
delay.fillScreen(ST77XX_BLACK);
tft.setRotation(3); // 画面を回転させる
tft}
void loop() {
char buff[4];
(buff, "%2d", number); // 数字を右寄せで表示させるため
sprintf.setTextWrap(false);
tft
.fillScreen(ST77XX_BLACK);
tft.setCursor(20, 50);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(18);
tft.println(buff);
tft(500);
delay
++;
number}
数字が一桁の場合、sprintf
を使って右寄せに配置させてます。画面をクリアしないと文字がどんどん重なってしまいますので、fillScreen(ST77XX_BLACK)
で画面を真っ黒に埋めてます。fillScreen
の中身は fillRect
で、四角形で画面を塗りつぶしているだけです。ところで実際このように数字をカウントアップさせて描画すると、下の映像のようになります。

画面クリアする際のチラつき(フリッカー)が気になります。ArduinoのスペックやTFTモジュール、SPI通信の制限により、これ以上フリッカーを抑えることは難しそうです。ちょっと残念です。
Arduino_ST7789_Fast というライブラリ を見つけまして、描画速度が速そうなので使ってみました。映像だと分かりづらいですが、若干フリッカーが減った感じがしますが。これでも満足のいく結果ではありませんね(´・_・`)

高速描画へ改善(フリッカーノイズ除去)
ここまでで、fillScreen
で画面全体を更新させてしまうとどうしてもフリッカーノイズが発生してしまいます。
そこでスムーズに描画する方法はないかとさらに調べたところ、 ArduinoフォーラムにFast7Segment
というソースコードが見つかりました。ソースを覗くと、fillScreen
で画面更新するのではなく、文字の領域部分を
drawFastHLine
や drawFastVLine
といった関数で更新されています。
ソースコードを少し改造してマイナスと小数点表現に対応し高速描画の実現に成功しました。動画はその実験の様子です。
関連記事
- ESP32でST7735 TFT LCD液晶ディスプレイを使ってみる
- 【Arduino】非接触温度センサ(GY-906)をつかってみた
- ESP32でAdafruit STEMMA Soil Sensorを使って土壌水分量と温度の測定
- Raspberry PiでOLEDに文字表示
- トランジスタのhFE測定方法