ESP32: タッチパネル付き液晶ディスプレイLCD(ILI9341),SDカードを同時に使う(HSPI, VSPI)

公開講座

はじめに

2021年度公開講座では,タッチパネル付きの液晶ディスプレイとSDカードを使用します。

液晶ディスプレイ(タッチパネル付き),SDカード

・液晶ディスプレイ2.4インチ:ILI9341

・タッチパネルコントローラ:XPT2048(液晶ディスプレイに付属)

・SDカード:Micro SDカード / SDカードスロット

配線

ESP32と液晶ディスプレイ・タッチパネル,さらに,マイクロSDカードスロットを配線しました。ESP32には,SPIとしてVSPIとHSPIがあり,この2つのSPIを使いました。

次の図のように,液晶ディスプレイとタッチパネルにはVSPIを使用し,CS信号ででいずれかを切り替えます。マイクロSDはHSPIとしました。

ESP32との接続

・液晶ディスプレイ:VSPI(SCK,MISO,MOSI,CS,DC) = (18,29,23,5,32)

・タッチパネル:VSPI (SCK,MISO,MOSI,CS) = (18,19,23,4)

(SCK,MISO,MOSIは,液晶ディスプレイと共通,チップセレクトCS(4,5)で切り替え)

・マイクロSDカードスロット:HSPI(SCK,MISO,MOSI,SS) = (14,33,13,15)

(HSPIのデフォルト12ピンは,MISOとして使用不可,SDカードモジュールにてプルアップされているため。)

※ SPIのバスクロック(SCK)は27Mhzと速いため「ブレッドボード」での検証では上記の配線でうまく動作しませんでした。ジャンパ線を工夫して最短で接続すると動作するかもしれませんが未検証です。

プログラム(液晶ディスプレイとタッチパネル)

TFT_eSPIライブラリを使うと,簡単に液晶ディスプレイとタッチパネルの動作確認ができます。

ライブラリ:TFT_eSPI

スケッチ例→TFT_eSPI -> 320 x 240 -> Keypad 240×320

1. ユーザ設定ファイルの変更(重要)

使用する液晶ディスプレイに合わせてユーザ設定ファイルを変更します。

・ドキュメント/Arduino/libraries/TFT_eSPI/UserSetup.h

ESP32の液晶ディスプレイ,タッチパネルの配線に合わせて,次の箇所を変更します。

#define ILI9341_DRIVER

#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS    5 // Chip select control pin
#define TFT_DC    32  // Data Command control pin
#define TFT_RST  -1  // Set TFT_RST to -1 if display RESET is connected to ESP32 board RST

#define TOUCH_CS 4     // Chip select pin (T_CS) of touch screen

2. Arduino:ファイル→スイッチ例→TFT_eSPI→320×240→Keypad_240x320

サンプルのプログラムを実行すると,

・タッチパネルの「キャリブレーション」が実行されます。

液晶ディスプレイのコーナー(4カ所)をタッチペンで押していきます。

・電卓のプログラムが始まります。

// Keypad_240x320.ino
#include "FS.h"

#include <SPI.h>
#include <TFT_eSPI.h>      // Hardware-specific library

TFT_eSPI tft = TFT_eSPI(); // Invoke custom library

// This is the file name used to store the calibration data
// You can change this to create new calibration files.
// The SPIFFS file name must start with "/".
#define CALIBRATION_FILE "/TouchCalData1"

// Set REPEAT_CAL to true instead of false to run calibration
// again, otherwise it will only be done once.
// Repeat calibration if you change the screen rotation.
#define REPEAT_CAL false

// Keypad start position, key sizes and spacing
#define KEY_X 40 // Centre of key
#define KEY_Y 96
#define KEY_W 62 // Width and height
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         #define KEY_H 30
#define KEY_SPACING_X 18 // X and Y gap
#define KEY_SPACING_Y 20
#define KEY_TEXTSIZE 1   // Font size multiplier

// Using two fonts since numbers are nice when bold
#define LABEL1_FONT &FreeSansOblique12pt7b // Key label font 1
#define LABEL2_FONT &FreeSansBold12pt7b    // Key label font 2

// Numeric display box size and location
#define DISP_X 1
#define DISP_Y 10
#define DISP_W 238
#define DISP_H 50
#define DISP_TSIZE 3
#define DISP_TCOLOR TFT_CYAN

// Number length, buffer for storing it and character index
#define NUM_LEN 12
char numberBuffer[NUM_LEN + 1] = "";
uint8_t numberIndex = 0;

// We have a status line for messages
#define STATUS_X 120 // Centred on this
#define STATUS_Y 65

// Create 15 keys for the keypad
char keyLabel[15][5] = {"New", "Del", "Send", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "0", "#" };
uint16_t keyColor[15] = {TFT_RED, TFT_DARKGREY, TFT_DARKGREEN,
                         TFT_BLUE, TFT_BLUE, TFT_BLUE,
                         TFT_BLUE, TFT_BLUE, TFT_BLUE,
                         TFT_BLUE, TFT_BLUE, TFT_BLUE,
                         TFT_BLUE, TFT_BLUE, TFT_BLUE
                        };

// Invoke the TFT_eSPI button class and create all the button objects
TFT_eSPI_Button key[15];

//------------------------------------------------------------------------------------------

void setup() {
  // Use serial port
  Serial.begin(9600);

  // Initialise the TFT screen
  tft.init();

  // Set the rotation before we calibrate
  tft.setRotation(0);

  // Calibrate the touch screen and retrieve the scaling factors
  touch_calibrate();

  // Clear the screen
  tft.fillScreen(TFT_BLACK);

  // Draw keypad background
  tft.fillRect(0, 0, 240, 320, TFT_DARKGREY);

  // Draw number display area and frame
  tft.fillRect(DISP_X, DISP_Y, DISP_W, DISP_H, TFT_BLACK);
  tft.drawRect(DISP_X, DISP_Y, DISP_W, DISP_H, TFT_WHITE);

  // Draw keypad
  drawKeypad();
}

//------------------------------------------------------------------------------------------

void loop(void) {
  uint16_t t_x = 0, t_y = 0; // To store the touch coordinates

  // Pressed will be set true is there is a valid touch on the screen
  boolean pressed = tft.getTouch(&t_x, &t_y);

  // / Check if any key coordinate boxes contain the touch coordinates
  for (uint8_t b = 0; b < 15; b++) {
    if (pressed && key[b].contains(t_x, t_y)) {
      key[b].press(true);  // tell the button it is pressed
    } else {
      key[b].press(false);  // tell the button it is NOT pressed
    }
  }

  // Check if any key has changed state
  for (uint8_t b = 0; b < 15; b++) {

    if (b < 3) tft.setFreeFont(LABEL1_FONT);
    else tft.setFreeFont(LABEL2_FONT);

    if (key[b].justReleased()) key[b].drawButton();     // draw normal

    if (key[b].justPressed()) {
      key[b].drawButton(true);  // draw invert

      // if a numberpad button, append the relevant # to the numberBuffer
      if (b >= 3) {
        if (numberIndex < NUM_LEN) {
          numberBuffer[numberIndex] = keyLabel[b][0];
          numberIndex++;
          numberBuffer[numberIndex] = 0; // zero terminate
        }
        status(""); // Clear the old status
      }

      // Del button, so delete last char
      if (b == 1) {
        numberBuffer[numberIndex] = 0;
        if (numberIndex > 0) {
          numberIndex--;
          numberBuffer[numberIndex] = 0;//' ';
        }
        status(""); // Clear the old status
      }

      if (b == 2) {
        status("Sent value to serial port");
        Serial.println(numberBuffer);
      }
      // we dont really check that the text field makes sense
      // just try to call
      if (b == 0) {
        status("Value cleared");
        numberIndex = 0; // Reset index to 0
        numberBuffer[numberIndex] = 0; // Place null in buffer
      }

      // Update the number display field
      tft.setTextDatum(TL_DATUM);        // Use top left corner as text coord datum
      tft.setFreeFont(&FreeSans18pt7b);  // Choose a nicefont that fits box
      tft.setTextColor(DISP_TCOLOR);     // Set the font colour

      // Draw the string, the value returned is the width in pixels
      int xwidth = tft.drawString(numberBuffer, DISP_X + 4, DISP_Y + 12);

      // Now cover up the rest of the line up by drawing a black rectangle.  No flicker this way
      // but it will not work with italic or oblique fonts due to character overlap.
      tft.fillRect(DISP_X + 4 + xwidth, DISP_Y + 1, DISP_W - xwidth - 5, DISP_H - 2, TFT_BLACK);

      delay(10); // UI debouncing
    }
  }
}

//------------------------------------------------------------------------------------------

void drawKeypad()
{
  // Draw the keys
  for (uint8_t row = 0; row < 5; row++) {
    for (uint8_t col = 0; col < 3; col++) {
      uint8_t b = col + row * 3;

      if (b < 3) tft.setFreeFont(LABEL1_FONT);
      else tft.setFreeFont(LABEL2_FONT);

      key[b].initButton(&tft, KEY_X + col * (KEY_W + KEY_SPACING_X),
                        KEY_Y + row * (KEY_H + KEY_SPACING_Y), // x, y, w, h, outline, fill, text
                        KEY_W, KEY_H, TFT_WHITE, keyColor[b], TFT_WHITE,
                        keyLabel[b], KEY_TEXTSIZE);
      key[b].drawButton();
    }
  }
}

//------------------------------------------------------------------------------------------

void touch_calibrate()
{
  uint16_t calData[5];
  uint8_t calDataOK = 0;

  // check file system exists
  if (!SPIFFS.begin()) {
    Serial.println("Formating file system");
    SPIFFS.format();
    SPIFFS.begin();
  }

  // check if calibration file exists and size is correct
  if (SPIFFS.exists(CALIBRATION_FILE)) {
    if (REPEAT_CAL)
    {
      // Delete if we want to re-calibrate
      SPIFFS.remove(CALIBRATION_FILE);
    }
    else
    {
      File f = SPIFFS.open(CALIBRATION_FILE, "r");
      if (f) {
        if (f.readBytes((char *)calData, 14) == 14)
          calDataOK = 1;
        f.close();
      }
    }
  }

  if (calDataOK && !REPEAT_CAL) {
    // calibration data valid
    tft.setTouch(calData);
  } else {
    // data not valid so recalibrate
    tft.fillScreen(TFT_BLACK);
    tft.setCursor(20, 0);
    tft.setTextFont(2);
    tft.setTextSize(1);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);

    tft.println("Touch corners as indicated");

    tft.setTextFont(1);
    tft.println();

    if (REPEAT_CAL) {
      tft.setTextColor(TFT_RED, TFT_BLACK);
      tft.println("Set REPEAT_CAL to false to stop this running again!");
    }

    tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);

    tft.setTextColor(TFT_GREEN, TFT_BLACK);
    tft.println("Calibration complete!");

    // store data
    File f = SPIFFS.open(CALIBRATION_FILE, "w");
    if (f) {
      f.write((const unsigned char *)calData, 14);
      f.close();
    }
  }
}

//------------------------------------------------------------------------------------------

// Print something in the mini status bar
void status(const char *msg) {
  tft.setTextPadding(240);
  //tft.setCursor(STATUS_X, STATUS_Y);
  tft.setTextColor(TFT_WHITE, TFT_DARKGREY);
  tft.setTextFont(0);
  tft.setTextDatum(TC_DATUM);
  tft.setTextSize(1);
  tft.drawString(msg, STATUS_X, STATUS_Y);
}

//------------------------------------------------------------------------------------------

 

プログラム(SDカード)

SDカードの接続には,HSPIを使用しています。SDライブラリは,VSPIの使用を想定しているため,少しコードの修正が必要です。次のコードの太文字部分を書き換えると,SDカードが使用できます。これで,液晶ディスプレイ・タッチパネルとSDカードを同時に使うことができます。

#include <SD.h>

SPIClass spiSD(HSPI);
#define SD_SCK  14
#define SD_MISO 33
#define SD_MOSI 13
#define SD_SS   15

char logFile[32] = "/20210315_1418.txt";
File dataFile;

void setup() {
  Serial.begin(115200);
  Serial.print("Initializing SD card...");
  spiSD.begin(SD_SCK, SD_MISO, SD_MOSI, SD_SS); //SCK,MISO,MOSI,SS //HSPI1

  if (!SD.begin(SD_SS, spiSD)) {
    Serial.println("Card Mount Failed");
    return;
  }

  dataFile = SD.open(logFile, FILE_WRITE);
  dataFile.println("date,elapse,pitch,xAccel,yAccel,zAccel,xGyro,yGyro,zGyro");
  dataFile.close();
  Serial.println("done");
}

void loop() {
}

 

まとめ

ESP32で,液晶ディスプレイ・タッチパネルとSDカードを使用する方法を示しました。講座では,液晶ディスプレイをセンサで測定した情報の表示,グラフの描画,画像の表示,タッチパネルの入力などに使います。

 

コメント

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