M5Stamp C3 Mateを試してみました【Arduino使用】

Raspberry Pi/電子工作
スポンサーリンク

こんにちは、あろっちです。

M5Stamp C3 Mateを入手したので試してみました。

参考URL:

m5-docs
The reference docs for M5Stack products. Quick start, get the detailed information or instructions such as IDE,UIFLOW,Ar...

M5Stamp C3U Mateも記事にしました。スケッチは本記事に掲載したもので試しています。

Seeed Studio XIAO ESP32C3(参考サイト)

本記事では、プログラムにArduino(C/C++)を使っています。

スポンサーリンク

ピン配列

LEDG2
ボタンG3

I2C

デフォルトのピン番号を記載します。

ピンGPIO
SDA8
SCL9

Arduinoでは、SDA、SCLをWire.begin(SDA, SCL);という形式で任意のピン番号に変更できます。
以下は、SDA=0、SCL=1に変更する例です。

Wire.begin(0, 1);

SPI

デフォルトのピン番号を記載します。

ピンGPIO
SCK4
MISO5
MOSI6
SS7

Arduinoでは、SPI.begin(SCK, MISO, MOSI, SS);という形式で任意のピン番号に変更できます。

この動画はSPIのTFTディスプレイ ILI9341の表示例です。

LovyanGFX 1.1.5 コンパイルエラー対処方法

ESP32-C3ボードでBus_SPI.cppのコンパイルエラーが発生するようです。

1.1.5以外のバージョンではコンパイルエラーは発生しないようです。

応急処置的な対処法を記載します。

修正ファイル

[スケッチ保存フォルダ]/libraries/LovyanGFX/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp

修正前

#include <soc/dport_reg.h>

修正方法は以下の通りです。

#if __has_include (<soc/dport_reg.h>)
#include <soc/dport_reg.h>
#endif

Ethernetモジュールの使用

SPI接続のEthernetモジュール(W5500)も動作しました。

W5500 Liteでも動作確認できました。

スケッチ等は以下の記事をご覧ください。

回路図

内容物

1 x M5Stamp C3
1 x 耐熱ステッカー
1 x M2 六角レンチ
2 x HY2.0-4Pコネクタ(90° Grove互換コネクタ)
1 x ピンヘッダ 10ピン
1 x ピンソケット 10ピン
1 x ピンヘッダ 12ピン
1 x ピンソケット 12ピン

技適マークは添付のステッカーに印刷されています。

最初は、ケースがついた状態で、ケースにMACアドレスが記載されたシールが貼り付けられていました。

MACアドレスのシールをはがした状態

ケースを取り外すにはMACアドレスのシールをはがし、中央のネジを付属の六角レンチで外します。

ピンヘッダー装着後

USBシリアルについて

M5Stamp C3はシリアル変換IC CH9102Fを搭載しています。

Mac シリアルドライバ

M5Stackのサイトから、CH9102_VCP_SER_MacOS v1.7をダウンロードできます。

https://docs.m5stack.com/en/download

※2022年6月26日
v1.7以前のドライバでは環境によってエラーが発生してインストールできないという不具合らしき現象が確認されていましたが、v1.7では解消されていることを確認しました。

以下は、上述の不具合の代替案として本記事執筆時に記述した情報となりますが、参考情報として残しておきます。

以下からWCH公式最新のUSBシリアルドライバをダウンロードできます。

GitHub

https://github.com/WCHSoftGroup/ch34xser_macos

Windows シリアルドライバ

M5Stackサイトから、CH9102_VCP_SER_Windowsをダウンロードできます。
ダブルクリックでインストーラが起動し、[INSTALL]をクリックするとドライバがインストールできます。

https://docs.m5stack.com/en/download

Arduinoボード設定

M5Stamp C3では次のボードが使用できます。

  • M5Stackボード STAMP-C3 (あるいはM5StampC3)
  • ESP32ボード ESP32C3 Dev Module (あるいはM5StampC3)

M5Stackボードを使用する場合

Arduino IDEにM5Stackボードを追加しておきます。

追加方法については、以下の記事をご参照ください。

  • ボードインストール

[ツール] > [ボード] > [ボードマネージャ]からM5Stackボードをインストールします。

  • ボードの選択

[ツール] > [ボード] > [M5Stack Arduino] > [STAMP-C3](あるいは[M5StampC3])を選択します。

  • シリアルポートの選択

シリアルポートは、Macの場合、/dev/cu.wchusbserial*から始まるポートを選択します。

Windowsの場合は、COM*で認識されますので、それを選択します。

ESP32ボードを使用する場合

Arduino IDEにESP32ボード(安定リリース版)を追加しておきます。

追加方法については、以下の記事をご参照ください。

  • ボードインストール

[ツール] > [ボード] > [ボードマネージャ]からESP32ボード(Ver2.0.0以降)をインストールします。

  • ボードの選択

[ツール] > [ボード] > [ESP32 Arduino] > [ESP32C3 Dev Module](あるいは[M5StampC3])を選択します。

  • シリアルポートの選択

シリアルポートは、Macの場合、/dev/cu.wchusbserial*から始まるポートを選択します。

Windowsの場合は、COM*で認識されますので、それを選択します。

USBシリアル(USB CDC On Boot:)の設定について

M5Stamp C3の場合、USBシリアルを有効にするには「USB CDC On Boot:」を「Disabled」に設定します。

書き込みモード(Download Boot)

G9とGNDを短絡することで、書き込みモード(Download Boot)にできます。

G9とGNDをジャンパーピンで短絡した状態

シリアルモニターには以下のように表示されます。
※確認できない場合はリセットボタンを押してみてください。

ESP32-C3 Technical Reference Manual(7.2 Boot Mode Control)および回路図を参考にしました。

書き込みがうまくいかない場合にお試しください。

以下はピンヘッダー未装着のときにスルーホール用テストワイヤで試した画像です。

以下のようにUSBを接続しArduino IDE(1.8.15 Mac版)からスケッチが書き込めるのを確認しました。

スルーホール用テストワイヤ(TT-200)は秋月電子通商さんで購入しました

参考(pdf):

ESP32-C3 Technical Reference Manual
ESP32-C3 Datasheet

サンプルプログラム(Arduinoスケッチ)

Lチカ

カラフルにLチカしてみました。

Adafruit NeoPixelライブラリを使用しています。

#include <Adafruit_NeoPixel.h>

#define LED_PIN 2

#define COLOR_REPEAT 2

// create a pixel strand with 1 pixel on PIN_NEOPIXEL
Adafruit_NeoPixel pixels(1, LED_PIN);

uint8_t color = 0, count = 0;
uint32_t colors[] = {pixels.Color(125, 0, 0), pixels.Color(0, 125, 0), pixels.Color(0, 0, 125), pixels.Color(125, 125, 125)};
const uint8_t COLORS_LEN = (uint8_t)(sizeof(colors) / sizeof(colors[0]));

void setup() {
  pixels.begin();  // initialize the pixel
}

void loop() {
  pixels.setPixelColor(0, colors[color]);
  pixels.show();
  
  delay(1000);
  
  pixels.clear();
  pixels.show();
  
  delay(1000);
  
  count++;

  if(count >= COLOR_REPEAT) {
    count = 0;
    color++;
    if(color >= COLORS_LEN) {
      color = 0;
    }
  }
}

レインボー

#include <Adafruit_NeoPixel.h>

#define LED_PIN 2
#define MAX_BRIGHTNESS 255

Adafruit_NeoPixel pixels(1, LED_PIN);

int rgbValues[] = {MAX_BRIGHTNESS, 0, 0}; // 0=Red, 1=Green and 2=Blue
int upIndex = 0, downIndex = 1;

void setup()
{
  pixels.begin(); // initialize the pixel
}

void loop()
{
  rgbValues[upIndex] += 1;
  rgbValues[downIndex] -= 1;

  if (rgbValues[upIndex] > MAX_BRIGHTNESS)
  {
    rgbValues[upIndex] = MAX_BRIGHTNESS;
    upIndex = upIndex + 1;

    if (upIndex > 2)
    {
      upIndex = 0;
    }
  }

  if (rgbValues[downIndex] < 0)
  {
    rgbValues[downIndex] = 0;
    downIndex = downIndex + 1;

    if (downIndex > 2)
    {
      downIndex = 0;
    }
  }

  pixels.setPixelColor(0, pixels.Color(MAX_BRIGHTNESS - rgbValues[0], MAX_BRIGHTNESS - rgbValues[1], MAX_BRIGHTNESS - rgbValues[2]));
  pixels.show();

  delay(5);
}

一撃入魂

内蔵ボタンを押すと、1/2でレインボーに光るというスケッチです。

ルール
チャンスは1回、ボタン押下時に抽選

ということで、今回のスケッチは、あえてボタン入力の受付は1回のみとしてみました。
再チャレンジしたい場合は、リセットボタンを押してみてください。

RGB LEDの状態について

ボタン入力受付は、白点滅
抽選演出は、赤で表現
抽選結果は、当選時 > レインボー、落選時 > フェードアウト

#include <Adafruit_NeoPixel.h>

#define BTN 3

#define LED_PIN 2

#define TOGGLE_PERIOD (1000u)

#define LED_STAND_BY_COLOR 0xffffff
#define LED_READY_COLOR 0xff0000

Adafruit_NeoPixel pixels(1, LED_PIN);

uint8_t rgbValues[7][3] = {
    {255, 0, 0}, {255, 165, 0}, {255, 255, 0}, {0, 128, 0}, {0, 255, 255}, {0, 0, 255}, {128, 0, 128}};

int16_t randNumber;

int16_t brightness = 0;
int16_t fadeAmount = 10;

void toggleLED_nb(void)
{
  static bool toggle = true;
  static auto lastToggle = millis(); // saved between calls
  auto now = millis();

  if (now - lastToggle > TOGGLE_PERIOD)
  {
    if (toggle)
    {
      pixels.setPixelColor(0, LED_STAND_BY_COLOR);
      pixels.setBrightness(18);
      pixels.show();
    }
    else
    {
      pixels.clear();
      pixels.show();
    }
    toggle = !toggle;
    lastToggle = now;
  }
}

void readyLED()
{
  for (uint8_t i = 0; i < 3; i++)
  {
    brightness = 0;
    pixels.clear();
    pixels.show();
    delay(100);

    for (uint8_t j = 0; j < (100 / fadeAmount); j++)
    {
      pixels.setPixelColor(0, LED_READY_COLOR);
      pixels.setBrightness(brightness);
      pixels.show();

      brightness = brightness + fadeAmount;
      delay(80);
    }

    delay(1000);
  }
  brightness = 150;
  pixels.setPixelColor(0, LED_READY_COLOR);
  pixels.setBrightness(brightness);
  pixels.show();
  delay(3000);
}

void successLED()
{
  for (uint8_t i = 0; i < 8; i++)
  {
    for (uint8_t j = 0; j < 7; j++)
    {
      pixels.setPixelColor(0, pixels.Color(rgbValues[j][0], rgbValues[j][1], rgbValues[j][2]));
      pixels.setBrightness(18);
      pixels.show();
      delay(50);
    }
  }
}

void failureLED()
{
  while (0 < brightness)
  {
    pixels.setPixelColor(0, LED_READY_COLOR);
    pixels.setBrightness(brightness);
    pixels.show();

    brightness = brightness - fadeAmount;
    delay(80);
  }

  pixels.clear();
  pixels.show();
  delay(200);
  pixels.setPixelColor(0, LED_READY_COLOR);
  pixels.setBrightness(10);
  pixels.show();
  delay(150);
}

void setup()
{
  pinMode(BTN, INPUT_PULLUP);

  pixels.begin();
  while (digitalRead(BTN) == HIGH)
  {
    toggleLED_nb();
    delay(10);
  }

  // 抽選
  randomSeed(analogRead(0));
  randNumber = (int16_t)random(0xffff + 1);

  readyLED();

  if (randNumber < 0)
  {
    // おめでとう
    successLED();
  }
  else
  {
    // 残念
    failureLED();
  }

  pixels.clear();
  pixels.show();
}

void loop()
{
}

HelloServer

[スケッチ例] > [WebServer] > [HelloServer]のコードをM5Stamp C3用に修正したものです。
LEDにNeoPixelを使うように修正しています。

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Adafruit_NeoPixel.h>

const char* ssid = "........";
const char* password = "........";

WebServer server(80);

#define LED_PIN 2
Adafruit_NeoPixel pixels(1, LED_PIN);

void led_on() {
  pixels.setPixelColor(0, pixels.Color(125, 0, 0));
  pixels.show();
}

void led_off() {
  pixels.clear();
  pixels.show();  
}

void handleRoot() {
  led_on();
  server.send(200, "text/plain", "hello from esp32!");
  led_off();
}

void handleNotFound() {
  led_on();
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  led_off();
}

void setup(void) {
  pixels.begin();
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  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());

  if (MDNS.begin("esp32")) {
    Serial.println("MDNS responder started");
  }

  server.on("/", handleRoot);

  server.on("/inline", []() {
    server.send(200, "text/plain", "this works as well");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  server.handleClient();
  delay(2);//allow the cpu to switch to other tasks
}

ssidとpasswordを接続するWi-Fiの情報に書き換えてください。

シリアルモニターを開くと、HelloServerのIPアドレスが表示されますので、ブラウザからアクセスします。

以下のように表示されます。

http://IPアドレス

http://IPアドレス/inline

http://IPアドレス/適当な文字列

BLE UART

BLEの例を紹介します。
[スケッチ例] > [ESP32 BLE Arduino] > [BLE_uart]をペリフェラル(M5Stamp C3)のシリアルを通してBLEクライアントにメッセージ通知(notify)できるように改修してみました。

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;

#define TXVALUE_SIZE 64
uint8_t txValue[TXVALUE_SIZE];
size_t size;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID           "6b02758a-24f2-11ed-861d-0242ac120002" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6b027832-24f2-11ed-861d-0242ac120002"
#define CHARACTERISTIC_UUID_TX "6b02797c-24f2-11ed-861d-0242ac120002"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string rxValue = pCharacteristic->getValue();

      if (rxValue.length() > 0) {
        Serial.println("*********");
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)
          Serial.print(rxValue[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};


void setup() {
  Serial.begin(115200);

  // Create the BLE Device
  BLEDevice::init("ESP32-C3 UART Service");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pTxCharacteristic = pService->createCharacteristic(
										CHARACTERISTIC_UUID_TX,
										BLECharacteristic::PROPERTY_NOTIFY
									);
                      
  pTxCharacteristic->addDescriptor(new BLE2902());

  BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
											 CHARACTERISTIC_UUID_RX,
											BLECharacteristic::PROPERTY_WRITE
										);

  pRxCharacteristic->setCallbacks(new MyCallbacks());

  // Start the service
  pService->start();

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {

    if (deviceConnected) {
      while (Serial.available())
      {
        size = Serial.readBytes(txValue, TXVALUE_SIZE);
        pTxCharacteristic->setValue(txValue, size);
        pTxCharacteristic->notify();
      }
      delay(10); // bluetooth stack will go into congestion, if too many packets are sent
    }

    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        delay(500); // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
		// do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
}

動作テスト

BLEクライアントの通信アプリケーションとして、ここでは「LightBlue」を使用します。
「LightBlue」はAndroidやiOSのアプリです。操作性(画面)はAndroidとiOSで若干異なります。

ここではAndroid版を使用します。
iOS版の操作方法は後述の「ESP32_BleSerialライブラリを使う」の動作テストを参考にしてください。

ペリフェラル側(PC)は「Arduino IDE」のシリアルモニターを使用します。

BLE接続(LightBlue for Android)

はじめにLightBlueからM5Stamp C3(ESP32-C3 UART Service)にBLE接続します。

接続に成功すると次のような画面になります。

ペリフェラル(PC) → BLEクライアント (Notify)

LightBlueのNotifyをタップします。

Data formatをUTF-8 Stringに変更して、SUBSCRIBEをタップします。

Arduino IDEのシリアルモニターからメッセージを送信します。

メッセージ「あろしーど」を送信

LightBlueに受信データ(今回は「あろしーど」)が表示されます。

BLEクライアント → ペリフェラル(PC) (Writable)

LightBlueのWritableをタップします。

Data formatをUTF-8 Stringにします。

WRITTEN VALUESにメッセージを入力し「WRITE」をタップします。

メッセージ「あろしーど」をWRITE(送信)

Arduino IDEのシリアルモニターにLightBlueから送信したメッセージが表示されます。

ピクチャーフレーム

ストレージ領域に格納された画像ファイル(jpgかpng)をLCDに表示するスケッチです。
画像ファイルが複数あると順番に表示します。

開発環境
  • IDE: Arduino IDE 2系
  • ボード: ESP32 (M5Stackボードは非対応です)
  • ファイルアップローダー: arduino-littlefs-upload
  • ライブラリ: LovyanGFX

準備

arduino-littlefs-upload

Arduino IDE 2系用のストレージ領域にファイルをアップロードするツールです。
未インストールの場合にインストールします。

https://github.com/earlephilhower/arduino-littlefs-upload/releases

上記サイトからvsixファイルをダウンロードします。

画像は1.3.0ですが、最新バージョンをダウンロードすればよいかと思います。

以下の場所に配置します。

  • Windows
     C:\Users\<username>\.arduinoIDE\plugins
  • Mac
    ~/.arduinoIDE/plugins/

Windowsの例

pluginsフォルダがない場合は作成して配置します。

スケッチ

#include <LittleFS.h>
#include <LovyanGFX.hpp>

// グラフィックボードのピン配置(M5Stamp C3)
#define TFT_MISO -1
#define TFT_MOSI 6
#define TFT_SCLK 4
// #define TFT_CS 7   // Chip select control pin
#define TFT_DC 8    // Data Command control pin
#define TFT_RST 10  // Reset pin (could connect to RST pin)
// ST7789の場合バックライト(ピン)はプルアップされるのでコメント
// #define TFT_BL 22

// ST7789 1.3インチ CSなし
class LGFX : public lgfx::LGFX_Device {
  lgfx::Panel_ST7789 _panel_instance;
  lgfx::Bus_SPI _bus_instance;  // SPIバスのインスタンス

public:
  LGFX(void) {
    {                                     // バス制御の設定を行います。
      auto cfg = _bus_instance.config();  // バス設定用の構造体を取得します。

      // SPIバスの設定
      cfg.spi_host = SPI2_HOST;
      cfg.spi_mode = 0;           // SPI通信モードを設定 (0 ~ 3)
      cfg.freq_write = 40000000;  // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
      cfg.freq_read = 20000000;   // 受信時のSPIクロック

      //      cfg.spi_3wire  = true;        // 受信をMOSIピンで行う場合はtrueを設定
      //      cfg.use_lock   = true;        // トランザクションロックを使用する場合はtrueを設定
      cfg.dma_channel = SPI_DMA_CH_AUTO;  // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)

      cfg.pin_sclk = TFT_SCLK;  // SPIのSCLKピン番号を設定
      cfg.pin_mosi = TFT_MOSI;  // SPIのMOSIピン番号を設定
      cfg.pin_miso = TFT_MISO;  // SPIのMISOピン番号を設定 (-1 = disable)
      cfg.pin_dc = TFT_DC;      // SPIのD/Cピン番号を設定  (-1 = disable)

      _bus_instance.config(cfg);               // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    }

    {                                       // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config();  // 表示パネル設定用の構造体を取得します。

      cfg.pin_cs = -1;    // CSが接続されているピン番号   (-1 = disable)
      cfg.pin_rst = -1;   // RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy = -1;  // BUSYが接続されているピン番号 (-1 = disable)

      cfg.panel_width = 240;   // 実際に表示可能な幅
      cfg.panel_height = 240;  // 実際に表示可能な高さ

      cfg.invert = true;  // パネルの明暗が反転してしまう場合 trueに設定
      // cfg.rgb_order = true; // パネルの赤と青が入れ替わってしまう場合 trueに設定

      _panel_instance.config(cfg);
    }

    setPanel(&_panel_instance);  // 使用するパネルをセットします。

    lgfx::pinMode(TFT_RST, lgfx::pin_mode_t::output);
    lgfx::pinMode(TFT_SCLK, lgfx::pin_mode_t::output);

    lgfx::gpio_lo(TFT_RST);
    lgfx::gpio_hi(TFT_SCLK);
    lgfx::gpio_hi(TFT_RST);
  }
};

const unsigned long displayDuration = 2000;  // Display time (2 seconds)

LGFX lcd;  // LGFX instance
std::vector<String> fileList;
int currentIndex = 0;
int32_t screenWidth;
int32_t screenHeight;

void initFileSystem() {
  if (!LittleFS.begin()) {
    Serial.println("Failed to mount LittleFS");
    for (;;) yield();
  }
}

void createFileList() {
  File root = LittleFS.open("/", "r");
  File file = root.openNextFile();

  while (file) {
    String filePath = file.path();
    if (filePath.endsWith(".jpg") || filePath.endsWith(".png")) {
      fileList.push_back(filePath);
    }
    file = root.openNextFile();
  }
  root.close();

  if (fileList.empty()) {
    Serial.println("No image files found in LittleFS.");
    for (;;) yield();
  }
}

// Get JPEG image size
bool getJpgSize(File &file, int &width, int &height) {
  uint8_t data[5];
  file.read(data, 2);

  if (data[0] == 0xFF && data[1] == 0xD8) {
    while (file.read(data, 4) == 4) {
      if (data[0] == 0xFF && (data[1] >= 0xC0 && data[1] <= 0xC3)) {
        file.read(data, 5);
        height = data[1] << 8 | data[2];
        width = data[3] << 8 | data[4];
        return true;
      } else {
        uint16_t size = data[2] << 8 | data[3];
        file.seek(file.position() + size - 2);
      }
    }
  }
  return false;
}

// Get PNG image size
bool getPngSize(File &file, int &width, int &height) {
  uint8_t data[24];
  if (file.read(data, 24) == 24) {
    if (data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47) {
      width = data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19];
      height = data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23];
      return true;
    }
  }
  return false;
}

void drawImage(const String &fileName) {
  File file = LittleFS.open(fileName, "r");
  if (!file) {
    Serial.println("Failed to open file: " + fileName);
    return;
  }

  int imgWidth = 0, imgHeight = 0;
  bool validImage = false;
  bool isJpg = false;

  // 画像サイズ取得
  if (fileName.endsWith(".jpg")) {
    validImage = getJpgSize(file, imgWidth, imgHeight);
    isJpg = true;
  } else if (fileName.endsWith(".png")) {
    validImage = getPngSize(file, imgWidth, imgHeight);
  }

  if (!validImage) {
    file.close();
    Serial.println("Invalid or unsupported image format.");
    return;
  }

  // ファイル開始位置を戻す
  file.seek(0);

  // スケーリング
  float scaleX = static_cast<float>(screenWidth) / imgWidth;
  float scaleY = static_cast<float>(screenHeight) / imgHeight;
  float scale = min(scaleX, scaleY);

  // x座標 センタリング
  int32_t x = (screenWidth - imgWidth * scale) / 2;

  lcd.startWrite();
  lcd.clear();
  if (isJpg) {
    lcd.drawJpg(&file, x, 0, screenWidth, screenHeight, 0, 0, scale);
  } else {
    lcd.drawPng(&file, x, 0, screenWidth, screenHeight, 0, 0, scale);
  }
  lcd.endWrite();
  file.close();
}

void setup() {
  Serial.begin(115200);

#ifdef TFT_BL
  // バックライトを設定
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH);
#endif

  // LCD初期化
  lcd.init();
  screenWidth = lcd.width();
  screenHeight = lcd.height();

  // LittleFSのファイルリストを作成
  initFileSystem();
  createFileList();
}

void loop() {
  // 画像を描画
  drawImage(fileList[currentIndex]);
  // 次の画像(インデックス)
  currentIndex = (currentIndex + 1) % fileList.size();
  delay(displayDuration);
}

このスケッチは1.3インチ ST7789 CSなしLCDに対応しています。

LCDピン/GPIO
GNDGND
VCC3.3V
SCL4
SDA6
RES10
DC8
BLK未接続

お好みのLCDに対応させるには、LGFXクラスの定義を変えればよいでしょう。
以下の記事を参考にしてください。

LGFXの定義を変えればESP32やESP32-S3にも対応できるかと思います。

ESP32動作例

画像ファイルアップロード

サンプル画像を用意しました。以下からダウンロードできます。

sample.zip

zipファイルを解凍するとdataフォルダがあります。
dataフォルダをスケッチフォルダに配置してください。

ファイルアップロード手順は以下の通りです。

ファイルのアップロードに成功するとこのように表示されます。
LCDに画像が表示されればOKです。

表示できる画像ファイルについて

.jpgと.pngの表示に対応しています。
お好みの画像ファイル(※)をスケッチフォルダ配下のdataフォルダに配置し、画像ファイルアップロードの手順でファイルをアップロードすれば表示されるかと思います。
ぜひお試しください。

※画像ファイル容量の合計はストレージ領域に収まる範囲になります。デフォルトでは1.5MB以内。

まとめ

小さくて高機能。USBシリアルポート搭載で、すぐ試せるというのが嬉しいですね。

ご参考になればと思います。

関連記事

当ブログのマイコン記事です。ぜひご覧ください。

コメント

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