SenseCAP IndicatorでESP32側からmicroSDのファイルを読む

SenseCAP Indicatorという4インチタッチスクリーン液晶のESP32デバイスがあるんですね。
https://www.seeedstudio.com/SenseCAP-Indicator-D1-p-5643.html
Groveコネクタもあるし、microSDカードも挿せるので、でかいM5Stackみたいに使えないかな…と思って…買いました。
https://files.seeedstudio.com/products/SenseCAP/SenseCAP_Indicator/SenseCAP%20Indicator%20User%20Manual_2023.4.21.pdf
ところがこのマニュアルの回路図を見てほしいんですけど、microSDスロットがつながっているのはRP2040の方なんですよ。M5Stackの感覚で直接読むわけには行かないのでどうしようかと思って…RP2040でmicroSDから読んだデータをUARTでESP32の方に送信してみることにしました。

## 準備
### Platform IOでESP32側の開発ができるようにする
公式にはESP-IDFを使う方法が書いてあるんですけど、
https://wiki.seeedstudio.com/SenseCAP_Indicator_How_To_Flash_The_Default_Firmware/#for-esp32-s3
使い慣れたPlatform IOを使いたかったのでここを参考にしました。
https://qiita.com/fukuebiz/items/de6007c651255dd2458b
例ではesp32s3box.jsonを書き換えていますが、一応SenseCap-Indicator.jsonみたいに別ファイルを作成してボードを追加しています。
LovyanGFXが使えれば画像の表示も簡単そうです。microSDカードから画像を読みこんで表示することにしました。
### Arduino IDEでRP2040側の開発ができるようにする
こっちもPlatform IOでやりたかったのですが…よく分からなかったため…公式に設定方法が書いてあるArduino IDEの方を使うことにしました。
https://wiki.seeedstudio.com/SenseCAP_Indicator_How_To_Flash_The_Default_Firmware/#for-rp2040

## UART通信
### RP2040側
UART通信ってどの程度まで速度出せるんでしょうね？こちらを参考に921600を設定してます。
https://asukiaaa.blogspot.com/2023/02/esp32-c3-s3-builtin-usb-can-send-700kbps.html
ESP32側から一行単位でコマンドを受け取り、応答を返します。「SD_OPEN_READ:(ファイル名)」でファイルを開いて「SD_READ_LENGTH:(バイト数)」でファイルを読むとかそんな感じです。手を抜いたので読み込みのみだし、ファイルは1つしか同時に開けないし、ファイル名は13文字までです（「/filename.txt」みたいなSD直下の8.3ファイル名を想定しています）。
```cpp
#include <Arduino.h>
#include <SPI.h>
#include <SD.h>

File currentFile;

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

  const int chipSelect = 13;
      SPI1.setSCK(10);
      SPI1.setTX(11);
      SPI1.setRX(12);
      if (SD.begin(chipSelect, 1000000, SPI1))
      {
        Serial.println("SD_BEGIN:TRUE");
      }
      else
      {
        Serial.println("SD_BEGIN:FALSE");
      }
  
  // Begin Serial1 for UART communication with ESP32
  Serial1.setTX(16);
  Serial1.setRX(17);
  Serial1.begin(921600);
}

void loop()
{
  if (Serial1.available() > 0)
  { // Listening Serial1 to receive commands from ESP32S3
    String line = Serial1.readStringUntil('\n');

    if (line.startsWith("SD_EXISTS:"))
    {
      String fileName = line.substring(strlen("SD_EXISTS:"));
      if (fileName.length() < 14) {
        char fileChar[14];
        fileName.toCharArray(fileChar, fileName.length());
        if (SD.exists(fileChar))
        {
          Serial1.printf("SD_EXISTS:TRUE %s\n", fileChar);
        }
        else
        {
          Serial1.printf("SD_EXISTS:FALSE %s\n", fileChar);
        }
      }
      else
      {
        Serial1.printf("SD_EXISTS:FALSE LONG %s\n", fileName.c_str());
      }
    }
    
    else if (line.startsWith("SD_OPEN_READ:"))
    {
      String fileName = line.substring(strlen("SD_OPEN_READ:"));
      if (fileName.length() < 14) {
        char fileChar[14];
        fileName.toCharArray(fileChar, fileName.length());

        if (currentFile = SD.open(fileChar))
        {
          Serial1.println("SD_OPEN_READ:TRUE");
        }
        else
        {
          Serial1.println("SD_OPEN_READ:FALSE");
        }
      } else {
        Serial1.println("SD_OPEN_READ:FALSE LONG");
      }
    }
    else if (line.startsWith("SD_CLOSE:"))
    {
      if (currentFile)
      {
        currentFile.close();
        Serial1.println("SD_CLOSE:TRUE");
      }
      else
      {
        Serial1.println("SD_CLOSE:FALSE");
      }
    }
    else if (line.startsWith("SD_SIZE:"))
    {
      if (currentFile)
      {
        Serial1.printf("SD_SIZE:%d\n", currentFile.size());
      }
      else
      {
        Serial1.println("SD_SIZE:0");
      }
    }
    else if (line.startsWith("SD_READ_LENGTH:"))
    {
      String lenString = line.substring(strlen("SD_READ_LENGTH:"));
      size_t len = lenString.toInt();

      if (currentFile.size() - currentFile.position() < len) {
        len = currentFile.size() - currentFile.position();
      }
       
      Serial1.println("SD_READ_START:");

      char buffer[512];
      while (len > 0)
      {
        size_t readLen = 0;
        if (len > 512) {
          readLen = currentFile.readBytes(buffer, 512);
        } else {
          readLen = currentFile.readBytes(buffer, len);
        }
        Serial1.write(buffer, readLen);
        //byte aByte = currentFile.read();
        //len -= Serial1.write(aByte);
        len -= readLen;
      }
      
    }
    else if (line.startsWith("SD_READ_LINE:"))
    {
      String fileLine = currentFile.readStringUntil('\n');
      Serial1.println(fileLine);
    }
    else
    {
      Serial1.println(line);
    }
  }
}
```

### ESP32側
本当はLovyanGFX周りのセッティングがありますが、それはこちらのまんまですので割愛します。
https://qiita.com/fukuebiz/items/de6007c651255dd2458b
Serial2を使ってRP2040側にコマンド文字列を送り、ファイルサイズを聞いてメモリを確保し、受信したJPEGをLovyanGFXで表示させます。本当はStreamとして直に渡せると思う。
```cpp
void setup(void)
{
  Serial2.begin(921600, SERIAL_8N1, 20, 19);
}

void loadJpeg(String fileName)
{
  String command = "SD_OPEN_READ:" + fileName;
  Serial2.println(command);
  String response = Serial2.readStringUntil('\n');
  Serial.println(response);

  Serial2.println("SD_SIZE:");
  response = Serial2.readStringUntil('\n');
  Serial.println(response);

  String sizeString = response.substring(strlen("SD_SIZE:"));
  size_t fileSize = sizeString.toInt();
  size_t position = 0;

  byte *fileBuffer = (byte *)malloc(fileSize * sizeof(byte));
  if (!fileBuffer) {
    Serial.println("malloc failed");
    return;
  }

  Serial2.printf("SD_READ_LENGTH:%d\n", fileSize);
  response = Serial2.readStringUntil('\n');

  while (position < fileSize)
  {
    if (Serial2.available()) {
      size_t readLength =  Serial2.readBytes(fileBuffer + position, (fileSize - position));
      position += readLength;
      if (position == fileSize) break;
    }
  }
  display.drawJpg(fileBuffer, fileSize, 0, 0, 480, 480);
  Serial2.println("SD_CLOSE");
  free(fileBuffer);
}
```

## できた
できた。480x480、96KBのJPEGで1秒くらい受信にかかっているようです。
![IMG_3530.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37794/23578f00-06b0-bbe6-c302-1c92b9f3442f.jpeg)