ESP32タッチディスプレイデバイス 使用マニュアル ESP32-2432S028

技術関連

ESP32 2.8インチ タッチスクリーン開発ボード 完全仕様書(ESP32-2432S028)v2.3

  1. 概要
  2. ⚠️ 使用上の重要な注意事項
    1. 必ずお読みください
    2. コネクタ配置
  3. 💻 開発環境のセットアップ
  4. ハードウェア構成
    1. マイコン
    2. ディスプレイ
    3. タッチスクリーン
  5. 完全ピン配置マップ
    1. ディスプレイ (ILI9341) – VSPI
    2. タッチスクリーン (XPT2046) – HSPI
    3. SDカード (TFカードスロット) – VSPI共用
    4. RGB LED – アクティブLOW制御
    5. オーディオ・バックライト
    6. 光センサー (GT36516)
    7. センサー・入力ピン
    8. 外部コネクタ
      1. 拡張IO1 (4ピン 1.25mm JST)
      2. 拡張IO2 (4ピン 1.25mm JST)
      3. シリアルポート (4ピン 1.25mm JST)
      4. スピーカー (2ピン 1.25mm JST)
  6. Arduino IDE設定
    1. ボード設定
    2. TFT_eSPI設定 (User_Setup.h)
    3. 【最重要】色反転の対処
    4. 必須ライブラリ
  7. 完全な初期化テンプレート
    1. 基本バージョン(シンプル)
    2. プロフェッショナルバージョン(部分更新推奨)
  8. 初期化順序の重要性
    1. 【重要】正しい初期化順序
    2. なぜこの順序が重要なのか
    3. ❌ 避けるべき初期化パターン
  9. GPIO使用状況一覧表
    1. 未使用GPIO (利用可能)
  10. 制約事項と注意点
    1. 1. GPIO4共用の制約(重要!)
    2. 2. GPIO4使用時の推奨パターン
    3. 3. その他の共用制約
    4. 4. 入力専用GPIO
    5. 5. 起動時の制約
    6. 6. SPI共用時のコード例
  11. WiFi設定テンプレート
  12. スプライト使用時の注意
    1. メモリ制限
    2. 推奨される描画方法
      1. 方法1: 部分更新(最も推奨)
      2. 方法2: 小さいスプライト
    3. ちらつき防止のベストプラクティス
  13. トラブルシューティング
    1. ディスプレイが表示されない
    2. 色がすべておかしい(最重要)
    3. 画面がちらつく
    4. スプライト作成に失敗する
    5. タッチが反応しない
    6. RGB LEDが点灯しない・おかしい
    7. SDカードが認識されない
    8. 光センサーが読み取れない(常に0)
    9. 光センサーの値が逆に見える
  14. よくあるコーディングパターン
    1. パターン1: 部分更新で時計表示(推奨)
    2. パターン2: タッチで色変更(部分更新版)
    3. パターン3: WiFi接続状態をLEDで表示
    4. パターン4: タッチで音を鳴らす
    5. パターン5: メロディー再生
    6. パターン6: 光センサー読み取り
  15. 電源仕様
  16. 未確定事項 (実機確認推奨)
    1. 優先度: 高
    2. 優先度: 中
    3. 優先度: 低(確認済み)
  17. コード生成時のチェックリスト
  18. バージョン情報
  19. サポートライブラリバージョン
    1. 関連記事:

概要

ESP32-WROOM-32を搭載した、2.8インチTFTディスプレイとタッチスクリーン付き開発ボードです。WiFi/Bluetooth通信、オーディオ出力、SDカード、RGB LED、拡張ポートを備えた多機能な統合開発環境を提供します。


⚠️ 使用上の重要な注意事項

必ずお読みください

  1. ディスプレイの色反転問題
    • このデバイスのディスプレイは色が反転しています
      反転していないデバイスもあります。
    • tft.invertDisplay(true); を必ず呼び出してください
    • これを忘れると、すべての色が意図しない色で表示されます
  2. GPIO4 (赤LED) の制限
    • ディスプレイリセット信号と共用しています
    • 赤LEDは短時間(100ms以下)の点灯のみ使用してください
    • 長時間点灯するとディスプレイが不安定になります
  3. GPIO1/3 (シリアルポート) について
    • USB通信で常に使用されています
    • 外部コネクタに出ていますが、基本的に外部デバイス接続は避けてください
    • 接続するとプログラム書き込みやデバッグが不安定になります
  4. 電源の制約
    • 拡張IO2の3.3Vピンから取り出せる電流は100mA以下を推奨
    • 大電流を取り出すと電圧降下や動作不良の原因になります
  5. GPIO21 (バックライト) とI2C
    • バックライトとI2C SDAで共用しています
    • バックライト使用中はI2C通信ができません

Main features:

The module is the development board of ESP-WROOM-32, and the parameter characteristics of ESP-WROOM-32 are also the parameter characteristics of the module.

802.11b/g/WiFi+BT module;

Low power dual-core 32-bit CPU, can be used as an application processor;

Main frequency up to 240MHz, computing power up to 600DMIPS;

Built-in 520KBSRAM;

Support UART/SPI/I2C/PWM/DAC/ADC and other interfaces;

Support openOCD debugging interface;

Support a variety of sleep modes, deep sleep, current as low as 6.5uA;

Embedded Lwip and FreeRTOS;

Support STA/AP/STA+AP mode;

Support SmartConfig/AirKiss one-click network distribution;

General AT instruction, easy to get started;

Supports local serial upgrade and remote upgrade (FOTA).

コネクタ配置

None
None

💻 開発環境のセットアップ


ハードウェア構成

マイコン

型番: ESP32-WROOM-32
CPU: Dual Core Xtensa LX6 @ 240MHz
RAM: 520KB SRAM
Flash: 4MB
WiFi: 2.4GHz (802.11 b/g/n)
Bluetooth: BLE 4.2
OS: FreeRTOS

ディスプレイ

サイズ: 2.8インチ TFT LCD
解像度: 320x240 ピクセル
ドライバIC: ILI9341
色深度: 16bit (65536色)
インターフェース: SPI (VSPI)
ライブラリ: TFT_eSPI

タッチスクリーン

コントローラ: XPT2046
方式: 抵抗膜式
解像度: 4096x4096
インターフェース: SPI (HSPI)
ライブラリ: XPT2046_Touchscreen

完全ピン配置マップ

ディスプレイ (ILI9341) – VSPI

#define TFT_CS    15  // チップセレクト
#define TFT_DC    2   // データ/コマンド
#define TFT_RST   4   // リセット (※RGB LED Redと共用)
#define TFT_MOSI  23  // データ出力 (SDカードと共用)
#define TFT_SCK   18  // クロック (SDカードと共用)
#define TFT_MISO  19  // データ入力 (SDカードと共用)

タッチスクリーン (XPT2046) – HSPI

#define TOUCH_CS   33  // チップセレクト
#define TOUCH_CLK  25  // クロック
#define TOUCH_DIN  32  // データ入力
#define TOUCH_DO   39  // データ出力 (input only)
#define TOUCH_IRQ  36  // 割り込み (input only)

// キャリブレーション値
#define TS_MIN_X  300
#define TS_MAX_X  3900
#define TS_MIN_Y  350
#define TS_MAX_Y  3700

SDカード (TFカードスロット) – VSPI共用

#define SD_CS    5   // チップセレクト
#define SD_MOSI  23  // ディスプレイと共用
#define SD_SCK   18  // ディスプレイと共用
#define SD_MISO  19  // ディスプレイと共用

重要: ディスプレイとSDカードは同じSPIバス。CS信号で切り替え。

RGB LED – アクティブLOW制御

#define LED_RED    4   // 赤 (※TFT_RSTと共用)
#define LED_GREEN  16  // 緑
#define LED_BLUE   17  // 青

// 駆動回路: NPNトランジスタ (S8050) × 3
// ベース抵抗: 10kΩ
// 制御方法: アクティブLOW
digitalWrite(LED_RED, LOW);   // 点灯
digitalWrite(LED_RED, HIGH);  // 消灯

重要: 共通アノード方式。LOW=点灯、HIGH=消灯。
駆動: GPIO → 10kΩ抵抗 → NPNトランジスタ(S8050) → LED → VCC

オーディオ・バックライト

#define AUDIO_OUT     26  // PWMオーディオ出力
#define BACKLIGHT_LED 21  // バックライト制御 (※I2C SDAと共用)

光センサー (GT36516)

センサーIC: GT36516 (フォトトランジスタ)
接続ピン: GPIO 34 (ADC1_CH6)
インターフェース: アナログ入力
読み取り範囲: 0-4095 (12bit ADC)

回路構成:

3.3V ─┬─ R15 (1MΩ) ─┬─ IO34 ─┬─ R21 (1MΩ) ─┬─ GND
      │              │        │              │
      └─ R19 (1MΩ) ─┴─ GT36516 ─────────────┘

特性:

  • 明るい環境 → 低い値 (0に近づく)
  • 暗い環境 → 高い値 (4095に近づく)
  • 手で覆うと値が増加する

コード例:

#include <driver/adc.h>

#define LIGHT_SENSOR_PIN 34  // ADC1_CH6

void setupLightSensor() {
  adc1_config_width(ADC_WIDTH_BIT_12);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
}

int readLightSensor() {
  // 複数回読み取って平均化(ノイズ対策)
  int sum = 0;
  for (int i = 0; i < 10; i++) {
    sum += adc1_get_raw(ADC1_CHANNEL_6);
    delayMicroseconds(100);
  }
  return sum / 10;
}

重要:

  • analogRead(34) ではなく adc1_get_raw(ADC1_CHANNEL_6) を使用
  • WiFiライブラリ使用時もADC1は正常に動作する(ADC2は競合する)
  • 必ず adc1_config_channel_atten() で初期化すること

センサー・入力ピン

#define LIGHT_SENSOR_PIN  34  // GT36516 光センサー (ADC1_CH6)
#define BOOT_BTN          0   // BOOTボタン (プルアップ済み)

外部コネクタ

拡張IO1 (4ピン 1.25mm JST)

// ピン配置: GND, IO35, IO22, IO21
#define EXP1_INPUT  35  // 入力専用 (ADC1_CH7)
#define EXP1_SCL    22  // I2C SCL
#define EXP1_SDA    21  // I2C SDA (※バックライトと共用)
// GND

拡張IO2 (4ピン 1.25mm JST)

// ピン配置: GND, IO22, IO27, 3.3V
#define EXP2_SCL    22  // I2C SCL (拡張IO1と共用)
#define EXP2_TOUCH  27  // タッチ対応 (ADC2_CH7)
// GND, 3.3V

シリアルポート (4ピン 1.25mm JST)

// ピン配置: VIN, TX, RX, GND
// USB-UART変換IC: CH340C
#define SERIAL_TX  1   // GPIO1 (TX0) - UART0送信
#define SERIAL_RX  3   // GPIO3 (RX0) - UART0受信
// VIN: 5V電源入力
// GND: グランド

// 注意: GPIO1/3はUSB-シリアル変換と共有
// プログラム書き込み時に使用されるため、
// 通常はこのピンを別の用途に使わない

スピーカー (2ピン 1.25mm JST)

仕様: 1.5W / 4Ω
制御: GPIO26 (PWM)

Arduino IDE設定

ボード設定

ボード: ESP32 Dev Module
Upload Speed: 921600
Flash Frequency: 80MHz
Flash Mode: QIO
Flash Size: 4MB
Partition Scheme: Default 4MB with spiffs

TFT_eSPI設定 (User_Setup.h)

// --- 必須ドライバ ---
#define ILI9341_DRIVER

// --- ディスプレイサイズ ---
#define TFT_WIDTH  240
#define TFT_HEIGHT 320

// --- VSPIピン構成 ---
#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS   15
#define TFT_DC   2
#define TFT_RST  4

// --- フォント設定(推奨) ---
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF
#define SMOOTH_FONT

// --- SPIバスのトランザクションサポート(重要) ---
#define SUPPORT_TRANSACTIONS

// --- SPI速度 ---
#define SPI_FREQUENCY       40000000
#define SPI_READ_FREQUENCY  20000000

【最重要】色反転の対処

// このデバイスのディスプレイパネルは色が反転している
// tft.init()の直後に必ず以下を呼び出すこと
void setup() {
  tft.init();
  tft.setRotation(1);
  tft.invertDisplay(true);  // ← これを忘れると全ての色が反転する
}

必須ライブラリ

#include <SPI.h>                   // ESP32標準
#include <TFT_eSPI.h>              // v2.5.0+
#include <XPT2046_Touchscreen.h>   // v1.4.0+
#include <WiFi.h>                  // ESP32標準
#include <SD.h>                    // ESP32標準 (オプション)
#include <driver/adc.h>            // 光センサー読み取り用

完全な初期化テンプレート

基本バージョン(シンプル)

#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>

// ===== ピン定義 =====
#define TOUCH_CS   33
#define TOUCH_IRQ  36
#define TOUCH_CLK  25
#define TOUCH_DO   39
#define TOUCH_DIN  32
#define LED_RED    4
#define LED_GREEN  16
#define LED_BLUE   17
#define BACKLIGHT  21
#define SD_CS      5

// ===== オブジェクト生成 =====
TFT_eSPI tft = TFT_eSPI();
SPIClass hspi(HSPI);  // タッチスクリーン用HSPIバス
XPT2046_Touchscreen ts(TOUCH_CS, TOUCH_IRQ);

// ===== セットアップ =====
void setup() {
  Serial.begin(115200);

  // ----- ディスプレイ初期化(最初に行う) -----
  tft.init();
  tft.setRotation(1);  // 横向き (320x240)
  tft.invertDisplay(true);  // 【重要】色反転を補正
  tft.fillScreen(TFT_BLACK);

  // バックライトON
  pinMode(BACKLIGHT, OUTPUT);
  digitalWrite(BACKLIGHT, HIGH);

  // ----- 【重要】ディスプレイ初期化後、2秒待機 -----
  delay(2000);

  // ----- タッチスクリーン初期化(ディスプレイの後に行う) -----
  hspi.begin(TOUCH_CLK, TOUCH_DO, TOUCH_DIN, TOUCH_CS);  // CLK, MISO, MOSI, CS
  ts.begin(hspi);
  ts.setRotation(1);

  // ----- RGB LED初期化 -----
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);
  digitalWrite(LED_RED, HIGH);
  digitalWrite(LED_GREEN, HIGH);
  digitalWrite(LED_BLUE, HIGH);

  Serial.println("初期化完了");
}

void loop() {
  if (ts.touched()) {
    TS_Point p = ts.getPoint();
    int x = map(p.x, 300, 3900, 0, 320);
    int y = map(p.y, 350, 3700, 0, 240);

    tft.fillCircle(x, y, 5, TFT_WHITE);
  }
}

プロフェッショナルバージョン(部分更新推奨)

#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>

// ===== ピン定義 =====
#define TOUCH_CS   33
#define TOUCH_IRQ  36
#define TOUCH_CLK  25
#define TOUCH_DO   39
#define TOUCH_DIN  32
#define LED_RED    4
#define LED_GREEN  16
#define LED_BLUE   17
#define BACKLIGHT  21

// ===== オブジェクト生成 =====
TFT_eSPI tft = TFT_eSPI();
SPIClass hspi(HSPI);  // タッチスクリーン用HSPIバス
XPT2046_Touchscreen ts(TOUCH_CS, TOUCH_IRQ);

// 前回の描画位置を記録(部分更新用)
int prevX = -1, prevY = -1;

// ===== セットアップ =====
void setup() {
  Serial.begin(115200);

  // ----- ディスプレイ初期化(最初に行う) -----
  tft.init();
  tft.setRotation(1);
  tft.invertDisplay(true);  // 【重要】色反転を補正
  tft.fillScreen(TFT_BLACK);  // 初回のみ全画面クリア

  // バックライトON
  pinMode(BACKLIGHT, OUTPUT);
  digitalWrite(BACKLIGHT, HIGH);

  // ----- 【重要】ディスプレイ初期化後、2秒待機 -----
  delay(2000);

  // ----- タッチスクリーン初期化(ディスプレイの後に行う) -----
  hspi.begin(TOUCH_CLK, TOUCH_DO, TOUCH_DIN, TOUCH_CS);  // CLK, MISO, MOSI, CS
  ts.begin(hspi);
  ts.setRotation(1);

  // ----- RGB LED初期化 -----
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);
  digitalWrite(LED_RED, HIGH);
  digitalWrite(LED_GREEN, HIGH);
  digitalWrite(LED_BLUE, HIGH);

  // 静的な要素を描画(一度だけ)
  tft.setTextColor(TFT_WHITE);
  tft.drawString("Touch Test", 10, 10, 2);

  Serial.println("初期化完了");
}

void loop() {
  if (ts.touched()) {
    TS_Point p = ts.getPoint();
    int x = map(p.x, 300, 3900, 0, 320);
    int y = map(p.y, 350, 3700, 0, 240);

    // 前回の円を消去(部分更新)
    if (prevX >= 0 && prevY >= 0) {
      tft.fillCircle(prevX, prevY, 12, TFT_BLACK);
    }

    // 新しい円を描画
    tft.fillCircle(x, y, 10, TFT_RED);

    // 位置を記録
    prevX = x;
    prevY = y;
  }

  delay(50);
}

初期化順序の重要性

【重要】正しい初期化順序

void setup() {
  // ===== ステップ1: ディスプレイ初期化(最初に行う) =====
  tft.init();
  tft.setRotation(1);
  tft.invertDisplay(true);
  tft.fillScreen(TFT_BLACK);

  pinMode(BACKLIGHT, OUTPUT);
  digitalWrite(BACKLIGHT, HIGH);

  // ===== ステップ2: 2秒待機(必須) =====
  // ディスプレイとタッチスクリーンは別のSPIバスを使用するが、
  // ディスプレイの初期化が完全に完了してからタッチを初期化する必要がある
  delay(2000);

  // ===== ステップ3: タッチスクリーン初期化(ディスプレイの後に行う) =====
  hspi.begin(TOUCH_CLK, TOUCH_DO, TOUCH_DIN, TOUCH_CS);
  ts.begin(hspi);
  ts.setRotation(1);

  // ===== ステップ4: その他の初期化 =====
  // RGB LED、WiFi、SDカードなど
}

なぜこの順序が重要なのか

  1. ディスプレイ (VSPI) とタッチ (HSPI) は別バスだが、初期化タイミングが重要
  2. ディスプレイ初期化直後にタッチを初期化すると、タッチが正常に動作しないことがある
  3. 2秒の待機時間により、ディスプレイの初期化が完全に安定してからタッチを初期化できる

❌ 避けるべき初期化パターン

void setup() {
  // 悪い例1: タッチを先に初期化
  ts.begin();  // ❌ ディスプレイより先
  tft.init();

  // 悪い例2: 待機時間なし
  tft.init();
  ts.begin();  // ❌ 待機なしで即座に初期化

  // 悪い例3: HSPIバスを明示的に初期化しない
  ts.begin();  // ❌ hspi.begin()を呼ばずに初期化
}

GPIO使用状況一覧表

GPIO機能方向備考
0BOOT ButtonInput起動モード選択
1TX0 (Serial)OutputUSB-UART (CH340C)
2TFT_DCOutputデータ/コマンド
3RX0 (Serial)InputUSB-UART (CH340C)
4TFT_RST / LED_REDOutput共用ピン
5SD_CSOutputSDカードCS
15TFT_CSOutputディスプレイCS
16LED_GREENOutput緑LED (アクティブLOW)
17LED_BLUEOutput青LED (アクティブLOW)
18TFT_SCK / SD_SCKOutputSPI Clock (共用)
19TFT_MISO / SD_MISOInputSPI MISO (共用)
21BACKLIGHT / I2C_SDAOutput共用ピン
22I2C_SCLOutput拡張IO1/IO2共通
23TFT_MOSI / SD_MOSIOutputSPI MOSI (共用)
25TOUCH_CLKOutputタッチSPI Clock
26AUDIOOutputPWMオーディオ
27EXP2 (拡張)Input/OutputADC2_CH7 / TOUCH7
32TOUCH_DINOutputタッチSPI MOSI
33TOUCH_CSOutputタッチCS
34Light SensorInputGT36516 / ADC1_CH6 / 入力専用
35EXP1 (拡張)Input入力専用 / ADC1_CH7
36TOUCH_IRQInput入力専用 / 割り込み
39TOUCH_DOInput入力専用 / SPI MISO

未使用GPIO (利用可能)

GPIO: 12, 13, 14

注意:

  • GPIO34, 35, 36, 39は入力専用(プルアップ/ダウン不可)
  • GPIO0, 2, 5, 12, 15は起動時の状態が重要
  • GPIO1, 3はUART0で使用中(USB-シリアル変換と共有)
  • GPIO34は光センサー (GT36516) で使用中

制約事項と注意点

1. GPIO4共用の制約(重要!)

// GPIO4: TFT_RSTとLED_REDで共用
// TFT初期化シーケンス:
// 1. GPIO4 = HIGH (リセット解除)
// 2. delay(数ms)
// 3. GPIO4 = LOW (リセット実行) → 赤LEDが点灯
// 4. delay(数ms)
// 5. GPIO4 = HIGH (リセット解除) → 赤LEDが消灯
// 6. 以降、GPIO4はRGB LED制御に使用可能

// ⚠️ 注意点:
// - TFT初期化中は赤LEDが一瞬点灯する(正常動作)
// - 初期化後にGPIO4をLOWにすると、ディスプレイがリセットされる可能性
// - ディスプレイ初期化完了後、GPIO4は常にHIGH状態を推奨

2. GPIO4使用時の推奨パターン

void setup() {
  // ディスプレイ初期化
  tft.init();  // この時GPIO4がリセットシーケンスで動作
  delay(100);  // 初期化完了を待つ

  // この後、GPIO4を赤LED制御に使用可能
  // ただし、LOWにするとディスプレイがリセットされる可能性あり
  pinMode(LED_RED, OUTPUT);
  digitalWrite(LED_RED, HIGH);  // 消灯状態で開始(安全)
}

// 赤LEDを点灯させる場合
void flashRedLED() {
  digitalWrite(LED_RED, LOW);   // 点灯
  delay(100);                   // 短時間のみ
  digitalWrite(LED_RED, HIGH);  // すぐ消灯(ディスプレイ保護)
}

// ⚠️ 避けるべきパターン
void badPattern() {
  digitalWrite(LED_RED, LOW);   // 点灯
  delay(5000);                  // 長時間LOW → ディスプレイリセットの危険
}

3. その他の共用制約

// GPIO21: BACKLIGHTとI2C_SDAで共用
// → バックライト常時ONの場合、I2C使用不可

// GPIO22: 拡張IO1とIO2で共用
// → I2Cバスの複数ポート展開(正常)

// VSPI: ディスプレイとSDカードで共用
// → CS信号で切り替えて使用

4. 入力専用GPIO

// プルアップ/プルダウン抵抗が使えない
GPIO: 34, 35, 36, 39

5. 起動時の制約

// 以下のGPIOは起動時の状態に注意
GPIO: 0, 2, 5, 12, 15
// 起動時にHIGH/LOWが重要(ブートモード決定)

6. SPI共用時のコード例

// このデバイスではTFT(VSPI)とタッチ(HSPI)が別バス
// 基本的にSPI競合は発生しない

// ただし、SDカードなど3つ目のSPIデバイス追加時は注意
// SDカードはVSPI(ディスプレイと共用)を使用

// ディスプレイ使用
digitalWrite(TFT_CS, LOW);
// SPI.beginTransaction(SPISettings(...)); // 必要に応じて
spi.transfer(data);
// SPI.endTransaction();
digitalWrite(TFT_CS, HIGH);

// SDカード使用
digitalWrite(SD_CS, LOW);
// SPI.beginTransaction(SPISettings(...)); // 必要に応じて
spi.transfer(data);
// SPI.endTransaction();
digitalWrite(SD_CS, HIGH);

WiFi設定テンプレート

#include <WiFi.h>
#include <time.h>

const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

void setupWiFi() {
  WiFi.begin(ssid, password);

  tft.print("WiFi接続中");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    tft.print(".");
  }

  tft.println("\nWiFi接続完了");
  tft.print("IP: ");
  tft.println(WiFi.localIP());

  // NTP時刻同期 (JST)
  configTime(9 * 3600, 0, "ntp.nict.jp");
}

スプライト使用時の注意

メモリ制限

フルスクリーンスプライト(320×240×16bit = 約150KB)はESP32のRAM(520KB)では他の処理との兼ね合いでメモリ不足になり、確保に失敗することがある。

// スプライト作成時は必ず戻り値を確認
void* ptr = spr.createSprite(320, 240);
if (ptr == nullptr) {
  Serial.println("スプライト作成失敗 - メモリ不足");
  // フォールバック処理へ
}

推奨される描画方法

方法1: 部分更新(最も推奨)

// 画面全体のクリアを避け、変更部分のみ更新
int prevValue = -1;

void updateDisplay(int newValue) {
  // 値が変わった時だけ更新
  if (newValue == prevValue) return;

  // 悪い例: 全画面クリア(ちらつく)
  // tft.fillScreen(TFT_BLACK);

  // 良い例: 変更部分のみクリア
  tft.fillRect(100, 50, 120, 30, TFT_BLACK);
  tft.setCursor(100, 50);
  tft.print(newValue);

  prevValue = newValue;
}

方法2: 小さいスプライト

// 更新が必要な部分だけ小さいスプライトを使う
TFT_eSprite smallSpr = TFT_eSprite(&tft);

void setup() {
  // ... tft初期化後 ...

  // 小さいサイズなら安定して確保できる
  void* ptr = smallSpr.createSprite(100, 30);
  if (ptr == nullptr) {
    Serial.println("小スプライトも失敗");
  }
}

void updateSmallArea() {
  smallSpr.fillSprite(TFT_BLACK);
  smallSpr.drawString("12:34", 0, 0, 2);
  smallSpr.pushSprite(110, 105);  // 指定位置に転送
}

ちらつき防止のベストプラクティス

  1. 初回のみfillScreenで全画面描画
  2. 以降はfillRectで必要な部分のみクリア
  3. 静的な要素(タイトル、枠線、ボタンなど)は再描画しない
  4. 動的な要素(数値、メッセージなど)のみ部分更新
  5. 前回値を記録して、変化があった時だけ再描画
// 実装例: 時計表示
int lastSec = -1;

void loop() {
  struct tm timeinfo;
  getLocalTime(&timeinfo);

  // 秒が変わった時だけ更新
  if (timeinfo.tm_sec != lastSec) {
    // 時刻表示エリアだけクリア
    tft.fillRect(50, 100, 220, 40, TFT_BLACK);

    tft.setCursor(50, 100);
    tft.setTextSize(3);
    tft.printf("%02d:%02d:%02d", 
      timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);

    lastSec = timeinfo.tm_sec;
  }

  delay(100);
}

トラブルシューティング

ディスプレイが表示されない

// 1. User_Setup.hの確認(一字一句正確に)
// 2. 色反転の設定を確認
tft.init();
tft.invertDisplay(true);  // これがないと色が反転

// 3. バックライト確認
pinMode(21, OUTPUT);
digitalWrite(21, HIGH);

// 4. 初期化順序
tft.init();
tft.setRotation(1);
tft.invertDisplay(true);
tft.fillScreen(TFT_BLACK);

色がすべておかしい(最重要)

// 症状: 黒が白、緑がピンクなど、全ての色が反転
// 原因: このデバイスのパネルは色が反転している
// 解決: tft.init()の直後に以下を追加

void setup() {
  tft.init();
  tft.setRotation(1);
  tft.invertDisplay(true);  // ← この一行で解決!
}

画面がちらつく

// 原因: fillScreenで毎フレーム全画面クリアしている
// 解決: 部分更新を使用(上記「スプライト使用時の注意」参照)

// ❌ 悪い例
void loop() {
  tft.fillScreen(TFT_BLACK);  // 毎回全画面クリア → ちらつく
  tft.drawString("Hello", 10, 10);
}

// ✅ 良い例
void loop() {
  tft.fillRect(10, 10, 100, 20, TFT_BLACK);  // 必要な部分だけクリア
  tft.drawString("Hello", 10, 10);
}

スプライト作成に失敗する

// 原因: メモリ不足(フルスクリーン=約150KB必要)
// 解決1: 小さいスプライトを使う
void* ptr = spr.createSprite(100, 50);  // 小さく

// 解決2: 部分更新に切り替える(スプライト不使用)

// 解決3: 8bitカラーモードを使う(メモリ半減)
spr.setColorDepth(8);  // createSpriteの前に呼ぶ
spr.createSprite(320, 240);  // 約75KBに削減

タッチが反応しない

// 1. 初期化順序を確認(ディスプレイ→2秒待機→タッチ)
tft.init();
tft.setRotation(1);
tft.invertDisplay(true);
delay(2000);  // 【重要】2秒待機

// 2. HSPIバスの明示的な初期化を確認
SPIClass hspi(HSPI);
hspi.begin(25, 39, 32, 33);  // CLK, MISO, MOSI, CS
ts.begin(hspi);              // バスを指定して初期化
ts.setRotation(1);

// 3. キャリブレーション調整
x = map(p.x, 300, 3900, 0, 320);
y = map(p.y, 350, 3700, 0, 240);  // TS_MIN_Y = 350

RGB LEDが点灯しない・おかしい

// アクティブLOWなのでLOWで点灯
digitalWrite(LED_RED, LOW);   // 点灯 ✓
digitalWrite(LED_RED, HIGH);  // 消灯

// ⚠️ 赤LED (GPIO4) の特殊な注意点
// GPIO4はTFT_RSTと共用のため:
// 1. ディスプレイ初期化直後は必ずHIGH状態にする
// 2. 長時間LOWにしない(ディスプレイリセットの危険)
// 3. 点灯は短時間(100ms以下推奨)に抑える

// 安全な赤LED点灯パターン
void safeRedBlink() {
  digitalWrite(LED_RED, LOW);   // 点灯
  delay(50);                    // 短時間
  digitalWrite(LED_RED, HIGH);  // すぐ消灯
}

// 危険なパターン
void dangerousPattern() {
  digitalWrite(LED_RED, LOW);   // 点灯
  delay(5000);                  // 長時間LOW → ディスプレイがリセットされる可能性
}

SDカードが認識されない

// 1. FAT32フォーマット確認
// 2. CS信号確認
if (!SD.begin(5)) {  // GPIO5
  Serial.println("SD Mount Failed");
}

// 3. SPI共用の確認(ディスプレイと競合していないか)

光センサーが読み取れない(常に0)

// 原因1: ADCの初期化忘れ
// 解決: setup()で以下を呼び出す
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);

// 原因2: analogRead()を使用している
// 解決: adc1_get_raw()を使用
int value = adc1_get_raw(ADC1_CHANNEL_6);  // ✓
// int value = analogRead(34);  // ✗ 動作しない場合あり

// 原因3: WiFiとの競合(ADC2の場合のみ)
// GPIO34はADC1なので競合しない

光センサーの値が逆に見える

// このセンサーは明るいと値が低くなる仕様
// 「明るさ」として表示したい場合は反転させる
int lightValue = adc1_get_raw(ADC1_CHANNEL_6);
int brightness = 4095 - lightValue;  // 反転: 明るい→高い値
int brightnessPercent = brightness * 100 / 4095;

よくあるコーディングパターン

パターン1: 部分更新で時計表示(推奨)

#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>

TFT_eSPI tft = TFT_eSPI();
SPIClass hspi(HSPI);
XPT2046_Touchscreen ts(33, 36);

int lastSec = -1;

void setup() {
  tft.init();
  tft.setRotation(1);
  tft.invertDisplay(true);
  tft.fillScreen(TFT_BLACK);  // 初回のみ全画面クリア

  // 静的要素を描画(一度だけ)
  tft.setTextColor(TFT_CYAN);
  tft.drawString("Current Time", 100, 50, 2);
  tft.drawRect(40, 90, 240, 60, TFT_WHITE);  // 枠線

  delay(2000);

  hspi.begin(25, 39, 32, 33);
  ts.begin(hspi);
  ts.setRotation(1);
}

void loop() {
  time_t now = time(nullptr);
  struct tm *timeinfo = localtime(&now);

  // 秒が変わった時だけ更新
  if (timeinfo->tm_sec != lastSec) {
    // 時刻表示エリアだけクリア
    tft.fillRect(45, 95, 230, 50, TFT_BLACK);

    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(3);
    tft.setCursor(60, 105);
    tft.printf("%02d:%02d:%02d", 
      timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);

    lastSec = timeinfo->tm_sec;
  }

  delay(100);
}

パターン2: タッチで色変更(部分更新版)

int currentColor = 0;  // 0:黒, 1:赤, 2:緑, 3:青

void loop() {
  if (ts.touched()) {
    TS_Point p = ts.getPoint();
    int x = map(p.x, 300, 3900, 0, 320);
    int y = map(p.y, 350, 3700, 0, 240);

    int newColor;
    uint16_t tftColor;

    // 画面を3分割
    if (x < 107) {
      newColor = 1;
      tftColor = TFT_RED;
      digitalWrite(LED_RED, LOW);
      digitalWrite(LED_GREEN, HIGH);
      digitalWrite(LED_BLUE, HIGH);
    } else if (x < 214) {
      newColor = 2;
      tftColor = TFT_GREEN;
      digitalWrite(LED_RED, HIGH);
      digitalWrite(LED_GREEN, LOW);
      digitalWrite(LED_BLUE, HIGH);
    } else {
      newColor = 3;
      tftColor = TFT_BLUE;
      digitalWrite(LED_RED, HIGH);
      digitalWrite(LED_GREEN, HIGH);
      digitalWrite(LED_BLUE, LOW);
    }

    // 色が変わった時だけ画面更新
    if (newColor != currentColor) {
      tft.fillScreen(tftColor);
      currentColor = newColor;
    }

    delay(200);  // デバウンス
  }
}

パターン3: WiFi接続状態をLEDで表示

void updateWiFiStatus() {
  if (WiFi.status() == WL_CONNECTED) {
    digitalWrite(LED_RED, HIGH);
    digitalWrite(LED_GREEN, LOW);   // 緑 = 接続中
    digitalWrite(LED_BLUE, HIGH);
  } else {
    digitalWrite(LED_RED, LOW);     // 赤 = 切断
    digitalWrite(LED_GREEN, HIGH);
    digitalWrite(LED_BLUE, HIGH);
  }
}

パターン4: タッチで音を鳴らす

void setupAudio() {
  ledcSetup(0, 1000, 8);
  ledcAttachPin(26, 0);
}

void playTone(int frequency, int duration) {
  ledcSetup(0, frequency, 8);
  ledcWrite(0, 128);  // 50% duty
  delay(duration);
  ledcWrite(0, 0);
}

void loop() {
  if (ts.touched()) {
    TS_Point p = ts.getPoint();
    int x = map(p.x, 300, 3900, 0, 320);

    // タッチ位置で音程を変える
    int freq = map(x, 0, 320, 200, 2000);
    playTone(freq, 100);

    // 短時間緑LED点灯(赤LEDは避ける)
    digitalWrite(LED_GREEN, LOW);
    delay(50);
    digitalWrite(LED_GREEN, HIGH);
  }
}

パターン5: メロディー再生

// ドレミファソラシド
int melody[] = {262, 294, 330, 349, 392, 440, 494, 523};
int noteDuration = 300;

void playMelody() {
  for (int i = 0; i < 8; i++) {
    playTone(melody[i], noteDuration);
    delay(50);  // 音符間の休符
  }
}

パターン6: 光センサー読み取り

#include <driver/adc.h>

#define LIGHT_SENSOR_PIN 34

void setup() {
  // ADC初期化
  adc1_config_width(ADC_WIDTH_BIT_12);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
}

int readLightSensor() {
  // 複数回読み取って平均化(ノイズ対策)
  int sum = 0;
  for (int i = 0; i < 10; i++) {
    sum += adc1_get_raw(ADC1_CHANNEL_6);
    delayMicroseconds(100);
  }
  return sum / 10;
}

void loop() {
  int lightValue = readLightSensor();

  // 明るさとして表示(反転)
  int brightness = 4095 - lightValue;
  int brightnessPercent = brightness * 100 / 4095;

  Serial.printf("Light: %d, Brightness: %d%%\n", lightValue, brightnessPercent);
  delay(500);
}

電源仕様

入力: USB Type-C 5V
USB-シリアル変換IC: CH340C
電圧レギュレータ: AMS1117-3.3 × 2個
出力電圧: 3.3V
最大出力電流: 800mA (per レギュレータ)
消費電流:
  通常: 200-300mA
  WiFi使用時: 250-350mA
  スリープ: <1mA

回路構成:

  • USB 5V → AMS1117-3.3 → 3.3V (ESP32メイン)
  • USB 5V → AMS1117-3.3 → 3.3V (周辺デバイス)
  • 拡張IO2の3.3Vピン: レギュレータ出力から供給(最大電流注意)

未確定事項 (実機確認推奨)

優先度: 高

  1. GPIO4共用の詳細な影響
    • ✅ 確認済み: 赤LED長時間点灯時のディスプレイへの影響
    • 推奨: 赤LEDは短時間点灯(<100ms)のみ使用

優先度: 中

  1. GPIO21共用時の制約
    • バックライトON時のI2C通信可否
  2. 電源電圧仕様の詳細
    • ✅ 確認済み: AMS1117-3.3、最大800mA
    • 拡張IO2の3.3Vピンからの最大取り出し電流

優先度: 低(確認済み)

  1. シリアルポート (TX/RX) のGPIO番号
    • 確定: GPIO1 (TX0), GPIO3 (RX0)
    • USB-UART変換IC: CH340C
  2. スピーカー回路の詳細
    • 確定: 2段増幅(NPN S8050 + N-ch MOSFET AO3402)
    • 出力: 1.5W / 4Ω
  3. RGB LED駆動回路
    • 確定: NPNトランジスタ S8050 × 3、ベース抵抗10kΩ
  4. 制御ボタンのGPIO
    • BOOT: GPIO0
    • RST: ENピン(マイコンリセット)
  5. タッチスクリーン初期化手順
    • 確定: HSPIバスを明示的に初期化
    • 確定: ディスプレイ初期化後2秒待機が必要
  6. タッチキャリブレーション
    • 確定: TS_MIN_Y=350
  7. スプライトメモリ制限
    • 確定: フルスクリーン約150KB
    • 推奨: 部分更新方式
  8. 光センサー回路
    • 確定: GT36516、GPIO34、ADC1_CH6
    • 確定: 明るい→低値、暗い→高値

コード生成時のチェックリスト

AIがコードを生成する際は、以下を必ず確認してください:

  • [ ] TFT_eSPI User_Setup.hの設定が正しいか(一字一句)
  • [ ] SUPPORT_TRANSACTIONS が定義されているか
  • [ ] tft.invertDisplay(true) を呼び出しているか(最重要)
  • [ ] フルスクリーンスプライトを避け、部分更新を使用しているか
  • [ ] スプライト使用時はcreateSprite戻り値をチェックしているか
  • [ ] 初期化順序が正しいか(ディスプレイ→2秒待機→タッチ)
  • [ ] タッチ初期化でHSPIバスを明示的に使用しているか
    • [ ] SPIClass hspi(HSPI) を宣言しているか
    • [ ] hspi.begin(25, 39, 32, 33) でバスを初期化しているか
    • [ ] ts.begin(hspi) でバスを指定しているか
  • [ ] RGB LEDがアクティブLOW(LOW=点灯)になっているか
  • [ ] 赤LED (GPIO4) は短時間点灯のみ(<100ms推奨)
  • [ ] タッチ座標のキャリブレーション値を使用しているか(TS_MIN_Y=350)
  • [ ] GPIO4とGPIO21の共用を考慮しているか
  • [ ] ディスプレイとSDカードのSPI共用を考慮しているか
  • [ ] 入力専用GPIO(34,35,36,39)にプルアップを使っていないか
  • [ ] WiFi使用時はNTPサーバーを ntp.nict.jp にしているか
  • [ ] バックライトをHIGHで点灯させているか
  • [ ] 光センサー使用時は adc1_config_channel_atten() で初期化しているか
  • [ ] 光センサーは adc1_get_raw(ADC1_CHANNEL_6) で読み取っているか
  • [ ] 光センサーの特性(明るい→低値、暗い→高値)を考慮しているか

バージョン情報

ドキュメントバージョン: 2.3
対象基板: ESP-2432S028
最終更新: 2024年12月
確認済み事項: 
  - RGB LED配線(GPIO17=青, GPIO4=赤, GPIO16=緑)
  - 色反転補正(invertDisplay必須)
  - スピーカー回路(2段増幅: S8050 + AO3402)
  - シリアルポート(GPIO1=TX0, GPIO3=RX0, CH340C)
  - 電源回路(AMS1117-3.3 × 2、最大800mA)
  - RGB LED駆動(S8050 NPNトランジスタ × 3)
  - タッチ初期化手順(HSPI明示的初期化、2秒待機)
  - タッチキャリブレーション(TS_MIN_Y=350)
  - スプライトメモリ制限(フルスクリーン約150KB)
  - 推奨描画方法(部分更新)
  - 光センサー回路(GT36516、GPIO34、ADC1_CH6)
  - 光センサー特性(明るい→低値、暗い→高値)
未確認事項: 
  - GPIO21使用時のI2C通信可否
  - 拡張IO2の3.3V最大電流

サポートライブラリバージョン

TFT_eSPI: 2.5.0以上
XPT2046_Touchscreen: 1.4.0以上
ESP32 Arduino Core: 2.0.0以上

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