ラズパイPicoでタッチパネル液晶表示器 ILI9341の使い方

Raspberry Pi Pico タッチパネル液晶の使い方アイキャッチ

「Raspberry Pi Pico」で「ILI9341」を使用した「液晶表示」と「タッチパネル」の使い方を、開発環境に「ArduinoIDE」を使って詳しく紹介します。

まずは、基本的な文字表示やフォント設定の確認から、画面に線を描いて「お絵描き」しながら、タッチパネルの座標取得方法や、線の描画方法を確認します。
最後に、「タッチボタン」や「スライドボリューム」を画面上に配置して、LEDのON/OFF、明るさの調整を行う、GOT(グラフィックオペレーションターミナル)としての使い方まで、コピペ用サンプルプログラムで詳しく紹介します。
今回は「Raspberry Pi Pico2」を使用しましたが「Pico」でも「PicoW」でも同じように使用できます。
「Pico2」が一番高速に動作しますが、お絵描き速度やスライドボリュームの追従等「Pico/PicoW」と大きな変化は感じられませんでした。

「ラズパイPico」の基本的な使用方法や「ArduinoIDE」を使用した開発環境の準備等は以下のリンクで詳しく紹介しています。

ラズパイPico/Pico2/PicoWのArduinoIDE2のインストール方法や使い方紹介
Raspberry Pi Pico/Pico2/PicoWのC言語での開発環境 ArduinoIDE2のインストールから初期設定、使い方、Lチカで動作確認まで詳しく紹介
スポンサーリンク

1.Raspberry Pi Picoとは

Raspberry Pi Pico(ラズパイ Pico)とは、イギリスのRaspberry Pi財団が開発したマイコンボードで、「Python」や「C言語」でプログラムすることができます。

本体基板上の入出力端子に、スイッチやセンサ、各種制御モジュール、通信モジュールを接続することで、それらをプログムによって制御することができ、プログラミング学習やホビー、組込み用途に最適です。


「ラズパイ Pico」の端子配列は以下のようになります。
(「Pico2」やWi-Fi通信機能搭載の「Pico W」もLED以外の基本的な端子配列は同じです。)

ラズパイ(Raspberry Pi) Pico 端子配列
LEDの端子番号については「LED_BUILTIN」で指定することで「Pico/Pico2/PicoW」それぞれのLED番号が自動で設定されます。

「ラズパイ Pico」の使い方や端子機能、開発環境の準備については、以下のリンク先で詳しく紹介しています。

ラズパイPico/PicoWの使い方を3つの開発環境Python、ArduinoIDE、PlatformIOで紹介
Raspberry Pi Pico/Pico Wの使い方を端子配列からPython(MicroPython)とC言語の開発環境、Lチカ方法まで紹介。PythonはTonny、C言語はArduinoIDEとPlatformIOの3種類で詳しく紹介します。
スポンサーリンク

2.タッチパネル液晶表示器 ILI9341とは

「ILI9341」とは、タッチパネル搭載のTFT液晶表示器で、今回使用するのは2.8インチ(320x240Pixel)のものです

外観は下画像のようになります。

TFT液晶表示器ILI9341の外観

今回購入したものの梱包状態は上画像になります。
タッチパネル機能のない(制御ICが実装されていない)ものもあるようなので、注意して購入しましょう。

TFT液晶表示器ILI9341の外観

タッチペンが付属していましたが、全てに付属はしてないかもしれません。なくてもいいと思いましたが、動作確認するとスマホのような精度はないので、タッチペンは必須と思いました。

TFT液晶表示器ILI9341の外観

ピンヘッダーは実装済みのものでした。
SDカードリーダーも実装されていますが、こちらの端子は未実装でした。

TFT液晶表示器ILI9341の外観

裏面にタッチパネルの制御ICが実装されています。
実装されてないものもあるようなので注意して購入しましょう。
今回実装されていた制御ICは「XPT2046」でした。

SDカードリーダーの使い方は、以下で詳しく紹介しています。

ラズパイPicoタッチパネル液晶 ILI9341でSDカードの使い方
Raspberry Pi PicoでSDカードの読み書きや、JPEG画像読み込みを、タッチパネル液晶ILI9341に搭載のSDカードリーダーを使用して詳しく紹介
タッチパネルの制御ICは「MSP2807」が実装されているものもあるようなので、この場合はここのサンプルプログラムでは動作しないかもしれません。
電源電圧は「3.3V〜5.0V」ですが、制御入力は3.3Vなのでご注意ください。

今回購入したものは以下のアマゾンのものです。

スポンサーリンク

3.配線図

配線図について、液晶表示のみの場合と、液晶表示とタッチパネルを使う場合とで紹介します。

・液晶表示のみの場合

液晶表示だけを使用する場合の配線図は以下のようになります。

ILI9341配線図(TFTのみ)

実際に配線した様子は下画像のようになります。
ブレッドボードを横に2枚連結して、ジャンパー線で接続しています。

配線部を拡大すると下画像のようになります。

・液晶表示+タッチパネルの場合

液晶表示とタッチパネルを使用する場合の配線図は以下のようになります。
液晶表示のみの場合の配線に、タッチパネルの配線4本を追加しています。

ILI9341配線図(TFT&Touch)
タッチパネルのSPI通信端子は「T_DIN = MOSI」、「T_DO = MISO」になります。
「T_IRQ」はタッチが検出された時の割り込み信号で、タッチ時にLOWレベルになるものですが、今回は使用しません。

実際に配線した様子は下画像のようになります。
ブレッドボードを横に2枚連結して、ジャンパー線で接続しています。

配線部を拡大すると下画像のようになります。

ジャンパーリード配線でもできますが・・・

最初はジャンパーリード配線を使用して下画像のように動作確認していましたが、タッチパネル操作時にリード線に触れたり配線が揺れたりすると、接触不良で画面がチラついたり、暗くなったり、表示が真っ白になったりしたので、あまりおすすめできません。

半田付けするのが一番良いですが、動作確認には以下のようなブレッドボードやジャンパー線を使用して、確実に配線することをおすすめします。

4.ライブラリの準備

開発環境「ArduinoIDE」で使用するライブラリは以下になります。
事前にインストールしておいてください。

ライブラリ名用途バージョン検索名
Adafruit GFX Library文字フォントの指定や画面の
チラツキ防止の「スプライト」用
1.11.1GFX
Adafruit BusIOAdafruit GFXライブラリを使用するために必要1.16.2busio
Adafruit ILI9341液晶表示器ILI9341の制御用1.6.19341
XPT2046_Touchscreenタッチパネル制御用1.4xpt paul

ライブラリはプログラムの初めにヘッダーとして以下のように書いて呼び出して使用します。

#include <Adafruit_GFX.h>         // Adafruitのグラフィックスライブラリ
#include <Adafruit_ILI9341.h>     // 液晶表示器 ILI9341 制御用ライブラリ
#include <XPT2046_Touchscreen.h>  // タッチパネル制御用ライブラリ
Adafruit BusIOはヘッダーに書く必要はありません。

「ArduinoIDE」のライブラリのインストール方法は以下のリンクで詳しく紹介しています。

Arduino IDE 2のインストール方法、初期設定、使い方
バージョンアップして使いやすくなったArduino IDE 2のインストールから使い方まで詳しく紹介、便利な機能やM5Stack、ラズパイPicoでの使用方法も紹介します。

5.簡単な「液晶表示」方法の確認

まずは基本的な液晶画面の表示方法を確認していきます。

・動作紹介

サンプルプログラムでは下画像のように、単純な文字表示と、変化する数値の確認を行なっています。

画面中央にはフォントを指定した文字を表示し、左上には電源ONからの経過時間をms単位で表示しています。

画面への出力はメモリ内の描画領域を一括表示させる「スプライト」を行うことで「チラツキ」のない表示ができます。

・サンプルプログラム

サンプルプログラムは以下になります。
コピペで貼り付けて書き込んでください。コピーは下の黒塗り部右上のアイコンクリックでもできます。

#include <Adafruit_GFX.h>      // Adafruitのグラフィックスライブラリ
#include <Adafruit_ILI9341.h>  // 液晶表示器 ILI9341 制御用ライブラリ
#include <Fonts/FreeSans18pt7b.h> // フォントを読み込み
#include <Fonts/FreeSans12pt7b.h>

#define TFT_WIDTH   320 // 画面幅
#define TFT_HEIGHT  240 // 画面高さ

#define TFT_SCK  18  // 液晶表示の SCK
#define TFT_MOSI 19  // 液晶表示の MOSI
#define TFT_DC   20  // 液晶画面の DC
#define TFT_RST  21  // 液晶画面の RST
#define TFT_CS   22  // 液晶画面の CS

// ILI9341ディスプレイのインスタンスを作成
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST);

// スプライト(メモリ描画領域から一括表示)をcanvasとして準備
// 画面表示をtftではなくcanvasで指定して一括描画することでチラツキなく表示できる
GFXcanvas16 canvas(TFT_WIDTH, TFT_HEIGHT);  // 16bitカラースプライト(オフスクリーンバッファ)

// 初期設定 ----------------------------------------
void setup() {
  // SPI0初期設定
  SPI.setTX(TFT_MOSI); // SPI0のTX(MOSI)
  SPI.setSCK(TFT_SCK); // SPI0のSCK

  // TFT初期設定
  tft.begin();           // TFTを初期化
  tft.setRotation(3);    // TFTの回転を設定(0-3)
  canvas.setTextSize(1); // テキストサイズを設定
}

// メイン -----------------------------------------
void loop() {
  canvas.fillScreen(0x0000); // 背景色
  
  // 文字表示
  canvas.setCursor(48, 125);        // 表示座標指定
  canvas.setTextColor(0xFFFF);      // テキスト色(文字色、背景色)※背景色は省略可
  canvas.setFont(&FreeSans18pt7b);  // フォント指定
  canvas.println("Logikara Blog");  // 表示内容

  // 電源ONからの経過時間カウント表示
  canvas.setFont(&FreeSans12pt7b);  // フォント指定
  canvas.setCursor(0, 22);          // 表示座標指定
  canvas.print(millis());           // 経過時間をms単位で表示

  // スプライト(メモリ内に描画した画面)をTFTに描画
  tft.drawRGBBitmap(0, 0, canvas.getBuffer(), TFT_WIDTH, TFT_HEIGHT);
}

・プログラムの詳細

液晶表示器の初期設定

サンプルプログラムの「16行目」で以下のように、SPI通信の一部の端子を指定して、インスタンスを作成します。これによって「tft.〜」で指定して液晶表示の制御ができるようになります。

// ILI9341ディスプレイのインスタンスを作成
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST);
SPI通信に使用する「MOSI、SCK」に任意の端子を指定してインスタンスを作成することもできますが、通信速度が遅く実用的ではありません。SPI通信本来の通信速度で使用するには上記のように指定して、以下のように初期設定で標準のSPI通信端子を指定します。

初期設定の「25,6行目」で以下のように、SPI通信(SPI0)の標準の通信端子を指定します。
液晶表示器では「MOSI(データ送信)」と「SCK(クロック)」のみ使用し「MISO(データ受信)」はタッチパネルを使う場合に設定します。

// SPI0初期設定
SPI.setTX(TFT_MOSI); // SPI0のTX(MOSI)
SPI.setSCK(TFT_SCK); // SPI0のSCK

画面のチラツキ防止の「スプライト」

20行目」で画面の「チラツキ」を防止するために、メモリ描画領域を一括表示する「スプライト」のインスタンスを、画面サイズを指定して作成しています。

// スプライト(メモリ描画領域から一括表示)をcanvasとして準備
// 画面表示をtftではなくcanvasで指定して一括描画することでチラツキなく表示できる
GFXcanvas16 canvas(TFT_WIDTH, TFT_HEIGHT);  // 16bitカラースプライト(オフスクリーンバッファ)

29〜31行目」で液晶画面の初期設定を行っています。
画面の初期化と画面表示方向の回転のみ「tft.〜」で指定していますが、以降の表示設定は「canvas.〜」で指定します。これによって、表示内容を一旦メモリ内の描画領域に描画します。

// TFT初期設定
tft.begin();           // TFTを初期化
tft.setRotation(3);    // TFTの回転を設定(0-3)
canvas.setTextSize(1); // テキストサイズを設定

50行目」でメモリ内の描画領域に描画した画面の内容を一括表示して、画面の「チラツキ」を抑える「スプライト」を行なっています。

// スプライト(メモリ内に描画した画面)をTFTに描画
tft.drawRGBBitmap(0, 0, canvas.getBuffer(), TFT_WIDTH, TFT_HEIGHT);
「スプライト」を使用しないと、今回のサンプルプログラムでは、画面を黒く塗りつぶしてから文字を表示するという過程が全て描画されます。
画面を黒く塗りつぶして真っ黒になってからデータが表示されるため、画面が点滅するような「チラツキ」が発生します。
試しに「canvas.〜」を「tft.〜」に書き換えると直接描画されて、画面に「チラツキ」が発生するため、違いがはっきりわかると思います。

文字フォントの指定

文字フォントは使用しなくても表示はできますが、「ドット文字」のようになって見にくいので、フォントを指定することをおすすめします。

3,4行目」で以下のように、ヘッダーファイルとしてフォント名を指定して読み込みます。

#include <Fonts/FreeSans18pt7b.h> // フォントを読み込み
#include <Fonts/FreeSans12pt7b.h>

41,45行目」で以下のように「setFont(&フォント名)」で読み込んだフォント名を指定してフォントを設定します。

canvas.setFont(&FreeSans18pt7b);  // フォント指定

canvas.setFont(&FreeSans12pt7b);  // フォント指定

フォントは下画像のように「Adafruit_GFX_Library」内の「Fonts」フォルダ内にたくさんあります。
ファイル名がフォント名になるので、いろいろ読み込んで試してみてください。

Adafruit GFX フォント一覧
Windowsの場合は以下の場所にフォントファイルがあります。
[Documents] → [Arduino ]→ [libraries] → [Adafruit_GFX_Library] → [Fonts]

6.お絵描きで「タッチパネル」の動作確認

タッチパネルの動作確認として、タッチ座標を取得して線を描く、お絵描きパネルで動作確認してみましょう。

・動作紹介

「サンプルプログラム」を書き込んで実行すると、下画像のように画面は真っ白な表示になります。
画面上をタッチペンでなぞると、線が描かれて文字や図形を描くことができます。

ILI9341タッチパネル動作確認
画面右上端の四角をタッチすると画面を消去することができます。

以下は当サイトのブログ名を書いてみたものです。
線はタッチ座標に円を連続して描画することで実現しています。

ILI9341タッチパネル動作確認

字が汚いのは置いといて・・・画面をなぞった通りにお絵描きができます。
線の太さは描画する円の半径を変えることで調節できます。

・サンプルプログラム

サンプルプログラムは以下になります。
コピペで貼り付けて書き込んでください。コピーは下の黒塗り部右上のアイコンクリックでもできます。

#include <Adafruit_GFX.h>         // Adafruitのグラフィックスライブラリ
#include <Adafruit_ILI9341.h>     // 液晶表示器 ILI9341 制御用ライブラリ
#include <XPT2046_Touchscreen.h>  // タッチパネル制御用ライブラリ

#define TFT_WIDTH   320 // 画面幅
#define TFT_HEIGHT  240 // 画面高さ
#define TFT_ROTATION  3 // 画面の回転(タッチパネルの回転と合わせる)

#define COMMON_SCK  18  // 液晶表示とタッチパネル共通の SCK
#define COMMON_MOSI 19  // 液晶表示とタッチパネル共通の MOSI
#define TOUCH_MISO 16   // タッチパネルの MISO
#define TOUCH_CS 17     // タッチパネルの CS
#define TFT_DC   20     // 液晶画面の DC
#define TFT_RST  21     // 液晶画面の RST
#define TFT_CS   22     // 液晶画面の CS

XPT2046_Touchscreen ts(TOUCH_CS); // タッチパネルのインスタンスを作成
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST); // ILI9341ディスプレイのインスタンスを作成

// 初期設定 ----------------------------------------
void setup() {
  //SPI0設定
  SPI.setTX(COMMON_MOSI); // SPI0のTX(MOSI)
  SPI.setRX(TOUCH_MISO);  // SPI0のRX(MISO)
  SPI.setSCK(COMMON_SCK); // SPI0のSCK

  //液晶画面初期設定
  tft.begin();                   // TFTを初期化
  tft.setRotation(TFT_ROTATION); // TFTの回転を設定(0-3)

  //タッチパネル初期設定
  ts.begin();                    // タッチパネル初期化
  ts.setRotation(TFT_ROTATION);  // タッチパネルの回転を設定(液晶画面と合わせる) 
  tft.fillScreen(ILI9341_WHITE); // 画面白色塗りつぶし
  tft.fillRect(300, 1, 19, 18, ILI9341_DARKGREY); // 画面消去ボタン

  // 出力端子初期設定
  pinMode(LED_BUILTIN, OUTPUT);   // LED用端子
  digitalWrite(LED_BUILTIN, HIGH); // LED点灯(電源ランプ)
}
// メイン ------------------------------------------
void loop() {
  if (ts.touched() == true) {        // タッチされていれば
    TS_Point tPoint = ts.getPoint(); // タッチ座標を取得
    int16_t x = (tPoint.x-400) * TFT_WIDTH / (4095-550);  // タッチx座標をTFT画面の座標に換算
    int16_t y = (tPoint.y-230) * TFT_HEIGHT / (4095-420); // タッチy座標をTFT画面の座標に換算

    // 円の連続で線を描画
    tft.fillCircle(x, y, 2, 0x0A08); // タッチ座標に塗り潰し円を描画

    // 描画エリア消去
    if (x > 300 && y < 20) {  // 画面右上角座標をタッチしたら
      tft.fillScreen(ILI9341_WHITE); // 画面白色塗りつぶし
      tft.fillRect(300, 1, 19, 18, ILI9341_DARKGREY); // 消去ボタン
    }
  }
}

・プログラムの詳細

初期設定

サンプルプログラムの「17,18行目」で以下のように、タッチパネルと液晶表示のインスタンスを作成します。これにより、タッチパネルは「ts.〜」、液晶表示は「tft.〜」でそれぞれの各種動作指定ができるようになります。

XPT2046_Touchscreen ts(TOUCH_CS); // タッチパネルのインスタンスを作成
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST); // ILI9341ディスプレイのインスタンスを作成

23〜25行目」の初期設定でSPI通信(SPI0)標準の通信端子を設定します。
「MOSI(データ送信)」と「SCK(クロック)」は共通で「MISO(データ受信)」はタッチパネルのみ使用します。

SPI.setTX(COMMON_MOSI); // SPI0のTX(MOSI)
SPI.setRX(TOUCH_MISO);  // SPI0のRX(MISO)
SPI.setSCK(COMMON_SCK); // SPI0のSCK

初期設定の「29,33行目」で以下のように、液晶画面とタッチパネルの回転を設定しますが両方の回転方向は合わせる必要があります。
また、回転方向によって、表示とタッチ座標にズレが生じますので、回転方向ごとに補正する必要があります。

tft.setRotation(TFT_ROTATION); // TFTの回転を設定(0-3)

ts.setRotation(TFT_ROTATION);  // タッチパネルの回転を設定(液晶画面と合わせる)

タッチ座標の取得、補正

43,44行目」で以下のように、「ts.touched()」でタッチされているかどうかを確認し、タッチされていれば「ts.getPoint()」でタッチ座標(x,y)を取得します。
取得した座標は「tPoint.x」でx座標、「tPoint.y」でy座標を確認することができます。

if (ts.touched() == true) {        // タッチされていれば
  TS_Point tPoint = ts.getPoint(); // タッチ座標を取得

45,46行目」で以下のように、液晶画面とタッチ座標の補正を行なっています。
補正値は個体によって異なると思うので、以下を参考に調整してください。

int16_t x = (tPoint.x-400) * TFT_WIDTH / (4095-550);  // タッチx座標をTFT画面の座標に換算
int16_t y = (tPoint.y-230) * TFT_HEIGHT / (4095-420); // タッチy座標をTFT画面の座標に換算
画面の座標は横画面の場合、左上がx,yで(0, 0)で、右下が(320, 240)です。
タッチの検出範囲は表示画面より広いので、画面表示範囲外になるとマイナス表示になりますが、その状態が正しい補正状態です。

画面の回転が「Rotation(1)」の場合は座標が増えるごとにズレ量も増えるという変化をしました。
この場合は補正が少々複雑になるため、以下参考までに載せておきます。

// Rotation(1)の場合の座標補正計算
int16_t x = (tPoint.x –100) * TFT_WIDTH / (4095 – (tPoint.x /6));
int16_t y = (tPoint.y –100) * TFT_HEIGHT / (4095 – (tPoint.y /10));

塗りつぶし円の連続で線を描画

49行目」で以下のように、取得したタッチ座標に塗りつぶし円を連続的に描画することで線を描いています。円の半径で線の太さが調節でき、色を変えると線の色を変更できます。

// 円の連続で線を描画
tft.fillCircle(x, y, 2, 0x0A08); // タッチ座標に塗り潰し円を描画
円を描く関数の詳細
tft.fillCircle(x座標, y座標, 半径, );
※色の指定は16ビットカラーで指定(サンプルプログラムでは濃い灰色)しますが、「ILI9341_BLACK」や「ILI9341_RED」、「ILI9341_BLUE」のようにライブラリの登録色を指定することもできます。

タッチエリアの検出、画面消去

52〜54行目」で以下のように、取得したタッチ座標の範囲を確認することで、タッチした位置によって動作を分岐させることができます。
サンプルプログラムでは画面の右上角部をタッチすることで画面を白色に塗りつぶして、描画した線を消去しています。

// 描画エリア消去
if (x > 300 && y < 20) {  // 画面右上角座標をタッチしたら
  tft.fillScreen(ILI9341_WHITE); // 画面白色塗りつぶし
  tft.fillRect(300, 1, 19, 18, ILI9341_DARKGREY); // 消去ボタン
今回のお絵描きでは「スプライト」は使用せず直接画面に描画するようにしています。
「スプライト」するとメモリ内の描画領域に描画してから一括表示するため、描画に時間がかかって線が途切れやすくなります。
画面上に数値や文字を表示したい場合は、表示するエリアだけ「スプライト」して、線を描くエリアは直接描画するようにした方が綺麗な線が描けます。

7.タッチパネル式操作表示器(GOT)

最後にタッチパネルを使用した操作表示器としての使い方を紹介します。
産業用途では「GOT(グラフィックオペレーションターミナル)」と呼ばれ「シーケンサ」と組み合わせて使用され、数万から十万以上するものもありますが、自作すれば数千円で製作でき、用途によっては機能的には十分だと思います。

サンプルプログラムの動作について、基本的にはここまで紹介してきたサンプルプログラムの応用になります。
できるだけ関数にまとめて、プログラム内にコメントを細かく書いているので、詳細はコメントを参照してください。

・動作紹介

基本的な「GOT」の機能としては、「タッチボタン」と「データ表示」と思います。あとはスライドボリュームがあるといろいろな調整に使えると思うので、下画像のようなサンプル画面を作製しました。

ILI9341タッチパネル液晶でGOT

動作確認のため、タッチ座標はタッチした時に常に表示されるようにしています。

ILI9341タッチパネル液晶でGOT動作確認

上画像のように「ONボタン」にタッチすると左の「丸ランプ」が点灯します。
この時ラズパイPico本体のLEDを点灯させるようにしています。

ILI9341タッチパネル液晶でGOT動作確認

「OFFボタン」にタッチすると「丸ランプ」は消灯し、ラズパイPico本体のLEDも消灯します。

ILI9341タッチパネル液晶でGOT動作確認

上画像のようにスライドボリューム部にタッチすると、ツマミ部を移動させることができます。
上下に移動させることで画面上の数値が「0〜100」の範囲で変化して、LEDの明るさを調整できます。

ILI9341タッチパネル液晶でGOT動作確認

写真では分かりにくいですがボリュームを下げて数値が小さくなるとLEDが暗くなり、「0」で消灯します。

・サンプルプログラム

サンプルプログラムは以下になります。
コピペで貼り付けて書き込んでください。コピーは下の黒塗り部右上のアイコンクリックでもできます。

#include <Adafruit_GFX.h>         // Adafruitのグラフィックスライブラリ
#include <Adafruit_ILI9341.h>     // 液晶表示器 ILI9341 制御用ライブラリ
#include <XPT2046_Touchscreen.h>  // タッチパネル制御用ライブラリ
#include <Fonts/FreeSerifBold9pt7b.h> // フォントを読み込み
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSans18pt7b.h> 
#include <Fonts/FreeSansBold18pt7b.h> 

#define TFT_WIDTH   320 // 画面幅
#define TFT_HEIGHT  240 // 画面高さ
#define TFT_ROTATION  3 // 画面の回転(タッチパネルの回転と合わせる)

#define COMMON_SCK  18  // 液晶表示とタッチパネル共通の SCK
#define COMMON_MOSI 19  // 液晶表示とタッチパネル共通の MOSI
#define TOUCH_MISO 16   // タッチパネルの MISO
#define TOUCH_CS 17     // タッチパネルの CS
#define TFT_DC   20     // 液晶画面の DC
#define TFT_RST  21     // 液晶画面の RST
#define TFT_CS   22     // 液晶画面の CS

XPT2046_Touchscreen ts(TOUCH_CS); // タッチパネルのインスタンスを作成
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST); // ILI9341ディスプレイのインスタンスを作成

// スプライト(メモリ描画領域から一括表示)をcanvasとして準備
// 画面表示をtftではなくcanvasで指定して一括描画することでチラツキなく表示できる
GFXcanvas16 canvas(TFT_WIDTH, TFT_HEIGHT);  // 16bitカラースプライト(オフスクリーンバッファ)

// 変数宣言
bool lampSignal = false;   // ランプ点灯状態格納用
int16_t slideYValue = 55;  // スライドボリュームY軸座標格納用
float volumeValue = 100;   // スライドボリュームレベル換算値格納用

/******************** テキスト描画関数 ********************/
void drawText(int16_t x, int16_t y, const char* text, const GFXfont* font, uint16_t color) {
  canvas.setFont(font);       // フォント
  canvas.setTextColor(color); // 文字色
  canvas.setCursor(x, y);     // 表示座標
  canvas.println(text);       // 表示内容
}

/******************** ボタン描画関数 ********************/
void drawButton(int x, int y, const char* label, const GFXfont* font, uint16_t bgColor, uint16_t labelColor) {
  canvas.fillRect(x, y, 80, 80, ILI9341_DARKGREY);      // 外枠
  canvas.fillRect(x + 3, y + 3, 74, 74, ILI9341_WHITE); // 境界線
  canvas.fillRect(x + 6, y + 6, 68, 68, bgColor);       // 操作部
  canvas.setFont(font); // 表示ラベル

  // テキストの幅と高さを取得
  int16_t textX, textY; // テキスト位置取得用
  uint16_t textWidth, textHeight; // テキストサイズ取得用
  canvas.getTextBounds(label, x, y, &textX, &textY, &textWidth, &textHeight);
  
  // 中央揃えのための新しいx, y座標の計算
  int16_t centeredX = x + (80 - textWidth) / 2;               // xを中央へ
  int16_t centeredY = y + (80 - textHeight) / 2 + textHeight; // yを下げて中央へ
  
  canvas.setTextColor(labelColor);        // 文字色
  canvas.setCursor(centeredX, centeredY); // 新しいカーソル位置を設定
  canvas.print(label);                    // テキストを描画
}

/***************** ランプ点灯状態更新関数 *****************/
void updateLamp() {
  if (lampSignal == true) { // ランプ状態点灯なら
    canvas.fillCircle(40, 190, 33, ILI9341_CYAN);         // ランプ部点灯
    analogWrite(LED_BUILTIN, (int)(volumeValue * 10.24)); // PWM出力開始
  } else { // ランプ状態消灯なら
    canvas.fillCircle(40, 190, 33, ILI9341_DARKGREY); // ランプ部消灯
    analogWrite(LED_BUILTIN, 0);                      // PWM出力停止
  }
}

// 初期設定 ----------------------------------------
void setup() {
  Serial.begin(115200); // シリアル通信初期化

  //SPI0設定
  SPI.setTX(COMMON_MOSI); // SPI0のTX(MOSI)
  SPI.setRX(TOUCH_MISO);  // SPI0のRX(MISO)
  SPI.setSCK(COMMON_SCK); // SPI0のSCK

  //液晶表示初期設定
  tft.begin();                      // TFTを初期化
  tft.setRotation(TFT_ROTATION);    // TFTの回転を設定(0-3)
  canvas.setTextSize(1);            // テキストサイズ(倍率)
  canvas.fillScreen(ILI9341_BLACK); // 背景色

  //タッチパネル初期設定
  ts.begin();                   // タッチパネル初期化
  ts.setRotation(TFT_ROTATION); // タッチパネルの回転を設定(液晶画面と合わせる)

  // PWM初期設定
  pinMode(LED_BUILTIN, OUTPUT); // LED用端子を出力設定
  analogWriteFreq(1000);        // PWM周波数
  analogWriteRange(1024);       // 分解能(10bit)
}

// メイン -----------------------------------------
void loop() {
  canvas.fillScreen(ILI9341_BLACK); // 画面クリア
  //タッチパネル処理
  canvas.setTextColor(ILI9341_WHITE); // 文字色
  canvas.setCursor(0, 26);            // 座標設定
  canvas.setFont(&FreeSans18pt7b);    // フォント
  if (ts.touched() == true) {  // タッチされていれば
    TS_Point tPoint = ts.getPoint();  // タッチ座標を取得
    // タッチx座標をTFT画面の座標に換算
    int16_t x = (tPoint.x-400) * TFT_WIDTH / (4095-550);  // タッチx座標をTFT画面の座標に換算
    int16_t y = (tPoint.y-230) * TFT_HEIGHT / (4095-420); // タッチy座標をTFT画面の座標に換算

    // ボタンタッチエリア検出
    if (x >= 91 && x <= 159 && y >= 156 && y <= 224) lampSignal = true;   // 範囲内ならランプ状態を点灯へ
    if (x >= 176 && x <= 244 && y >= 156 && y <= 224) lampSignal = false; // 範囲内ならランプ状態を消灯へ

    // スライドボリューム 調整エリア検出
    if (x >= 270 && x <= 315 && y >= 62 && y <= 216) { // 指定座標範囲内なら
      slideYValue = y - 10;   // ボリュームつまみ表示位置Y軸座標補正
      volumeValue = 100.0 - ((float)slideYValue - 55.0) * 0.68; // ボリュームレベルの変化を0〜100の値に換算
      volumeValue = (volumeValue > 100) ? 100 : (volumeValue < 0) ? 0 : volumeValue; // 100以上は100に、0以下は0でクランプ
    }
    canvas.printf("x=%d  y=%d", x, y); // 座標表示
    // Serial.printf("x=%d, y=%d\n", x, y); // シリアル出力確認用
  } else {  //タッチされていなければ座標表示なし
    canvas.print("x= -      y= -"); // 文字表示
  }
  // スライドボリュームレベル換算値表示
  canvas.setCursor(262, 30);          // 座標設定
  canvas.printf("%.0f", volumeValue); // 値表示

  // 線描画
  canvas.drawFastHLine(0, 38, 260, ILI9341_WHITE);  // 線(指定座標から平行線)
  canvas.drawFastHLine(0, 39, 260, ILI9341_WHITE);  // 線(指定座標から平行線)
  canvas.drawFastVLine(120, 44, 43, ILI9341_PINK);  // 線(指定座標から垂線)
  canvas.drawFastVLine(121, 44, 43, ILI9341_PINK);  // 線(指定座標から垂線)
  canvas.drawFastHLine(0, 90, 260, ILI9341_WHITE);  // 線(指定座標から平行線)
  canvas.drawFastHLine(0, 91, 260, ILI9341_WHITE);  // 線(指定座標から平行線)
  canvas.drawFastHLine(0, 135, 260, ILI9341_WHITE); // 線(指定座標から平行線)
  canvas.drawFastHLine(0, 136, 260, ILI9341_WHITE); // 線(指定座標から平行線)

  // 文字描画(x, y, 内容, フォント, 文字色)
  drawText(8, 60, "LCD Display", &FreeSerifBold9pt7b, ILI9341_GREEN); // 文字表示関数呼び出し
  drawText(5, 80, "Touch Screen", &FreeSerifBold9pt7b, ILI9341_GREEN);
  drawText(135, 77, "ILI9341", &FreeSans18pt7b, ILI9341_CYAN);
  drawText(15, 125, "LogikaraBlog", &FreeSansBold18pt7b, ILI9341_ORANGE);

  // ランプ描画(x, y, 半径, 色)
  canvas.fillCircle(40, 190, 40, ILI9341_DARKGREY); // 外枠
  canvas.fillCircle(40, 190, 36, ILI9341_WHITE);    // 境界線
  updateLamp(); // ボタン点灯状態更新関数呼び出し

  // ボタン描画(x, y, ラベル, フォント, ボタン色, ラベル色)
  drawButton(85, 150, "ON", &FreeSansBold12pt7b, ILI9341_GREEN, ILI9341_WHITE); // ONボタン
  drawButton(170, 150, "OFF", &FreeSansBold12pt7b, ILI9341_RED, ILI9341_WHITE); // OFFボタン

  // スライドボリューム描画
  canvas.fillRect(265, 38, 55, 200, ILI9341_DARKGREY); // スライドエリア
  canvas.fillRect(287, 48, 11, 182, ILI9341_WHITE);    // 縦ライン枠
  canvas.fillRect(290, 52, 5, 175, ILI9341_BLACK);     // 縦ライン
  canvas.fillRect(270, slideYValue, 45, 20, 0x0904);   // ボリュームつまみ
  canvas.fillRect(278, slideYValue + 8, 29, 3, ILI9341_RED);  // ボリュームつまみライン

  // スプライト(メモリ内に描画した画面)をTFTに描画
  tft.drawRGBBitmap(0, 0, canvas.getBuffer(), TFT_WIDTH, TFT_HEIGHT);
}

8.まとめ

「Raspberry Pi Pico」で「ILI9341」を使用した「液晶表示」と「タッチパネル」の使い方を詳しく紹介しました。

開発環境に「ArduinoIDE」を使用し、液晶表示器やタッチパネル制御用の「ライブラリ」を使用することで、比較的簡単に画面表示やタッチパネルの動作確認を行うことができます。

今回は基本的な文字表示やフォント設定の確認から、画面表示のチラツキを防止する「スプライト」の方法を確認し、画面に線を描いて「お絵描き」しながら、タッチパネルの座標取得方法や、線の描画方法をサンプルプログラムで詳しく紹介しました。

最後に応用編として「タッチボタン」や「スライドボリューム」を画面上に配置してLEDを操作する「GOT(グラフィックオペレーションターミナル)」としての使い方まで紹介しました。

個人的には数千円で「GOT」としての機能が実現できるのがわかったことはかなりの収穫です^^
仕事で高価なGOTの導入に迷ったときはコレで十分ですね♪

タッチパネル液晶表示器の用途は幅広く、「PicoW」でネットワークと組み合わせればさらに色々な応用ができます。今年2025年は「生成AI」が猛威を振るう年になりそうなので、APIを使用して、画像から文字認識させて〜等、考え出すと面白くなりそうです。

いいものができたらまた紹介したいと思いますので、今年もどうぞよろしくお願いいたします。

ラズパイPicoタッチパネル液晶 ILI9341でSDカードの使い方
Raspberry Pi PicoでSDカードの読み書きや、JPEG画像読み込みを、タッチパネル液晶ILI9341に搭載のSDカードリーダーを使用して詳しく紹介
ラズパイPicoの機能をフル活用! シーケンサ基板の使い方
Raspberry Pi Picoの機能を最大限に活かし、シーケンサ(PLC)として簡単にラダープログラムをPythonで作成、実行できる基板について詳しく紹介します。

コメント

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