ESP-WROOM-02でTFTに画像を表示する

TFTのLEDバックライト制御


HSES-LCD-24は非常に完成度の高い製品ですが、唯一といえる弱点がTFTのLEDバックライト制御ができません。
こ ちらにHSES-LCD-24の回路図が公開されていますが、TFTのLEDは3.3Vに直結です。

こちらのスケッチでは、再起動時にイメージファイルが無い時は、背景を黒に していますが、
それでもTFTはぼんやり明るい状態です。
そこで、Wifi-NodeMを使って、TFTのLEDバックライト制御を試してみました。


TFTとWifi-NodeMとの接続は以下の通りです。
TFT Wifi-NodeM
VCC 3.3V
GND Gnd
CS IO2
RESET IO4(※1)
DC IO15
SDI/MOSI IO13
SCK IO14
LED IO5(※2)
SDO/MISO N/C(※3)

(※1)
今までTFTのRESTEピンはRST(PullUP)に接続していました。

(※2)
ちょっと心配しましたが、TFTのLEDピンをIO5に直結し、画像表示の際にIO5のON/OFFで、
LEDバックライトのON/OFFができる事を確認しました。
これで、再起動時にイメージファイルが無い時は、完全にTFTは真っ暗にすることができます。
本当はこ ちらで紹介しているように、トランジスタを使ったSW 回路で制御した方がいいです。

<追記>
その後ESP8266のディジタルピンの出力電流について調べてみました。
こちらより引用し ます。

1.12mA here refers to the source current and sink current is greater than 12mA.
2.12mA is for per pin and each pin is the same.
3. Drive capacity current of all GPIO pins total can be 16 x 12 mA.

The sink current of GPIO is about 20mA.
</追記>

(※3)
こ ちらでMISOは使用していないという記事を見つけました。
N/Cで問題なく動いています。


Wifi-NodeMは、いずれESP-WROOM-02に置き換える予定ですが、手元にPL-2303のUSB-TTL変換ケーブルしかありま せ ん。
どうもPL-2303の変換ケーブルでは、FlashSizeが4M(3M SPIFFS)のスケッチの書き込みに失敗するようです。
CH340/FT231X/CP210Xが使われている開発ボードでは、4M(3M SPIFFS)のスケッチが書き込めることを確認しています。
現在、CH340/FT232/CP210XのUSB-TTL変換ケーブルを手配中です。
結果が判明しましたらこちらに追記します。

<2017年1月22日追記>
FT232のUSB-TTL変換ケーブルが届きました。
予想通り4M(3M SPIFFS)のスケッチの書き込みができました。

<2017年1月23日追記>
CH340とCP210XのUSB-TTL変換ケーブルが届きました。
こちらでも4M(3M SPIFFS)のスケッチの書き込みができました。

PL-2303は互換品対策のせいで、Windowsのドライバー周りが色々面倒です。
思いきってPL-2303は全て捨てました。



USB-TTL変換ケーブルが届いたので、ESP-WROOM-02に置き換えました。
最終的なスケッチは以下の様になります。
今まで固定IPを使っていましたが、こちらで紹介しているように、 mDNSによる名前解決ができることが分かったので
固定IPをやめてDHCP+mDNSに変更しています。
ftp接続時にはesp8266_ftp.localのホスト名で接続できます。
また、今までtft.pushColor()でBMPファイルを描画できていたのですが、なぜかできなくなってしまったので
tft.setPixel()に置き換えています。
/*
   SPIFFSにあるBMPファイルを表示する
   LED制御はGPIO5で行う
   ホスト名はesp8266_ftp.local
*/
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266FtpServer.h>
#include <ESP8266mDNS.h>
#include <FS.h>
#include <SPI.h>
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

const char* ssid = "Your SSID";
const char* password = "Your Password";

//set #define FTP_DEBUG in ESP8266FtpServer.h to see ftp verbose on serial
FtpServer ftpSrv;

#define TFT_DC 15
#define TFT_CS 2
#define TFT_RST 4
#define TFT_LED 5

#define DNS_NAME "esp8266_ftp"

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

long lastTime = 0;

void listDir() {
  char cwdName[2];

  strcpy(cwdName, "/");
  Dir dir = SPIFFS.openDir(cwdName);
  while ( dir.next()) {
    String fn, fs;
    fn = dir.fileName();
    fn.remove(0, 1);
    fs = String(dir.fileSize());
    Serial.println("<" + fn + "> size=" + fs);
  } // end while
}

void display_Running_Sketch (void) {
  String the_path = __FILE__;
  int slash_loc = the_path.lastIndexOf('/');
  String the_cpp_name = the_path.substring(slash_loc + 1);
  int dot_loc = the_cpp_name.lastIndexOf('.');
  String the_sketchname = the_cpp_name.substring(0, dot_loc);

  Serial.print("\nArduino is running Sketch: ");
  Serial.println(the_sketchname);
  Serial.print("Compiled on: ");
  Serial.print(__DATE__);
  Serial.print(" at ");
  Serial.print(__TIME__);
  Serial.print("\n");
}


void setup(void) {
  delay(1000);
  Serial.begin(9600);
  Serial.print("TFT_DC=");  Serial.println(TFT_DC);
  Serial.print("TFT_CS=");  Serial.println(TFT_CS);
  Serial.print("TFT_RST="); Serial.println(TFT_RST);
  Serial.print("TFT_LED="); Serial.println(TFT_LED);
  display_Running_Sketch();
  pinMode(TFT_LED, OUTPUT);

  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // Start the mDNS responder
  // You can connect DNS_NAME.local
  if (!MDNS.begin(DNS_NAME)) {
    while (1) {
      Serial.println("Error setting up MDNS responder!");
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");

  tft.begin();
  if (SPIFFS.begin()) {
    Serial.println("SPIFFS opened!");

    File    bmpFile;
    int      bmpWidth, bmpHeight;   // W+H in pixels
    uint32_t bmpImageoffset;        // Start of image data in file

    // Check BMP file
    if (bmpFile = SPIFFS.open("/image.bmp", "r")) {
      Serial.println("BMP File found");
      if (read16(bmpFile) == 0x4D42) { // BMP signature
        Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
        (void)read32(bmpFile); // Read & ignore creator bytes
        bmpImageoffset = read32(bmpFile); // Start of image data
        Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
        // Read DIB header
        Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
        bmpWidth  = read32(bmpFile);
        bmpHeight = read32(bmpFile);
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);
      }
      bmpFile.close();

      // Show BMP file
      digitalWrite(TFT_LED, HIGH);
      tft.fillScreen(ILI9341_BLUE);
      tft.setRotation(3);
      int offsetX = (320 - bmpWidth) / 2;
      bmpDraw("/image.bmp", offsetX, 0);
    } else {
      Serial.println("BMP File NOT found");
      digitalWrite(TFT_LED, LOW);
      tft.fillScreen(ILI9341_BLACK);
    }

    //FTP Setup, ensure SPIFFS is started before ftp;
    //username, password for ftp.  set ports in ESP8266FtpServer.h  (default 21, 50009 for PASV)
    ftpSrv.begin("esp8266", "esp8266");
  } else {
    while (1) {
      Serial.println("SPIFFS failed!");
      delay(1000);
    }
  }
}

void loop(void) {
  static long ResetTime = 0x7fffffff;

  ftpSrv.handleFTP();
  yield();
  long now = millis();
  if (now - lastTime > 1000) {
    lastTime = now;
    Serial.print("IP Address=");
    Serial.print(WiFi.localIP());
    Serial.print(" ResetTime=");
    Serial.print(ResetTime);
    Serial.print(" ");
    Serial.print("now=");
    Serial.println(now);
    if (ResetTime < now) ESP.reset();

    File txtFile;
    // upload.txtが有れば10秒後に強制Reset
    if (txtFile = SPIFFS.open("/upload.txt", "r")) {
      Serial.println("upload.txt found");
      txtFile.close();
#if 0
ここでは表示しない
再起動時にBMPファイルが有れば表示する
      // Show BMP file
      tft.fillScreen(ILI9341_BLUE);
      tft.setRotation(3);
      bmpDraw("/image.bmp", 45, 0);
#endif
      SPIFFS.remove("/upload.txt");
      ResetTime = now + 10000; //10秒後にreset
    }
  }
}

// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance.

#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint16_t y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3 * BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  if ((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

  // Open requested file on SD card
  ;
  if (!(bmpFile = SPIFFS.open(filename, "r"))) {
    Serial.print(F("File not found"));
    return;
  }

  // Parse BMP header
  if (read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if (read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if ((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if (bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if ((x + w - 1) >= tft.width())  w = tft.width()  - x;
        if ((y + h - 1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x + w - 1, y + h - 1);

        for (row = 0; row < h; row++) { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if (bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos, SeekSet);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col = 0; col < w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            //tft.pushColor(tft.color565(r,g,b)); // なぜか動かなくなった
            tft.drawPixel(col, row, tft.color565(r, g, b));

          } // end pixel
        } // end scanline
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if (!goodBmp) Serial.println(F("BMP format not recognized."));
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

TFTは2.4インチから2.8インチに換装しています。


左がWif-NodeM+2.8インチ 右がHSES-LCD-24+2.4インチ