ESP32を使用してカメラ映像をLoRa通信で飛ばしてみた　パート１

今回事務所から離れた場所にあるアナログメータの写真を
事務所PCより確認したいとご依頼を頂きましたので、
LoRa通信を使用してESP32で取得したカメラ映像をPCから確認できるようにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/735ee976-3e9e-f280-33c2-1d2944e1a66d.png)



まずそのためには、カメラのデータをESP32が取り込む必要があります。
パート１では、カメラ映像をSDカードに保存する処理までをやってみます。

## ハードウェア
・FREENOVE ESP32-S3 cam
・マイクロSDカード

## ソフトウェア
まずは、書込み設定です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339052/ba1d919a-7709-0026-121b-ea63b759ed0e.png)

ソースコードは以下
```c
#include "esp_camera.h"
#include "SD_MMC.h"

// Pin definition for CAMERA_MODEL_ESP32S3_EYE
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5

#define Y2_GPIO_NUM 11
#define Y3_GPIO_NUM 9
#define Y4_GPIO_NUM 8
#define Y5_GPIO_NUM 10
#define Y6_GPIO_NUM 12
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 17
#define Y9_GPIO_NUM 16

#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13

// Pin definition for SD MMC
#define SD_MMC_CMD 38  //Please do not modify it.
#define SD_MMC_CLK 39  //Please do not modify it.
#define SD_MMC_D0 40   //Please do not modify it.

// カメラ画像の更新間隔 (ミリ秒で指定) 500ミリ秒間隔
#define UPDATE_INTERVAL (500u)

// カメラ画像フレームバッファ
camera_fb_t* fb;

void cameraInit(void) {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;  // 画像のフォーマット YUV422,GRAYSCALE,RGB565,JPEGから選択

  //PSRAMが存在する場合、UXGA解像度と高いJPEG画質で開始し、
  //事前に割り当てられたフレームバッファを大きくする。
  if (psramFound()) {
    Serial.println("psramFound true");
    config.jpeg_quality = 10;               //10-63 数字が小さいほど高品質
    config.fb_count = 2;                    //バッファ数選択
    config.frame_size = FRAMESIZE_VGA;      // FRAMESIZE_ + QVGA(320×240)|CIF(352×288)|VGA(640×480)|SVGA(800×600)|XGA(1024×768)|SXGA(1280×1024)|UXGA(1600×1200)
    config.grab_mode = CAMERA_GRAB_LATEST;  //バッファに保存するタイミングを設定　CAMERA_GRAB_WHEN_EMPTY=前回データ、CAMERA_GRAB_LATEST=最新のデータ
    config.fb_location = CAMERA_FB_IN_PSRAM;
  } else {
    // PSRAMが使用できない場合、フレームサイズを制限する。
    Serial.println("psramFound false");
    config.jpeg_quality = 20;
    config.fb_count = 1;
    config.frame_size = FRAMESIZE_VGA;
    config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
    config.fb_location = CAMERA_FB_IN_DRAM;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t* s = esp_camera_sensor_get();
  s->set_brightness(s, 0);                  // -2 to 2
  s->set_contrast(s, 0);                    // -2 to 2
  s->set_saturation(s, 0);                  // -2 to 2
  s->set_special_effect(s, 0);              // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
  s->set_whitebal(s, 1);                    // 0 = disable , 1 = enable
  s->set_awb_gain(s, 1);                    // 0 = disable , 1 = enable
  s->set_wb_mode(s, 0);                     // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
  s->set_exposure_ctrl(s, 1);               // 0 = disable , 1 = enable
  s->set_aec2(s, 0);                        // 0 = disable , 1 = enable
  s->set_ae_level(s, 0);                    // -2 to 2
  s->set_aec_value(s, 300);                 // 0 to 1200
  s->set_gain_ctrl(s, 1);                   // 0 = disable , 1 = enable
  s->set_agc_gain(s, 0);                    // 0 to 30
  s->set_gainceiling(s, (gainceiling_t)0);  // 0 to 6
  s->set_bpc(s, 0);                         // 0 = disable , 1 = enable
  s->set_wpc(s, 1);                         // 0 = disable , 1 = enable
  s->set_raw_gma(s, 1);                     // 0 = disable , 1 = enable
  s->set_lenc(s, 1);                        // 0 = disable , 1 = enable
  s->set_hmirror(s, 0);                     // 0 = disable , 1 = enable
  s->set_vflip(s, 0);                       // 0 = disable , 1 = enable
  s->set_dcw(s, 1);                         // 0 = disable , 1 = enable
  s->set_colorbar(s, 0);                    // 0 = disable , 1 = enable
  s->set_denoise(s, 1);                     // ノイズ除去
}

void sdmmcInit(void) {
  SD_MMC.setPins(SD_MMC_CLK, SD_MMC_CMD, SD_MMC_D0);
  if (!SD_MMC.begin("/sdcard", true, true, SDMMC_FREQ_DEFAULT, 5)) {
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD_MMC.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD_MMC card attached");
    return;
  }
  Serial.print("SD_MMC Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }
  uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
  Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
  Serial.printf("Total space: %lluMB\r\n", SD_MMC.totalBytes() / (1024 * 1024));
  Serial.printf("Used space: %lluMB\r\n", SD_MMC.usedBytes() / (1024 * 1024));
}

void createDir(fs::FS& fs, const char* path) {
  Serial.printf("Creating Dir: %s\n", path);
  if (fs.mkdir(path)) {
    Serial.println("Dir created");
  } else {
    Serial.println("mkdir failed");
  }
}

int readFileNum(fs::FS& fs, const char* dirname) {
  File root = fs.open(dirname);
  if (!root) {
    Serial.println("Failed to open directory");
    return -1;
  }
  if (!root.isDirectory()) {
    Serial.println("Not a directory");
    return -1;
  }

  File file = root.openNextFile();
  int num = 0;
  while (file) {
    file = root.openNextFile();
    num++;
  }
  return num;
}

void writejpg(fs::FS& fs, const char* path, const uint8_t* buf, size_t size) {
  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  file.write(buf, size);
  Serial.printf("Saved file to path: %s\r\n", path);
}

void printBuffer(byte* buf, size_t size) {
  for (size_t i = 0; i < size; i++) {
    Serial.print(buf[i], HEX);  // 16進数で表示
    Serial.print(" ");
  }
  Serial.println();
}

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("Setup Start");

  // カメラ初期化
  cameraInit();
  // カメラ画像が正常に撮れるか確認
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    // カメラ画像が取得できない場合は再起動
    ESP.restart();
  }

  // フレームバッファ解放
  esp_camera_fb_return(fb);

  // SDカード初期化
  sdmmcInit();
  createDir(SD_MMC, "/camera");

  // カメラのフレームを取得
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    // カメラ画像が取得できない場合は停止
    for (;;)
      yield();
  }
  Serial.println("Taking picture..");

  if (fb != NULL) {
    int photo_index = readFileNum(SD_MMC, "/camera");
    if (photo_index != -1) {
      Serial.println("camera shutter");

      String path = "/camera/" + String(photo_index) + ".jpg";
      // 画像保存
      writejpg(SD_MMC, path.c_str(), fb->buf, fb->len);
      Serial.println(fb->len);
      // バッファの内容を表示
      printBuffer(fb->buf, fb->len);
    }
  }
}

void loop() {
}
```

起動時にカメラ撮影を行いSDカードに画像を保存します。