LovyanGFXとJpgLoopAnimeでM5StackとM5Cameraの全画面WiFi動画ストリーミング実験

LovyanGFXとJpgLoopAnimeを使って、M5CameraとM5Stackの動画ストリーミング実験 M5Stack

M5Camera(送信側)の設定

M5Camera の設定は特別なことはありません。
基本的にESP32-DevKitC と同じ扱いでOKです。

M5Camera のスケッチ(プログラムソースコード)

M5Camera側のソースコードは、前回記事と構成は同じです。
大きく変わったのは、stream_handler関数のところです。

特に今回こだわったのは、JPEGデータ送信間隔を監視するようにして、間隔が短くなったらJPEGデータ送信を間引くようにしてみました。
ある程度効果があったのではないかと思います。

/* This is a program modified by mgo-tec from the esp32-camera library and CameraWebServer sketch.
 * 
 * The MIT License (MIT)
 * License URL: https://opensource.org/licenses/mit-license.php
 * Copyright (c) 2020 Mgo-tec. All rights reserved.
 * 
 * Use Arduino core for the ESP32 stable v1.0.4
 *  
 * Modify app_httpd.cpp(Arduino core for the ESP32 v1.0.4).
 * Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
 * app_httpd.cpp - Licensed under the Apache License, Version 2.0
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * esp32-camera library ( Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD)
 *  Licensed under the Apache License, Version 2.0 (the "License").
 *  URL:https://github.com/espressif/esp32-camera
 * 
 * sccb.c file is part of the OpenMV project. 
 *  Copyright (c) 2013/2014 Ibrahim Abdelkader.
 *  This work is licensed under the MIT license. 
 */
#include <rom/lldesc.h>
#include <driver/rtc_io.h>
#include <driver/i2s.h>
#include <driver/i2c.h>
#include <esp_err.h>
#include <driver/ledc.h>
#include <WiFi.h>
#include <esp_http_server.h>
#include <img_converters.h>

const char* ssid = "xxxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください

const uint8_t ov2640_i2c_addrs = 0x30;

//use M5Camera
const int8_t cam_pin_PWDN = -1; //power down is not used
const int8_t cam_pin_RESET = 15; //software reset will be performed
const int8_t cam_pin_XVCLK = 27;
const int8_t cam_pin_SIOD = 22;
const int8_t cam_pin_SIOC = 23;
const int8_t cam_pin_D7 = 19;
const int8_t cam_pin_D6 = 36;
const int8_t cam_pin_D5 = 18;
const int8_t cam_pin_D4 = 39;
const int8_t cam_pin_D3 = 5;
const int8_t cam_pin_D2 = 34;
const int8_t cam_pin_D1 = 35;
const int8_t cam_pin_D0 = 32;
const int8_t cam_pin_VSYNC = 25;
const int8_t cam_pin_HREF = 26;
const int8_t cam_pin_PCLK = 21;

/*
//use ESP32-DevKitC and Arducam BOO11
const int8_t cam_pin_PWDN = -1; //power down is not used
const int8_t cam_pin_RESET = 17; //software reset will be performed
const int8_t cam_pin_XVCLK = 27;
const int8_t cam_pin_SIOD = 21;
const int8_t cam_pin_SIOC = 22;
const int8_t cam_pin_D7 = 19;
const int8_t cam_pin_D6 = 36;
const int8_t cam_pin_D5 = 18;
const int8_t cam_pin_D4 = 39;
const int8_t cam_pin_D3 = 5;
const int8_t cam_pin_D2 = 34;
const int8_t cam_pin_D1 = 35;
const int8_t cam_pin_D0 = 32;
const int8_t cam_pin_VSYNC = 25;
const int8_t cam_pin_HREF = 26;
const int8_t cam_pin_PCLK = 23;
*/
uint8_t camera_pid = 0;
const uint16_t sensor_resolution_h = 400, sensor_resolution_v = 296; //CIF mode

uint16_t out_camera_w = 320;
uint16_t out_camera_h = 240;
uint16_t jpg_buf_size = out_camera_w * 2 * out_camera_h;
size_t jpg_buf_len = 0;
uint8_t *jpg_buf = NULL;

const uint8_t ledc_duty = 1; //1bit value:1 = duty 50%
const double ledc_base_freq = 20000000.0;

const uint32_t sccb_freq = 200000; // I2C master frequency
const uint8_t i2c_write_bit = 0; // I2C master write
const uint8_t i2c_read_bit = 1;  // I2C master read
const uint8_t ack_check_en = 1; // I2C master will check ack from slave
//const uint8_t ack_check_dis = 0;
//const uint8_t ack_val = 0; // I2C ack value
const uint8_t nack_val = 1; // I2C nack value
const int sccb_i2c_port = 1;
uint8_t scan_i2c_addrs = 0;

typedef enum {
    SM_0A0B_0B0C = 0,
    SM_0A0B_0C0D = 1,
    SM_0A00_0B00 = 3,
} i2s_sampling_mode_t;

lldesc_t *dma_desc;
uint16_t dma_desc_buf_size = out_camera_w * 2 * 4;

const uint8_t dma_desc_count = 4;
uint8_t dma_desc_cur = 0;
uint8_t read_desc_cur = 0;
uint32_t jpg_buf_cnt = 0;
uint8_t dma_filtered_count = 0;

intr_handle_t i2s_intr_handle;
esp_err_t err = ESP_OK;

static inline void IRAM_ATTR i2s_conf_reset();
static void IRAM_ATTR i2s_isr(void* arg);
static void IRAM_ATTR vsync_intr_enable();
static void IRAM_ATTR vsync_intr_disable();
static void IRAM_ATTR vsync_isr(void* arg);
static void IRAM_ATTR test_digitalWrite(uint8_t pin, uint8_t val);

httpd_handle_t stream_httpd = NULL;
httpd_handle_t camera_httpd = NULL;

bool isI2Sisr = false;
bool canStartStream = false;
bool canSendImage = false;
bool isWiFiConnected = false;
bool isCloseConnection = false;
bool shouldStartBus = false;
bool shouldStopBus = false;
bool isChangeFramesize = false;
bool isPassIncDescCur = false;
bool canResetDmaDesc = false;

uint32_t fps_timer = 0;
uint8_t fps_count = 0;
uint8_t frame_size_num = 5;
uint8_t jpg_quality = 10; //10:Highest quality, 63:Minimum quality
uint8_t pclk_div2 = 32; // 48MHz/pclk_div2

const char *stream_content_type = "multipart/x-mixed-replace;boundary=--myboundary";
const char *stream_boundary = "\r\n--myboundary\r\n";
const char *stream_part = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

//********CPU core 1 task********************
void setup() {
  Serial.begin(115200);
  Serial.println();
  delay(1000);
  pinMode(4, OUTPUT);
  TaskHandle_t taskServer_handl;
  xTaskCreatePinnedToCore(&taskServer, "taskServer", 8192, NULL, 20, &taskServer_handl, 0);
  while(!isWiFiConnected){
    Serial.print('.');
    delay(500);
  }
  esp_err_t err = initCamera();
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  Serial.printf("Camera:%d x %d pix, Display:%d y %d pix\r\n", out_camera_w, out_camera_h, out_camera_w, out_camera_h);
  initCameraDMA();
  changeAutoWhiteBalance(1);
  changeExposureControl(0);
}

void loop() {
  getCameraBuf();
  if(canStartStream && (millis() - fps_timer > 1000)){
    Serial.printf("%d (fps)\r\n", fps_count);
    fps_count = 0;
    fps_timer = millis();
  }
}
//********CPU core 0 task********************
void taskServer(void *pvParameters){
  connectToWiFi();
  while(!isWiFiConnected){
    delay(1);
  }
  startHttpd();
  while(true){
    delay(1);
  }
}
//****************************************
void getCameraBuf(){
  static bool isStartedBus = false;

  if(shouldStartBus){
    isStartedBus = i2s_start_bus();
    shouldStartBus = false;
  }
  if(shouldStopBus){
    i2s_stop_bus();
    shouldStopBus = false;
  }

  if(isStartedBus){
    if(isI2Sisr){
      if(dma_filtered_count == 0){
        jpg_buf_cnt = 0;
      }
      //Serial.printf("desc=%d,read=%d\r\n",dma_desc_cur,read_desc_cur);
      //FIFOから読み出し、jpg_bufへ格納
      lldesc_t* test_desc = &dma_desc[read_desc_cur];
      uint32_t cnt = 0;
      uint8_t dummy = 0;
      for(int i = 2; i < dma_desc_buf_size; i+=4) {
        if(jpg_buf_cnt >= jpg_buf_size) break;
        if(canSendImage){
          dummy = (uint8_t) * (test_desc->buf + i);
        }else{
          jpg_buf[jpg_buf_cnt++] = (uint8_t) * (test_desc->buf + i);
          cnt++;
        }
      }
      isI2Sisr = false;

      if(!canSendImage){
        if(dma_filtered_count == 0){
          //JPEG開始マーカーFFD8の検出
          uint32_t head = *((uint32_t *)jpg_buf);
          if(head == 0xE0FFD8FF){
            //FF,E0:marker JFIF形式, FF,E1:EXIF形式
            dma_filtered_count++;
          }
        }

        if(dma_filtered_count){
          //JPEG終了マーカーFFD9の検出
          int32_t cd = jpg_buf_cnt - 1;
          uint8_t *bf = &jpg_buf[jpg_buf_cnt - 1];
          uint32_t now_ptr = jpg_buf_cnt - cnt;
          while(bf >= (jpg_buf + now_ptr)){
            if(bf[0] == 0xff && bf[1] == 0xd9){
              jpg_buf_len = cd + 2;
              canSendImage = true;
              if(isChangeFramesize == true){
                setFramesize(frame_size_num);
                isChangeFramesize = false;
              }
              return;
            }
            bf--;
            cd--;
          }
          dma_filtered_count++;
        }

        if(jpg_buf_cnt >= 15000UL){ //jpg_len > 18,000bytes bad frame
          dma_filtered_count = 0;
          canResetDmaDesc = true;
          //Serial.printf("bad frame! size=%d, cnt=%d\r\n", jpg_buf_size, jpg_buf_cnt);
          if(isChangeFramesize == true){
            setFramesize(frame_size_num);
            isChangeFramesize = false;
          }
          return;
        }
      }
    }
  }
}
//****************************************
static esp_err_t stream_handler(httpd_req_t *req){
  esp_err_t res = ESP_OK;
  char part_buf[64];

  res = httpd_resp_set_type(req, stream_content_type);
  if(res != ESP_OK){
    return res;
  }
  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

  //uint32_t time_out = millis();
  uint32_t f_time = millis();
  uint32_t start_thin_out_time = 0;
  uint32_t thin_out_time = 0;
  bool isThinOut = false;
  static uint32_t threshold_thin_out_time = 20;
  while(true){
    if(isCloseConnection){
      Serial.println("Loop Out Stream!");
      break;
    }
    delay(1);

    if(canStartStream){
      if(canSendImage){
        if(millis() - start_thin_out_time > thin_out_time){
          f_time = millis();
          if(res == ESP_OK){
            fps_count++;
            res = httpd_resp_send_chunk(req, stream_boundary, strlen(stream_boundary));
          }else{
            Serial.printf("res3=%d\r\n", res);
          }
          if(res == ESP_OK){
            size_t hlen = snprintf((char *)part_buf, 64, stream_part, jpg_buf_len);
            res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
          }else{
            Serial.printf("res1=%d\r\n", res);
            continue;
          }
          if(res == ESP_OK){
            if(jpg_buf_len){
              res = httpd_resp_send_chunk(req, (const char *)&jpg_buf[0], jpg_buf_len);
            }else{
              Serial.println("Failed jpg_buf_len");
            }
          }else{
            Serial.printf("res2=%d\r\n", res);
          }

          canSendImage = false;
          canResetDmaDesc = true;
          dma_filtered_count = 0;
          res = ESP_OK;

          uint32_t tmp_time = millis() - f_time;
          if(tmp_time > threshold_thin_out_time){
            thin_out_time = tmp_time - threshold_thin_out_time;
            if(thin_out_time > 400) {
              thin_out_time = 300;
            }else if(thin_out_time > 200) {
              thin_out_time = 180;
            }
            isThinOut = true;
          }else{
            thin_out_time = 0;
            isThinOut = false;
          }
          if(isThinOut) start_thin_out_time = millis();
        }else{
          if(isThinOut){
            Serial.printf("thin_out_time=%dms\r\n", thin_out_time);
            isThinOut = false;
          }
        }
      }
    }
  }
  return res;
}
//****************************************
static esp_err_t cmd_handler(httpd_req_t *req){
  char*  buf;
  size_t buf_len;
  char id_txt[32] = {0,};
  char value_txt[32] = {0,};

  buf_len = httpd_req_get_url_query_len(req) + 1;
  if (buf_len > 1) {
    buf = (char*)malloc(buf_len);
    if(!buf){
      httpd_resp_send_500(req);
      return ESP_FAIL;
    }
    if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
      //Serial.println("-----Receive Control Command");
      //Serial.println(buf);
      if (httpd_query_key_value(buf, "var", id_txt, sizeof(id_txt)) == ESP_OK &&
        httpd_query_key_value(buf, "val", value_txt, sizeof(value_txt)) == ESP_OK) {
      } else {
        free(buf);
        httpd_resp_send_404(req);
        return ESP_FAIL;
      }
    } else {
      Serial.println(buf);
      free(buf);
      httpd_resp_send_404(req);
      return ESP_FAIL;
    }
    free(buf);
  } else {
    httpd_resp_send_404(req);
    return ESP_FAIL;
  }

  uint16_t val = atoi(value_txt);
  int res = 0;
  if(!strcmp(id_txt, "aec")) {
    changeExposureControl((uint8_t)val);
    Serial.printf("%s = %d\r\n", id_txt, val);
  }else if(!strcmp(id_txt, "awb")) {
    changeAutoWhiteBalance((uint8_t)val);
    Serial.printf("%s = %d\r\n", id_txt, val);
  }else if(!strcmp(id_txt, "quality")) {
    jpg_quality = val;
    changeQuality((uint8_t)val);
  }else if(!strcmp(id_txt, "framesize")){
    Serial.println("Change framesize");
    frame_size_num = val;
    isChangeFramesize = true;
  }else if(!strcmp(id_txt, "pclk_div")){
    pclk_div2 = val;
    changePclkDivider((uint8_t)val);
  }else if(!strcmp(id_txt, "start_stream")){
    canSendImage = false;
    canStartStream = true;
    shouldStartBus = true;
    isCloseConnection = false;
  }else if(!strcmp(id_txt, "stop_stream")){
    canStartStream = false;
    shouldStopBus = true;
    shouldStartBus = false;
    isCloseConnection = true;
  }else if(!strcmp(id_txt, "reset")){
    ESP.restart(); //ESP32強制リセット
  }else if(!strcmp(id_txt, "ping80")){
    Serial.println("---------ping receive");
  }else{
    res = -1;
  }

  if(res){
    return httpd_resp_send_500(req);
  }

  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  return httpd_resp_send(req, NULL, 0);
}
//****************************************
static esp_err_t index_handler(httpd_req_t *req){
  String html_body = "<!DOCTYPE html>\r\n";
          html_body += "<html><head>\r\n";
          html_body += "<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1'>\r\n";
          html_body += "</head><body>\r\n";
          html_body += "<img id='pic_place' style='border-style:solid; transform:scale(1, 1);'>\r\n";
          html_body += "<div>\r\n";
          html_body += "<p><button style='border-radius:25px;' onclick='startStream()'>Start Stream</button>\r\n";
          html_body += "<button style='border-radius:25px;' onclick='changeCtrlCam(\"stop_stream\",0)'>Stop Stream</button>\r\n";
          html_body += "<button style='border-radius:25px;' onclick='stopStream()'>Window Stop</button></p>\r\n";
          html_body += "<p><button style='border-radius:25px;' onclick='changeCtrlCam(\"framesize\",0)'>96 x96</button>\r\n";
          html_body += "<button style='border-radius:25px;' onclick='changeCtrlCam(\"framesize\",3)'>192 x 144</button>\r\n";
          html_body += "<button style='border-radius:25px;' onclick='changeCtrlCam(\"framesize\",4)'>240 x 176</button></p>\r\n";
          html_body += "<button style='border-radius:25px;' onclick='changeCtrlCam(\"framesize\",5)'>320 x 240</button></p>\r\n";
          html_body += "<p><button style='border-radius:25px;' onclick='changeCtrlCam(\"quality\",10)'>Qs 10</button>\r\n";
          html_body += "<button style='border-radius:25px;' onclick='changeCtrlCam(\"quality\",60)'>Qs 60</button></p>\r\n";
          html_body += "<p><button style='border-radius:25px;' onclick='changeCtrlCam(\"aec\",0)'>AEC auto</button>\r\n";
          html_body += "<button style='border-radius:25px;' onclick='changeCtrlCam(\"aec\",2)'>AEC OFF</button></p>\r\n";
          html_body += "</div>\r\n";
          html_body += "<script>\r\n";
          html_body += "var base_url = document.location.origin;\r\n";
          html_body += "var url_stream = base_url + ':81';\r\n";
          html_body += "function startStream(){\r\n";
          html_body += "var pic = document.getElementById('pic_place');\r\n";
          html_body += "pic.src = url_stream+'/stream';\r\n";
          html_body += "changeCtrlCam('start_stream',0);};\r\n";
          html_body += "function stopStream(){\r\n";
          html_body += "window.stop();};\r\n";
          html_body += "function changeCtrlCam(id_txt, value_txt){\r\n";
          html_body += "var new_url = base_url+'/control?var=';\r\n";
          html_body += "new_url += id_txt + '&';\r\n";
          html_body += "new_url += 'val=' + value_txt;\r\n";
          html_body += "fetch(new_url)\r\n";
          html_body += ".then((response) => {\r\n";
          html_body += "if(response.ok){return response.text();} \r\n";
          html_body += "else {throw new Error();}})\r\n";
          html_body += ".then((text) => console.log(text))\r\n";
          html_body += ".catch((error) => console.log(error));};\r\n";
          html_body += "</script></body></html>\r\n\r\n";

    httpd_resp_set_type(req, "text/html");
    httpd_resp_set_hdr(req, "Accept-Charset", "UTF-8");
    return httpd_resp_send(req, html_body.c_str(), html_body.length());
}
//****************************************
void startHttpd(){
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();

  httpd_uri_t index_uri = {
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = index_handler,
        .user_ctx  = NULL
    };

  httpd_uri_t cmd_uri = {
        .uri       = "/control",
        .method    = HTTP_GET,
        .handler   = cmd_handler,
        .user_ctx  = NULL
    };

  httpd_uri_t stream_uri = {
      .uri       = "/stream",
      .method    = HTTP_GET,
      .handler   = stream_handler,
      .user_ctx  = NULL
  };

  if (httpd_start(&camera_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(camera_httpd, &index_uri);
    httpd_register_uri_handler(camera_httpd, &cmd_uri);
  }

  config.server_port += 1;
  config.ctrl_port += 1;

  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &stream_uri);
  }
}
//**********************************************
static inline void IRAM_ATTR i2s_conf_reset(){
  //これを実行すると、FIFOメモリがリセットされるらしい
  const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M
                                       | I2S_AHBM_FIFO_RST_M;
  I2S0.lc_conf.val |= lc_conf_reset_flags;
  I2S0.lc_conf.val &= ~lc_conf_reset_flags;

  const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M
                                    | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M;
  I2S0.conf.val |= conf_reset_flags;
  I2S0.conf.val &= ~conf_reset_flags;
  while (I2S0.state.rx_fifo_reset_back) {
    ;
  }
}
//****************************************
static void IRAM_ATTR vsync_isr(void* arg){
  GPIO.status1_w1tc.val = GPIO.status1.val;
  GPIO.status_w1tc = GPIO.status;
  if(canResetDmaDesc){
    dma_desc_cur = 0;
    I2S0.conf.rx_start = 0;
    I2S0.in_link.start = 0;
    I2S0.int_clr.val = I2S0.int_raw.val;
    i2s_conf_reset();
    I2S0.rx_eof_num = dma_desc_buf_size;
    I2S0.in_link.addr = (uint32_t)&dma_desc[dma_desc_cur];
    I2S0.in_link.start = 1;
    I2S0.conf.rx_start = 1;
    isPassIncDescCur = true;
    canResetDmaDesc = false;
  }
}
//**********************************************
static void IRAM_ATTR vsync_intr_disable(){
  esp_intr_disable(i2s_intr_handle);
  gpio_set_intr_type((gpio_num_t)cam_pin_VSYNC, GPIO_INTR_DISABLE);
}

static void IRAM_ATTR vsync_intr_enable(){
  gpio_set_intr_type((gpio_num_t)cam_pin_VSYNC, GPIO_INTR_NEGEDGE);
}

static void IRAM_ATTR i2s_isr(void* arg){
  test_digitalWrite(4, HIGH);
  I2S0.int_clr.val = I2S0.int_raw.val;
  //vsync_isr直後のdesc_curカウントアップはスルーする
  if(isPassIncDescCur){
    isPassIncDescCur = false;
  }else{
    read_desc_cur = dma_desc_cur;
    dma_desc_cur = (dma_desc_cur + 1) % dma_desc_count;
    isI2Sisr = true;
  }
  test_digitalWrite(4, LOW);
}

static void IRAM_ATTR test_digitalWrite(uint8_t pin, uint8_t val){
  if(val) {
    if(pin < 32) {
      GPIO.out_w1ts = ((uint32_t)1 << pin);
    } else if(pin < 34) {
      GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
    }
  } else {
    if(pin < 32) {
      GPIO.out_w1tc = ((uint32_t)1 << pin);
    } else if(pin < 34) {
      GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
    }
  }
}
//****************************************
bool i2s_start_bus(){
  dma_desc_cur = 0;
  read_desc_cur = 0;
  dma_filtered_count = 0;
  canResetDmaDesc = true;

  esp_intr_disable(i2s_intr_handle);
  i2s_conf_reset();

  I2S0.rx_eof_num = dma_desc_buf_size;
  I2S0.in_link.addr = (uint32_t) &dma_desc[0];
  I2S0.in_link.start = 1;
  I2S0.int_clr.val = I2S0.int_raw.val;
  I2S0.int_ena.val = 0;
  I2S0.int_ena.in_done = 1;

  esp_intr_enable(i2s_intr_handle);
  I2S0.conf.rx_start = 1;
  vsync_intr_enable();
  return true;
}
//****************************************
void i2s_stop_bus(){
  dma_filtered_count = 0;
  read_desc_cur = 0;
  esp_intr_disable(i2s_intr_handle);
  vsync_intr_disable();
  i2s_conf_reset();
  I2S0.conf.rx_start = 0;
}
//****************************************
int setFramesize(uint8_t frame_size_num){
  if(jpg_buf){
    free(jpg_buf);
    jpg_buf = NULL;
  }
  switch(frame_size_num){
    case 0:
      out_camera_w = 96;
      out_camera_h = 96;
      break;
    case 1:
      out_camera_w = 160;
      out_camera_h = 80;
      break;
    case 2:
      out_camera_w = 160;
      out_camera_h = 120;
      break;
    case 3:
      out_camera_w = 192;
      out_camera_h = 144;
      break;
    case 4:
      out_camera_w = 240;
      out_camera_h = 176;
      break;
    case 5:
      out_camera_w = 320;
      out_camera_h = 240;
      break;
    default:
      break;
  }
  jpg_buf_size = out_camera_w * 2 * out_camera_h;
  jpg_buf = (uint8_t *)malloc(jpg_buf_size);

  Serial.println("my setFramesize() IN");
  int ret = 0;
  writeSCCB(0xFF, 0x00); //BANK:DSP
  writeSCCB(0x05, 0x01); //R_BYPASS: bit[0]:0x01 Bypass DSP, sensor out directly
  ov2640_settings_to_cif();

  //set_window-------------------------------
  ret = writeSCCB(0xFF, 0x00);//bank dsp
  if (!ret) {
    ret = writeSCCB(0x05, 0x00); //R_BYPASS:0x00 DSP use. 
    if(ret) return ret;
  }
  delay(5);

  //pclk_divは必ず0x02でなければならない
  uint8_t pclk_div = 0x02; //REG32 Common Control. PCLK frequency divide by 2(0x02=1/2, 0x03=1/4)

  uint16_t window_h_start = 137;
  uint16_t window_h_end = window_h_start + sensor_resolution_h;
  uint16_t window_v_start = 2;
  uint16_t window_v_end = window_v_start + sensor_resolution_v;
  uint8_t win_h_st_bit10_3 = (uint8_t)((window_h_start >> 3) & 0x00ff);
  uint8_t win_h_end_bit10_3 = (uint8_t)((window_h_end >> 3) & 0x00ff);
  uint8_t win_v_st_bit9_2 = (uint8_t)((window_v_start >> 2) & 0x00ff);
  uint8_t win_v_end_bit9_2 = (uint8_t)((window_v_end >> 2) & 0x00ff);
  uint8_t win_h_st_bit2_0 = (uint8_t)(window_h_start & 0x0007);
  uint8_t win_h_end_bit2_0 = (uint8_t)(window_h_end & 0x0007);
  uint8_t win_v_st_bit1_0 = (uint8_t)(window_v_start & 0x0003);
  uint8_t win_v_end_bit1_0 = (uint8_t)(window_v_end & 0x0003);

  writeSCCB(0xFF, 0x01); //BANK:sensor
  delay(5);
  //writeSCCB(0x12, 0b00100000); //COM7 [6:4]CIF mode
  writeSCCB(0x17, win_h_st_bit10_3); //HREFST(default:0x11)
  writeSCCB(0x18, win_h_end_bit10_3); //HREFEND(SVGA,CIF default:0x43)
  writeSCCB(0x19, win_v_st_bit9_2); //VSTRT
  writeSCCB(0x1A, win_v_end_bit9_2); //VEND
  writeSCCB(0x32, 0x00 | (pclk_div << 6) | (win_h_end_bit2_0 << 3) | win_h_st_bit2_0); //REG32,[7:6]10:PCLK frequency devide by 2, [5:0]0x09:CIF
  writeSCCB(0x03, 0x00 | (win_v_end_bit1_0 << 2) | win_v_st_bit1_0); //COM1 0x0A:CIF?
  delay(5);

  uint16_t HSIZE = sensor_resolution_h; //Image Horizontal Size
  uint16_t VSIZE = sensor_resolution_v; //Image Vertical Size
  uint16_t H_SIZE = HSIZE / 4;
  uint16_t V_SIZE = VSIZE / 4;

  uint8_t H_SIZE_bit7_0 = (uint8_t)(H_SIZE & 0x00ff);
  uint8_t V_SIZE_bit7_0 = (uint8_t)(V_SIZE & 0x00ff);
  uint8_t H_SIZE_bit8 = (uint8_t)((H_SIZE >> 8) & 0x0001);
  uint8_t H_SIZE_bit9 = (uint8_t)((H_SIZE >> 9) & 0x0001);
  uint8_t V_SIZE_bit8 = (uint8_t)((V_SIZE >> 8) & 0x0001);

  uint8_t zoom_speed = 0;
  uint16_t OUTW = (uint16_t)floor((double)out_camera_w / 4.0);
  uint16_t OUTH = (uint16_t)floor((double)out_camera_h / 4.0);
  uint8_t OUTW_bit7_0 = (uint8_t)(OUTW & 0x00ff);
  uint8_t OUTH_bit7_0 = (uint8_t)(OUTH & 0x00ff);
  uint8_t OUTW_bit9_8 = (uint8_t)((OUTW >> 8) & 0x0003);
  uint8_t OUTH_bit8 = (uint8_t)((OUTH >> 8) & 0x0001);

  uint8_t HSIZE_bit10_3 = (uint8_t)((HSIZE >> 3) & 0x00ff);
  uint8_t VSIZE_bit10_3 = (uint8_t)((VSIZE >> 3) & 0x00ff);
  Serial.printf("HSIZE=%d, VSIZE=%d, H_SIZE=%d, V_SIZE=%d, OUTW=%d, OUTH=%d\r\n", HSIZE, VSIZE, H_SIZE, V_SIZE, OUTW, OUTH);

  writeSCCB(0xFF, 0x00); //BANK:DSP
  delay(5);
  writeSCCB(0xE0, 0b00000100); //RESET bit[2]:DVP ※DVPはパラレルデジタルフォーマット
  delay(5);
  writeSCCB(0xC0, HSIZE_bit10_3); //HSIZE8は11bitのうちの上位8bit。これはh_pixel 400
  writeSCCB(0xC1, VSIZE_bit10_3); //VSIZE8は11bitのうちの上位8bit。これはv_pixel 296
  writeSCCB(0x8C, 0x00); //SIZEL 使い方は不明

  writeSCCB(0x86, 0x20 | 0x1d); //CTRL2 ,(CTRL2_DCW_EN | 0x1D), 0b00111101
  writeSCCB(0x50, 0x80 | 0x00); //CTRLl CTRLI_LP_DP=0x80 | 0x00, LP_DP EN (※CTRL1と混同し易いので注意)

  writeSCCB(0x51, H_SIZE_bit7_0); //H_SIZE
  writeSCCB(0x52, V_SIZE_bit7_0); //V_SIZE
  writeSCCB(0x53, 0x00); //XOFFL
  writeSCCB(0x54, 0x00); //YOFFL
  writeSCCB(0x55, 0x00 | (V_SIZE_bit8 << 7) | (H_SIZE_bit8 << 3)); //VHYX
  writeSCCB(0x57, (H_SIZE_bit9 << 7) | 0x00); //TEST
  writeSCCB(0x5A, OUTW_bit7_0); //ZMOW[7:0] (real/4)
  writeSCCB(0x5B, OUTH_bit7_0); //ZMOH[7:0] (real/4)
  writeSCCB(0x5C, 0x00 | (zoom_speed << 4) | (OUTH_bit8 << 2) | (OUTW_bit9_8)); //ZMHH

  writeSCCB(0xFF, 0x01); //BANK:sensor
  writeSCCB(0x11, 0x00); //CLKRC: bit[7]:Internal frequency doublers OFF, clk=XVCLK/(CLKRC[5:0] + 1)
  writeSCCB(0xFF, 0x00); //BANK:DSP
  //--------PCLK clock divider setting-----------------
  //※ブレットボード上では0b00100000~0b00001000が限界
  //M5Cameraの場合、4(PCLK 10MHz)以上。16(0x10)以上が安定
  writeSCCB(0xD3, pclk_div2); //R_DVP_SP: bit[7]:auto mode, bit[6:0] PCLK system_clock 48MHz/bit[6:0](YUV)
  //---------------------------------------------------
  writeSCCB(0x05, 0x00); //R_BYPASS:0x00 DSP use.
  writeSCCB(0xE0, 0x00); //RESET
  delay(10); //解像度を変更する場合、これは必要らしい
  return ret;
}
//********************************************************
void initI2S(){
  gpio_num_t pins[] = {
    (gpio_num_t)cam_pin_D7,
    (gpio_num_t)cam_pin_D6,
    (gpio_num_t)cam_pin_D5,
    (gpio_num_t)cam_pin_D4,
    (gpio_num_t)cam_pin_D3,
    (gpio_num_t)cam_pin_D2,
    (gpio_num_t)cam_pin_D1,
    (gpio_num_t)cam_pin_D0,
    (gpio_num_t)cam_pin_VSYNC,
    (gpio_num_t)cam_pin_HREF,
    (gpio_num_t)cam_pin_PCLK
  };
  gpio_config_t conf;
  conf.mode = GPIO_MODE_INPUT;
  conf.pull_up_en = GPIO_PULLUP_ENABLE;
  conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
  conf.intr_type = GPIO_INTR_DISABLE;

  for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) {
    if (rtc_gpio_is_valid_gpio(pins[i])) {
      rtc_gpio_deinit(pins[i]);
    }
    conf.pin_bit_mask = 1LL << pins[i];
    gpio_config(&conf);
  }

  gpio_matrix_in(cam_pin_D0, I2S0I_DATA_IN0_IDX, false);
  gpio_matrix_in(cam_pin_D1, I2S0I_DATA_IN1_IDX, false);
  gpio_matrix_in(cam_pin_D2, I2S0I_DATA_IN2_IDX, false);
  gpio_matrix_in(cam_pin_D3, I2S0I_DATA_IN3_IDX, false);
  gpio_matrix_in(cam_pin_D4, I2S0I_DATA_IN4_IDX, false);
  gpio_matrix_in(cam_pin_D5, I2S0I_DATA_IN5_IDX, false);
  gpio_matrix_in(cam_pin_D6, I2S0I_DATA_IN6_IDX, false);
  gpio_matrix_in(cam_pin_D7, I2S0I_DATA_IN7_IDX, false);
  gpio_matrix_in(cam_pin_VSYNC, I2S0I_V_SYNC_IDX, false);
  gpio_matrix_in(0x38, I2S0I_H_SYNC_IDX, false);
  gpio_matrix_in(cam_pin_HREF, I2S0I_H_ENABLE_IDX, false);
  gpio_matrix_in(cam_pin_PCLK, I2S0I_WS_IN_IDX, false);

  // Enable and configure I2S peripheral
  periph_module_enable(PERIPH_I2S0_MODULE);
  // Toggle some reset bits in LC_CONF register
  // Toggle some reset bits in CONF register
  i2s_conf_reset();
  // Enable slave mode (sampling clock is external)
  I2S0.conf.rx_slave_mod = 1;
  // Enable parallel mode
  I2S0.conf2.lcd_en = 1;
  // Use HSYNC/VSYNC/HREF to control sampling
  I2S0.conf2.camera_en = 1;
  // Configure clock divider
  I2S0.clkm_conf.clkm_div_a = 1;
  I2S0.clkm_conf.clkm_div_b = 0;
  I2S0.clkm_conf.clkm_div_num = 2;
  // FIFO will sink data to DMA
  I2S0.fifo_conf.dscr_en = 1;
  // FIFO configuration
  I2S0.fifo_conf.rx_fifo_mod = SM_0A00_0B00; //fifo mode = 3
  I2S0.fifo_conf.rx_fifo_mod_force_en = 1;
  I2S0.conf_chan.rx_chan_mod = 1;
  // Clear flags which are used in I2S serial mode
  I2S0.sample_rate_conf.rx_bits_mod = 0;
  I2S0.conf.rx_right_first = 0;
  I2S0.conf.rx_msb_right = 0;
  I2S0.conf.rx_msb_shift = 0;
  I2S0.conf.rx_mono = 0;
  I2S0.conf.rx_short_sync = 0;
  I2S0.timing.val = 0;
  I2S0.timing.rx_dsync_sw = 1;

  // Allocate I2S interrupt, keep it disabled
  ESP_ERROR_CHECK(esp_intr_alloc(ETS_I2S0_INTR_SOURCE,
                   ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM,
                   &i2s_isr, NULL, &i2s_intr_handle));
}

esp_err_t initDMAdesc(){
  Serial.println("initDMAdesc");
  assert(out_camera_w % 4 == 0); //幅pixelが4で割り切れるかどうかを事前判定

  Serial.printf("DMA buffer size: %d\r\n", dma_desc_buf_size);
  //dma_desc_count = 4; DMAディスクプリタ用メモリ4つ分の領域確保
  dma_desc = (lldesc_t*) malloc(sizeof(lldesc_t) * dma_desc_count);
  if (dma_desc == NULL) {
    return ESP_ERR_NO_MEM;
  }
  Serial.printf("Allocating DMA buffer size=%d\r\n", dma_desc_buf_size);

  for(int i = 0; i < dma_desc_count; ++i){
    lldesc_t* pd = &dma_desc[i];
    pd->length = dma_desc_buf_size;
    pd->size = pd->length;
    pd->owner = 1;
    pd->sosf = 1;
    pd->buf = (uint8_t*) malloc(dma_desc_buf_size);
    if (pd->buf == NULL) {
      Serial.println("pd->buf NULL");
      return ESP_ERR_NO_MEM;
    }
    pd->offset = 0;
    pd->empty = 0;
    pd->eof = 1;
    pd->qe.stqe_next = &dma_desc[(i + 1) % dma_desc_count];
  }
  Serial.printf("System Free Heap Size = %d\r\n", esp_get_free_heap_size());

  return ESP_OK;
}

int reset(){
  int ret = 0;
  ret = writeSCCB(0xFF, 0x01);//bank sensor
  if (!ret) {
    Serial.println("OV2640 System Resister Reset (COM7)");
    ret = writeSCCB(0x12, 0b10000000); //COM7:SRST System Reset & CIF mode
    if(ret) return ret;
  }
  delay(10);
  writeRegisterCIF();
  return ret;
}

esp_err_t probeCamera(){
  enableOutClockToCamera();

  sccbInit(cam_pin_SIOD, cam_pin_SIOC);

  Serial.println("Resetting camera");
  gpio_config_t conf = { 0 };
  conf.pin_bit_mask = 1LL << cam_pin_RESET;
  conf.mode = GPIO_MODE_OUTPUT;
  gpio_config(&conf);

  gpio_set_level((gpio_num_t)cam_pin_RESET, 0);
  delay(10);
  gpio_set_level((gpio_num_t)cam_pin_RESET, 1);
  delay(10);

  Serial.println("Searching for camera address");
  delay(10);
  uint8_t slv_addr = sccbProbe();

  if (slv_addr == ov2640_i2c_addrs) {
    Serial.println("Detected camera OV2640");
  }else{
    disableOutClockToCamera();
    return ESP_FAIL;
  }

  uint8_t reg_PIDH = 0x0A; //Product ID Number MSB
  uint8_t reg_PIDL = 0x0B; //Product ID Number LSB
  uint8_t reg_MIDH = 0x1C; //Manufacture ID Byte MSB
  uint8_t reg_MIDL = 0x1D; //Manufacture ID Byte LSB

  writeSCCB(0xFF, 0x01);//bank sensor

  camera_pid = readSCCB(reg_PIDH);
  uint8_t ver = readSCCB(reg_PIDL);
  uint8_t midh = readSCCB(reg_MIDH);
  uint8_t midl = readSCCB(reg_MIDL);
  delay(10);

  Serial.printf("Product ID=0x%02X\r\n", camera_pid);
  Serial.printf("Product Ver=0x%02X\r\n", ver);
  Serial.printf("Manufacture ID High=0x%02X\r\n", midh);
  Serial.printf("Manufacture ID Low=0x%02X\r\n", midl);

  if(camera_pid == 0x26){
      Serial.println("camera_model = CAMERA_OV2640");
  }else{
      disableOutClockToCamera();
      Serial.println("Detected camera not supported.");
      return ESP_FAIL;
  }

  reset();
  return ESP_OK;
}

void enableOutClockToCamera(){
  ledcSetup(LEDC_CHANNEL_0, ledc_base_freq, LEDC_TIMER_1_BIT); //40MHzにする場合、bit=1
  ledcAttachPin(cam_pin_XVCLK, LEDC_CHANNEL_0);
  ledcWrite(LEDC_CHANNEL_0, ledc_duty); //duty:50%
}

void disableOutClockToCamera(){
  periph_module_disable(PERIPH_LEDC_MODULE);
}

esp_err_t initCamera(){
  esp_err_t err = probeCamera();
  if (err != ESP_OK) {
    Serial.printf("Camera probe failed with error 0x%x", err);
    goto fail;
  }

  if (setFramesize(frame_size_num) != 0) {
    Serial.println("Failed to set frame size");
    err = ESP_FAIL;
    goto fail;
  }

  ov2640_settings_jpeg3();
  changeQuality(jpg_quality);

  writeSCCB(0xFF, 0x01);//bank sensor
  Serial.printf("Now Gain ceiling=%d\r\n", readSCCB(0x14));

  if (camera_pid == 0x26) {
    uint8_t s_value1, s_value2;
    //set AGC(Auto Gain Ceiling)
    writeSCCB(0xFF, 0x01); //Bank Sensor
    s_value1 = readSCCB(0x14); //COM9
    delay(5);
    writeSCCB(0xFF, 0x01); //readの後のwriteは必ずbank設定必要
    writeSCCB(0x14, s_value1 | 0b01000000); //COM9 AGC(Auto Gain Ceiling) 8x
    delay(5);
    //set BPC(Black Point Corrections?),WPC(White Point Corrections?), LENC(Lens Corrections?)
    writeSCCB(0xFF, 0x00); //Bank DSP
    s_value1 = readSCCB(0x87); //CTRL3
    s_value2 = readSCCB(0xC3); //CTRL1
    delay(5);
    writeSCCB(0xFF, 0x00); //Bank DSP
    writeSCCB(0x87, 0b10010000 | s_value1); //CTRL3 [7]BPC:1, [6]WPC:1
    writeSCCB(0xC3, 0b00000000 | s_value2); //CTRL1 [3]AWB, [1]LENC
    writeSCCB(0xC2, 0b10001100); //CTRL0 [7]AEC_EN
    delay(10);

    writeSCCB(0xFF, 0x01);
    Serial.printf("After set Gain ceiling=%d\r\n", readSCCB(0x14));
    writeSCCB(0xFF, 0x00); //Bank DSP
    Serial.print("After set CTRL3= ");
    Serial.println(readSCCB(0x87), BIN);
  }

  return ESP_OK;

fail:
  disableOutClockToCamera();
  Serial.println("ERROR camera init");
  return err;
}
//****************************************
void initCameraDMA(){
  Serial.println("taskDMA Start!!");
  initI2S();
  err = initDMAdesc();
  if (err != ESP_OK) {
    Serial.println("Failed to initialize I2S and DMA");
  }
  vsync_intr_disable();
  //ESP_INTR_FLAG_LEVEL1: Accept a Level 1 interrupt vector (lowest priority)
  gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM);
  err = gpio_isr_handler_add((gpio_num_t)cam_pin_VSYNC, &vsync_isr, NULL);
  if (err != ESP_OK) {
      Serial.printf("vsync_isr_handler_add failed (%x)\r\n", err);
  }
}
//***************************************
void sccbInit(int pin_sda, int pin_scl){
  i2c_config_t conf;
  conf.mode = I2C_MODE_MASTER;
  conf.sda_io_num = (gpio_num_t)pin_sda;
  conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
  conf.scl_io_num = (gpio_num_t)pin_scl;
  conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
  conf.master.clk_speed = sccb_freq;

  i2c_param_config((i2c_port_t)sccb_i2c_port, &conf);
  i2c_driver_install((i2c_port_t)sccb_i2c_port, conf.mode, 0, 0, 0);
}

uint8_t sccbProbe(){
  uint8_t slave_addr = 0x0;
  while (slave_addr < 0x7f) {
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, ( slave_addr << 1 ) | i2c_write_bit, ack_check_en);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin((i2c_port_t)sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    if ( ret == ESP_OK) {
      scan_i2c_addrs = slave_addr;
      Serial.printf("Detected Slave Address=%02X\r\n", scan_i2c_addrs);
      return scan_i2c_addrs;
    }
    slave_addr++;
  }
  return scan_i2c_addrs;
}

uint8_t readSCCB(uint8_t reg){
  uint8_t data = 0;
  esp_err_t ret = ESP_FAIL;
  i2c_cmd_handle_t cmd = i2c_cmd_link_create();
  i2c_master_start(cmd);
  i2c_master_write_byte(cmd, ( scan_i2c_addrs << 1 ) | i2c_write_bit, ack_check_en);
  i2c_master_write_byte(cmd, reg, ack_check_en);
  i2c_master_stop(cmd);
  ret = i2c_master_cmd_begin((i2c_port_t)sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
  i2c_cmd_link_delete(cmd);

  if (ret != ESP_OK) {
    Serial.println(ret);
    Serial.println("fail");
    return -1;
  }
  cmd = i2c_cmd_link_create();
  i2c_master_start(cmd);
  i2c_master_write_byte(cmd, ( scan_i2c_addrs << 1 ) | i2c_read_bit, ack_check_en);
  i2c_master_read_byte(cmd, &data, (i2c_ack_type_t)nack_val);
  i2c_master_stop(cmd);
  ret = i2c_master_cmd_begin((i2c_port_t)sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
  i2c_cmd_link_delete(cmd);
  if (ret != ESP_OK) {
    Serial.printf("readSCCB Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", scan_i2c_addrs, reg, data, ret);
  }
  return data;
}

uint8_t writeSCCB(uint8_t reg, uint8_t data){
  esp_err_t ret = ESP_FAIL;
  i2c_cmd_handle_t cmd = i2c_cmd_link_create();
  i2c_master_start(cmd);
  i2c_master_write_byte(cmd, ( scan_i2c_addrs << 1 ) | i2c_write_bit, ack_check_en);
  i2c_master_write_byte(cmd, reg, ack_check_en);
  i2c_master_write_byte(cmd, data, ack_check_en);
  i2c_master_stop(cmd);
  ret = i2c_master_cmd_begin((i2c_port_t)sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
  i2c_cmd_link_delete(cmd);
  //Serial.printf("writeSCCB ret=%d\r\n", ret);
  if (ret != ESP_OK) {
    Serial.printf("writeSCCB Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d\r\n", scan_i2c_addrs, reg, data, ret);
  }
  return ret == ESP_OK ? 0 : -1;
}

void writeRegisterCIF(){
  //esp32-camera library. ov2640_settings_cif
  Serial.println("writeRegisterCIF() esp32-camera library.");
  writeSCCB(0xff, 0x00);
  delay(5);
  writeSCCB(0x2c, 0xff);
  writeSCCB(0x2e, 0xdf);
  delay(5);
  writeSCCB(0xff, 0x01);
  delay(5);
  writeSCCB(0x3c, 0x32);
  //
  writeSCCB(0x11, 0x01); //CLKRC 1/2 clock divider
  writeSCCB(0x09, 0x02); //COM2, COM2_OUT_DRIVE_3x
  writeSCCB(0x04, 0b00101000); //REG04: bit[7]Horizontal Mirror, bit[6]Vertical Flip
  writeSCCB(0x13, 0b11100101); //COM8:default C7, [2]AGC 0:manual 1:auto [0]AEC(自動露出) 0:manual 1:auto
  writeSCCB(0x14, 0b01001000); //COM9(AGC gain ceiling),COM9_AGC_GAIN_8x
  writeSCCB(0x2c, 0x0c);
  writeSCCB(0x33, 0x78);
  writeSCCB(0x3a, 0x33);
  writeSCCB(0x3b, 0xfB);
  writeSCCB(0x3e, 0x00);
  writeSCCB(0x43, 0x11);
  writeSCCB(0x16, 0x10);
  writeSCCB(0x39, 0x92);
  writeSCCB(0x35, 0xda);
  writeSCCB(0x22, 0x1a);
  writeSCCB(0x37, 0xc3);
  writeSCCB(0x23, 0x00);
  writeSCCB(0x34, 0xc0); //ARCOM2
  writeSCCB(0x06, 0x88);
  writeSCCB(0x07, 0xc0);
  writeSCCB(0x0d, 0x87); //COM4
  writeSCCB(0x0e, 0x41);
  writeSCCB(0x4c, 0x00);
  writeSCCB(0x4a, 0x81);
  writeSCCB(0x21, 0x99);
  writeSCCB(0x24, 0x40); //AEW
  writeSCCB(0x25, 0x38); //AEB
  writeSCCB(0x26, 0b10000010); //VV Fast Mode Large Step Threshold. [7:4]High threshold, [3:0]Low threshold, (8<<4)|(2&0x0f)
  writeSCCB(0x5c, 0x00);
  writeSCCB(0x63, 0x00);
  writeSCCB(0x61, 0x70); //HISTO_LOW
  writeSCCB(0x62, 0x80); //HISTO_HIGH
  writeSCCB(0x7c, 0x05);
  writeSCCB(0x20, 0x80);
  writeSCCB(0x28, 0x30);
  writeSCCB(0x6c, 0x00);
  writeSCCB(0x6d, 0x80);
  writeSCCB(0x6e, 0x00);
  writeSCCB(0x70, 0x02);
  writeSCCB(0x71, 0x94);
  writeSCCB(0x73, 0xc1);
  //
  writeSCCB(0x3d, 0x34);
  writeSCCB(0x5a, 0x57);
  writeSCCB(0x4f, 0xbb); //BD50
  writeSCCB(0x50, 0x9c); //BD60
  writeSCCB(0x12, 0x20); //COM7, COM7_RES_CIF
  writeSCCB(0x17, 0x11); //HSTART
  writeSCCB(0x18, 0x43); //HSTOP
  writeSCCB(0x19, 0x00); //VSTART
  writeSCCB(0x1A, 0x25); //VSTOP
  writeSCCB(0x32, 0x89); //REG32
  writeSCCB(0x37, 0xc0);
  writeSCCB(0x4F, 0xca); //BD50
  writeSCCB(0x50, 0xa8); //BD60
  writeSCCB(0x6d, 0x00);
  writeSCCB(0x3d, 0x38);

  writeSCCB(0xff, 0x00); //BANK_SEL, BANK_DSP
  writeSCCB(0xe5, 0x7f);
  writeSCCB(0xF9, 0x80 | 0x40); //MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL
  writeSCCB(0x41, 0x24);
  writeSCCB(0xE0, 0x10 | 0x04); //RESET, RESET_JPEG | RESET_DVP
  writeSCCB(0x76, 0xff);
  writeSCCB(0x33, 0xa0);
  writeSCCB(0x42, 0x20);
  writeSCCB(0x43, 0x18);
  writeSCCB(0x4c, 0x00);
  writeSCCB(0x87, 0x40 | 0x10 ); //CTRL3, CTRL3_WPC_EN | 0x10
  writeSCCB(0x88, 0x3f);
  writeSCCB(0xd7, 0x03);
  writeSCCB(0xd9, 0x10);
  writeSCCB(0xD3, 0x80 | 0x02); //R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x02
  writeSCCB(0xc8, 0x08);
  writeSCCB(0xc9, 0x80);
  writeSCCB(0x7C, 0x00); //BPADDR
  writeSCCB(0x7D, 0x00); //BPDATA
  writeSCCB(0x7C, 0x03); //BPADDR
  writeSCCB(0x7D, 0x48); //BPDATA
  writeSCCB(0x7D, 0x48); //BPDATA
  writeSCCB(0x7C, 0x08); //BPADDR
  writeSCCB(0x7D, 0x20); //BPDATA
  writeSCCB(0x7D, 0x10); //BPDATA
  writeSCCB(0x7D, 0x0e); //BPDATA
  writeSCCB(0x90, 0x00);
  writeSCCB(0x91, 0x0e);
  writeSCCB(0x91, 0x1a);
  writeSCCB(0x91, 0x31);
  writeSCCB(0x91, 0x5a);
  writeSCCB(0x91, 0x69);
  writeSCCB(0x91, 0x75);
  writeSCCB(0x91, 0x7e);
  writeSCCB(0x91, 0x88);
  writeSCCB(0x91, 0x8f);
  writeSCCB(0x91, 0x96);
  writeSCCB(0x91, 0xa3);
  writeSCCB(0x91, 0xaf);
  writeSCCB(0x91, 0xc4);
  writeSCCB(0x91, 0xd7);
  writeSCCB(0x91, 0xe8);
  writeSCCB(0x91, 0x20);
  writeSCCB(0x92, 0x00);
  writeSCCB(0x93, 0x06);
  writeSCCB(0x93, 0xe3);
  writeSCCB(0x93, 0x05);
  writeSCCB(0x93, 0x05);
  writeSCCB(0x93, 0x00);
  writeSCCB(0x93, 0x04);
  writeSCCB(0x93, 0x00);
  writeSCCB(0x93, 0x00);
  writeSCCB(0x93, 0x00);
  writeSCCB(0x93, 0x00);
  writeSCCB(0x93, 0x00);
  writeSCCB(0x93, 0x00);
  writeSCCB(0x93, 0x00);
  writeSCCB(0x96, 0x00);
  writeSCCB(0x97, 0x08);
  writeSCCB(0x97, 0x19);
  writeSCCB(0x97, 0x02);
  writeSCCB(0x97, 0x0c);
  writeSCCB(0x97, 0x24);
  writeSCCB(0x97, 0x30);
  writeSCCB(0x97, 0x28);
  writeSCCB(0x97, 0x26);
  writeSCCB(0x97, 0x02);
  writeSCCB(0x97, 0x98);
  writeSCCB(0x97, 0x80);
  writeSCCB(0x97, 0x00);
  writeSCCB(0x97, 0x00);
  writeSCCB(0xa4, 0x00);
  writeSCCB(0xa8, 0x00);
  writeSCCB(0xc5, 0x11);
  writeSCCB(0xc6, 0x51);
  writeSCCB(0xbf, 0x80);
  writeSCCB(0xc7, 0x10);
  writeSCCB(0xb6, 0x66);
  writeSCCB(0xb8, 0xA5);
  writeSCCB(0xb7, 0x64);
  writeSCCB(0xb9, 0x7C);
  writeSCCB(0xb3, 0xaf);
  writeSCCB(0xb4, 0x97);
  writeSCCB(0xb5, 0xFF);
  writeSCCB(0xb0, 0xC5);
  writeSCCB(0xb1, 0x94);
  writeSCCB(0xb2, 0x0f);
  writeSCCB(0xc4, 0x5c);
  writeSCCB(0xC3, 0xfd); //CTRL1
  writeSCCB(0x7f, 0x00);
  writeSCCB(0xe5, 0x1f);
  writeSCCB(0xe1, 0x67);
  writeSCCB(0xdd, 0x7f);
  writeSCCB(0xDA, 0x00); //IMAGE_MODE
  writeSCCB(0xE0, 0x00); //RESET
  writeSCCB(0x05, 0x00); //R_BYPASS, R_BYPASS_DSP_EN
  writeSCCB(0, 0);
}

void ov2640_settings_to_cif() {
  Serial.println("ov2640_settings_to_cif() IN");
  writeSCCB(0xff, 0x01);
  writeSCCB(0x12, 0x20); //COM7=0x12,COM7_RES_CIF=0x20

  //Set the sensor output window
  writeSCCB(0x03, 0x0A); //COM1

  writeSCCB(0x32, 0x89); //REG32=0x32,REG32_CIF=0x89
  writeSCCB(0x17, 0x11); //HSTART=0x17
  writeSCCB(0x18, 0x43); //HSTOP=0x18
  writeSCCB(0x19, 0x00); //VSTART=0x19
  writeSCCB(0x1A, 0x25); //VSTOP=0x1A

  //{CLKRC, 0x00);
  writeSCCB(0x4f, 0xca); //BD50=0x4f
  writeSCCB(0x50, 0xa8); //BD60=0x50

  writeSCCB(0x5a, 0x23);
  writeSCCB(0x6d, 0x00);
  writeSCCB(0x3d, 0x38);
  writeSCCB(0x39, 0x92);
  writeSCCB(0x35, 0xda);
  writeSCCB(0x22, 0x1a);
  writeSCCB(0x37, 0xc3);
  writeSCCB(0x23, 0x00);
  writeSCCB(0x34, 0xc0); //ARCOM2=0x34
  writeSCCB(0x06, 0x88);
  writeSCCB(0x07, 0xc0);
  writeSCCB(0x0d, 0x87); //COM4=0x0d
  writeSCCB(0x0e, 0x41);
  writeSCCB(0x4c, 0x00);

  writeSCCB(0xff, 0x00);
  writeSCCB(0xE0, 0x04); //RESET=0xE0, RESET_DVP=0x04

  //Set the sensor resolution (UXGA, SVGA, CIF)
  writeSCCB(0xc0, 0x32); //HSIZE8=0xc0
  writeSCCB(0xc1, 0x25); //VSIZE8=0xc1
  writeSCCB(0x8c, 0x00); //SIZEL=0x8c

  //Set the image window size >= output size
  writeSCCB(0x51, 0x64); //HSIZE=0x51
  writeSCCB(0x52, 0x4a); //VSIZE=0x52
  writeSCCB(0x53, 0x00); //XOFFL=0x53
  writeSCCB(0x54, 0x00); //YOFFL=0x54
  writeSCCB(0x55, 0x00); //VHYX=0x55
  writeSCCB(0x57, 0x00); //TEST=0x57

  writeSCCB(0x86, 0x20 | 0x1D); //CTRL2=0x86, CTRL2_DCW_EN=0x20
  writeSCCB(0x50, 0x80 | 0x00); //CTRLI=0x50, CTRLI_LP_DP=0x80
  //{R_DVP_SP, 0x08},
  writeSCCB(0, 0);
}

void ov2640_settings_jpeg3() {
  Serial.println("writeRegisterJPEG");
  writeSCCB(0xff, 0x00); //bank dsp
  writeSCCB(0xE0, 0b00010100); //RESET JPEG DVP
  writeSCCB(0xDA, 0b00010010); //IMAGE_MODEBit[4]:jpeg output, Bit[1]:HREF=VSYNC

  writeSCCB(0xD7, 0x03);
  writeSCCB(0xE1, 0x77);
  writeSCCB(0xE5, 0x1F);
  writeSCCB(0xD9, 0x10);
  writeSCCB(0xDF, 0x80);
  writeSCCB(0x33, 0x80);
  writeSCCB(0x3C, 0x10);
  writeSCCB(0xEB, 0x30);
  writeSCCB(0xDD, 0x7F);

  writeSCCB(0xE0, 0b00000000); //RESET
  writeSCCB(0, 0);
  delay(10);
}
//****************************************
void connectToWiFi(){
  Serial.println("Connecting to WiFi network: " + String(ssid));
  WiFi.disconnect(true, true);
  delay(1000);
  WiFi.onEvent(WiFiEvent);
  WiFi.begin(ssid, password);
  Serial.println("Waiting for WIFI connection...");
}

void WiFiEvent(WiFiEvent_t event){
  switch(event) {
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.println("WiFi connected!");
      Serial.print("My IP address: ");
      Serial.println(WiFi.localIP());
      delay(1000);
      isWiFiConnected = true;
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("WiFi lost connection");
      isWiFiConnected = false;
      break;
    default:
      break;
  }
}
//****************************************
void changeAutoWhiteBalance(uint8_t val){
  Serial.printf("AWB change!");
  uint8_t CTRL1;
  writeSCCB(0xff, 0x00);
  CTRL1 = readSCCB(0xC3);
  Serial.print("Old CTRL1=");
  Serial.println(CTRL1, BIN);
  uint8_t CTRL1_bit3_2 = 0, awb_bit = 0, awb_gain_bit = 0;
  awb_bit = val << 3;
  awb_gain_bit = val << 2;
  CTRL1_bit3_2 = awb_bit | awb_gain_bit;
  uint8_t msb, lsb;
  msb = CTRL1 & 0b11110000;
  lsb = CTRL1 & 0b00000011;
  CTRL1 = (lsb | CTRL1_bit3_2) | msb;
  writeSCCB(0xff, 0x00);
  writeSCCB(0xC3, CTRL1);
  Serial.print("New CTRL1=");
  Serial.println(CTRL1, BIN);
}

void changeExposureControl(uint8_t val){
  writeSCCB(0xff, 0x01);
  uint8_t COM8 = readSCCB(0x13);
  Serial.print("Old COM8=");
  Serial.println(COM8, BIN);
  uint8_t tmp_com8 = 0;
  if(val == 0) {
    tmp_com8 = 0b11100111; //Bit[0] = 1: auto, Bit[5]=1:ON set min exp time 1/120s
  }else{
    tmp_com8 = 0b11000110; //Bit[5]=0:OFF set min exp time 1/120s
    uint16_t exposure[5] = 
    {0, 8, 64, 192, 8192};
    exposureResister(exposure[val - 1]);
  }
  if(COM8 != tmp_com8){
    COM8 = tmp_com8;
    writeSCCB(0xff, 0x01);
    writeSCCB(0x13, COM8);
    Serial.print("New COM8=");
    Serial.println(COM8, BIN);
  }
}

void exposureResister(uint16_t exp16){
  /* AEC[15:10] = REG45[5:0](0x45)
   * AEC[9:2] = AEC[9:2](0x10)
   * AEC[1:0] = REG04[1:0](0x04)
   */
  uint8_t exposure[2] = {(uint8_t)(exp16 >> 8), (uint8_t)(exp16 & 0x00ff)};
  writeSCCB(0xff, 0x01);
  uint8_t old_REG45 = readSCCB(0x45);
  uint8_t old_AEC = readSCCB(0x10);
  uint8_t old_REG04 = readSCCB(0x04);
  uint16_t old_ex[3] = {};
  old_ex[0] = (((uint16_t)old_REG45 & 0x003f)) << 10;
  old_ex[1] = ((uint16_t)old_AEC) << 2;
  old_ex[2] = ((uint16_t)old_REG04) & 0x0003;
  uint16_t old_exposure = old_ex[0] | old_ex[1] | old_ex[2];
  Serial.printf("old_exposure=%d\r\n", old_exposure);
  uint8_t new_REG45 = (old_REG45 & 0b11000000);
  new_REG45 = new_REG45 | (exposure[0] >> 2);
  uint8_t new_AEC = (exposure[0] << 6) | (exposure[1] >> 2);
  uint8_t new_REG04 = (old_REG04 & 0b11111100);
  new_REG04 = new_REG04 | (exposure[1] & 0b00000011);
  writeSCCB(0xff, 0x01);
  writeSCCB(0x45, new_REG45);
  writeSCCB(0x10, new_AEC);
  writeSCCB(0x04, new_REG04);
  Serial.printf("new_exposure=%d\r\n", exp16);
}

void changeQuality(uint8_t val){
  writeSCCB(0xff, 0x00);
  uint8_t Qs = readSCCB(0x44); //0x44 Quantization Scale Factor
  Serial.printf("Old Qs=%d\r\n", Qs);
  Serial.println(Qs);
  Qs = val;
  if(Qs > 63){
    Qs = 63;
  }else if(Qs < 10){
    Qs = 10;
  }
  writeSCCB(0x44, Qs);
  Serial.printf("New Qs=%d\r\n", Qs);
}

void changePclkDivider(uint8_t val){
  val = val + 1;
  pclk_div2 = pow(2, val);
  Serial.printf("pclk_div2 = %d\r\n", pclk_div2);
  setFramesize(frame_size_num);
}

コンパイル書き込み実行

Arduino IDE のボード設定は以下のようにします。
M5Camera は ESP32-WROVER搭載ですが、通常の Dev Module で良いです。
PSRAMは使いません。

ボード:  ESP32 Dev Module
Upload Speed:  921600
CPU Frequency:  240MHz (WiFi/BT)
Flash Frequency:  80MHz
Flash Mode:  QIO
Flash Size:  4MB (32Mb)
Partition Scheme:  Default 4MB width spiffs (1.2MB APP/1.5MB SPIFFS)
Core Debug Level:  なし
PSRAM:  Disabled
シリアルポート:  ※ご自分の M5Camera のUSBポート

 

ご自分のWiFiルーターを起動して、M5CameraをUSB接続して、この設定にしてからシリアルモニターを115200bpsで起動し、コンパイル書き込み実行します。
すると、下図の様に表示されます。

すると、上図のように M5Camera のローカルIPアドレスが表示されるので、そのアドレスで次に紹介する M5Stack側のスケッチ19行目を書き換えます。

では、次はM5Stack側のスケッチ(ソースコード)を紹介します。

コメント

  1. なかむら より:

    はじめまして。私はM5stackを楽しんでいる初心者です。
    プログラミングは苦手で、ほとんどはサンプルを少し弄る
    程度です。

    貴サイトの動画表示記事に興味があり、TimwerCameraFとM5stack(Basic)で
    チャレンジしています。
    ですが、ソースコードをarduino1.8.19にてコンパイルしているのですが
    下記のエラーになります。

    >exit status 1
    >’LGFX’ does not name a type

    エラー個所は
    >15:static LGFX lcd;
    です。

    ソースファイルと3つのヘッダファイルは同じフォルダに置いてます。

    何か原因※ありますでしょうか?。
    アドバイスを頂ければ幸いです。

    ※arduino2.0 IDEは、「ping timeout」でコンパイルが止まります。

    • mgo-tec mgo-tec より:

      なかむらさん

      記事をご覧いただき、ありがとうございます。

      初心者なのに、こんな難解なコードにチャレンジされたというのには、正直驚いています。

      今の私はESP32やM5Stackから離れて、まったく触っていませんので、かなり忘れてしまいました。
      ですから、あまり的確なアドバイスできないかも知れません。

      まず、TimwerCameraF とは、
      ESP32 PSRAM Timer Camera F (OV3660)
      のことでしょうか?
      ご存知かと思いますが、この記事はM5Camera(OV2640)を使っている為、OV3660で動くかどうかは私には分かりません。

      また、ご使用の環境が不明なことが多いので、
      Arduino core for the ESP32 のバージョンや、M5Stackライブラリ、LovyanGFXライブラリのバージョンはどれをお使いでしょうか?

      Arduino core for the ESP32は、1.0.4 で検証していますので、それを使って下さい。
      また、M5Stackライブラリのバージョンも同様に、0.3.0 を使って下さい。
      LovyanGFXライブラリも同様に 0.1.15 を使って下さい。

      ライブラリは最新バージョンでは動かない可能性が大きいです。

  2. なかむら より:

    丁寧なご回答をありがとうございます。
    説明不足で申し訳ありません。
    カメラは、ESP32 PSRAM Timer Camera F (OV3660)です。
    ご指摘の通りセンサーが異なるので、動作しなくても仕方ないです。
    ※ブラウザでは表示できていますが。

    ライブラリのバージョンは気にしていませんでした。

    ・M5Stackのライブラリは0.3.0でした。
    ・ボードマネージャ Arduino core for the ESP32
     1.0.4→これはちょっとわかりませんでした。
     
    ・LovyanLGFXのライブラリを1.1.12(最新)→0.1.15 に変更したところ
     先のエラーは消えました。
     が、新たなエラーになりました。

    >cannot declare ‘::main’ to be a global variable

    >16 static MainClass main;

    >C:\Users\chuhy\Documents\Arduino\TImerCameraF-viewer\TImerCameraF-viewer.ino: >At global scope:
    >TImerCameraF-viewer:16:18: error: cannot declare ‘::main’ to be a global >>>>variable static MainClass main;

    うーん、難しいですね。
    これ以上は自分の技量では追えそうにないので諦めます。
    お忙しい中、ありがとうございました。

    • mgo-tec mgo-tec より:

      なかむらさん

      以下の件、
      >・ボードマネージャ Arduino core for the ESP32
      >1.0.4→これはちょっとわかりませんでした。

      以下のサイトを参照してください。
      Arduino core ESP32 インストール方法

      Arduino IDE の環境設定で、「追加のボードマネージャのURL」という欄にURLを貼り付けて、「OK」をクリックします。
      そして、ボードマネージャの検索欄に「esp32」と入力すれば、「esp32 by Espressif Systems」という項目が見えます。
      その「バージョンを選択」で 1.0.4 を選択してインストールし直してみて下さい。
      うまくいかなかったら、今のバージョンを一度「削除」ボタンを押して削除してから、Arduino IDEを閉じて、再起動してから、1.0.4をインストールしてみてください。

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