M5Cameraの動画をM5StackへWiFi, UDPで送信する実験

M5Cameraの動画をM5StackへWiFi,,UDP送信する実験 M5Stack

Arduino core for the ESP32 のインストール

今回、Arduino IDE のバージョンと、Arduino core for the ESP32 のバージョンは以下を使用しました。

Arduino IDE ver 1.8.10
Arduino core for the ESP32 stable ver 1.0.4

インストール方法は以下の記事を参照してください。

Arduino core for the ESP32 のインストール方法

自作ライブラリESP32_mgo_tecのインストールおよびフォントファイル設定、Timeライブラリのインストール

M5Stack側のLCDディスプレイ表示には私の自作ライブラリESP32_mgo_tecを使います。
それと、フォントファイルはmicro SDHCカードに保存しておきます。
Timeライブラリは今回不要ですが、私の自作ライブラリではTimeライブラリが無いとコンパイルエラーが出てしまうので、予めインストールしておく必要があります。
今回、自作ライブラリESP32_mgo_tecは新たにバージョンアップしました。
ですから、古いバージョンのライブラリは必ず削除してから再インストールしてください。

インストールするライブラリのバージョンやフォントファイル群は以下です。
ライブラリインストール方法やフォントファイル設定方法は以下の記事を参照してください。

M5Stack と ESP32 のボタンで記事を選択できる Yahoo News 電光掲示板 天気予報 Watch

自作ライブラリ ESP32_mgo_tec beta ver 1.0.69

今回新たにdrawPixel65kColRGB565Bytes関数を追加して、バージョンアップしました。
旧バージョンを削除してから新たにインストールしてください。

また、センサbme280やbme680を使っていない場合、インストール後にライブラリ内のsensorフォルダを削除してください。
削除しないとコンパイルエラーが出ます。

Timeライブラリ ver 1.5

※今回は使わないが、コンパイルエラーが出るのでインストールしておいてください。

micro SDHCカードに保存しておくフォントファイル

※/font/フォルダを作成して、その中に保存しておいてください。

●Utf8Sjis.tbl (UTF8→Shift_JIS変換テーブル)
●shnmk16.bdf (16×16 全角東雲フォント)
●shnm8x16.bdf  ( 半角東雲フォント。shnm8x16r.bdf のファイル名中の ’r’ 文字を削除しておく)

M5Camera側のスケッチ入力(SoftAPモード)

では、イメージセンサで取得したカメラ動画を送信する側のプログラムをArduino IDEに入力していきます。
M5CameraのESP32をSoftAPモード(自信がアクセスポイントおよびWiFiルータになるモード)にした場合のスケッチ例です。
前々回のOV2640モジュールを使ったスケッチから、SSD1331ディスプレイ表示の部分を抜いて、別途前回記事のUDP送受信プログラムを付け加えました。
そして、自動ホワイトバランス調整や露出調整プログラムを追加しました。
ついに1000行を超えてしまいましたので、覚悟しておいてください。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

/* This was modified by mgo-tec with the following esp32-camera library files:
 * The MIT License (MIT)
 * License URL: https://opensource.org/licenses/mit-license.php
 * Copyright (c) 2019 Mgo-tec. All rights reserved.
 * 
 * Use Arduino core for the ESP32 stable v1.0.4
 * 
 * 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/gpio.h>
#include <driver/i2c.h>
#include <esp_err.h>
#include <driver/ledc.h>

#include <WiFi.h>
#include <WiFiUdp.h>

const char * to_udp_address = "192.168.4.2"; //M5StackのIPアドレス
const int to_server_udp_port = 55556; //送信相手のポート番号
const int my_server_udp_port = 55555; //開放する自ポート

const char* ssid = "xxxxxxxxx"; //9文字以上。ご自分好きなSSIDに書き換えてください
const char* password = "xxxxxxxxx"; //9文字以上。ご自分好きなパスワードに書き換えてください

boolean connected = false;
WiFiUDP udp;

const uint8_t ov2640_i2c_addrs = 0x30;

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;

uint8_t camera_pid = 0;
const uint16_t sensor_resolution_h = 400, sensor_resolution_v = 296; //CIF mode
const uint16_t oled_width_pix = 200, oled_height_pix = 148;
const uint16_t out_camera_w = sensor_resolution_h/2;
const uint16_t out_camera_h = sensor_resolution_v/2;
const uint16_t max_w_pix_buf = out_camera_w * 2;
const uint16_t max_w_disp_buf = oled_width_pix * 2;
uint8_t send_udp_buf[oled_height_pix][max_w_pix_buf + 1] = {};
boolean goDrawing_disp = false;
const uint8_t fix_div = 3;

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;

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

intr_handle_t i2s_intr_handle;
uint8_t y_pix_count = 0;

esp_err_t err = ESP_OK;
TaskHandle_t task_handl;

static inline void resetI2Sconf();
static void IRAM_ATTR i2s_isr(void* arg);

uint8_t send_ok = 0;
uint8_t receive_auto_white_balance = 0;
uint8_t receive_auto_exposure_control = 0;
uint8_t receive_exposure[2] = {};
uint8_t receive_reset = 0;

//*********************************************
void setup() {
  Serial.begin(115200);
  Serial.println();
  delay(1000);

  connectToWiFi(ssid, password);
  if(connected){
    Serial.println("connected!");
  }

  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, OLED:%d x %d pix\r\n", out_camera_w, out_camera_h, oled_width_pix, oled_height_pix);

  dispOnColorBar();
  uint32_t time_out = millis();
  while(true){
    if(millis() - time_out > 5000) break;
    sendUDP();
    delay(1);
  }
  dispOffColorBar();
}

void loop() {
  sendUDP();
  receiveUDP();
  if(receive_reset == 1){
    ESP.restart();
  }
}

void receiveUDP(){
  int receive_packetSize = udp.parsePacket();
  if(receive_packetSize > 0){
    send_ok = udp.read();
    receive_auto_white_balance = udp.read();
    receive_auto_exposure_control = udp.read();
    udp.read(&receive_exposure[0], 2);
    receive_reset = udp.read();
    uint16_t exposure = (receive_exposure[0] << 8) | receive_exposure[1];
    Serial.printf("Receive UDP send_ok=%d, AWB=%d, AEC=%d\r\n", send_ok, receive_auto_white_balance, receive_auto_exposure_control);
    Serial.printf("Receive Exposure=%d\r\n", exposure);
    Serial.printf("Receive Reset=%d\r\n", receive_reset);
    changeAutoWhiteBalance(receive_auto_white_balance);
    changeAutoExposureControl(receive_auto_exposure_control, receive_exposure);
  }
  udp.flush();
}

void sendUDP(){
  if(send_ok){
    if(goDrawing_disp){
      if(connected){
        uint16_t udp_line = 0;
        udp.beginPacket(to_udp_address, to_server_udp_port);
        for(int i = 0; i < oled_height_pix; i++){
          if(i != 0 && (i % fix_div) == 0){
            udp.endPacket();

            if(errno ==12) {
              delay(3);
              break;
            }else{
              delay(1);
              delayMicroseconds(100);
            }

            udp.beginPacket(to_udp_address, to_server_udp_port);
          }
          udp.write((const uint8_t *)&send_udp_buf[i][0], max_w_disp_buf + 1);
          //Serial.printf("max_w_disp_buf=%d\r\n", max_w_disp_buf);
        }
        udp.endPacket();
        if(errno ==12) {
          delay(3);
        }else{
          delay(2);
        }
      }
      goDrawing_disp = false;
    }
  }
}
//*********************************************
void connectToWiFi(const char * ssid, const char * pwd){
  Serial.println("Connecting to WiFi network: " + String(ssid));
  WiFi.disconnect(true, true); //WiFi config設定リセット
  WiFi.softAP(ssid, password);
  IPAddress myIP = WiFi.softAPIP();
  Serial.println("WiFi connected!");
  Serial.print("My IP address: ");
  Serial.println(myIP);
  udp.begin(myIP, my_server_udp_port);
  delay(1000);
  connected = true;
}

//****************************************
void taskDMA(void *pvParameters){
  Serial.println("taskDMA Start!!");
  disableCore0WDT(); //ウォッチドッグ無効
  uint32_t last_time = millis();
  while (true) {
    while(goDrawing_disp){
      delayMicroseconds(10); //※これが無いとディスプレイに表示されない
    }

    uint32_t st_t = millis();
    while (getGpioLevel((gpio_num_t)cam_pin_VSYNC) == 0) {
      if ((millis() - st_t) > 1000000LL) {
        Serial.println("Timeout waiting for VSYNC");
        goto timeout;
      }
    }

    while (getGpioLevel((gpio_num_t)cam_pin_VSYNC) == 1) {
      ;
    }
    I2S0.conf.rx_start = 0;
    I2S0.in_link.start = 0;
    esp_intr_disable(i2s_intr_handle);

    while (getGpioLevel((gpio_num_t)cam_pin_VSYNC) == 0) {
      ;
    }

    I2S0.rx_eof_num = dma_buf_size;
    I2S0.in_link.addr = (uint32_t)dma_desc;
    I2S0.in_link.start = 1;
    I2S0.conf.rx_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);

    while(getGpioLevel((gpio_num_t)cam_pin_VSYNC) != 0){
      ; //この間、OV2640からの画素データはFIFOメモリにDMAで書き込まれる
    }

    I2S0.conf.rx_start = 0;
    I2S0.in_link.start = 0;
    esp_intr_disable(i2s_intr_handle);
    resetI2Sconf();
    
    if(millis() - last_time > 10000) {
      enableCore0WDT();
      delay(5);
      disableCore0WDT();
      last_time = millis();
    }
  }
timeout:
  vTaskDelete(task_handl);
}
//*********************************************
static void IRAM_ATTR i2s_isr(void* arg){
  I2S0.int_clr.val = I2S0.int_raw.val;
  bool need_yield = false;
  lldesc_t *d = dma_desc;
  int i, pix_cnt = 0;
  for(i = 0; i < oled_height_pix; i++){
    send_udp_buf[i][0] = i;
  }
  pix_cnt = 1;

  for (i = 2; i < dma_buf_size; i += 4) {
    send_udp_buf[y_pix_count][pix_cnt++] = (uint8_t) * (d->buf + i);
  }

  if(y_pix_count == (oled_height_pix - 1)){
    I2S0.conf.rx_start = 0;
    I2S0.in_link.start = 0;
    y_pix_count = 0;
    goDrawing_disp = true;
    return;
  }
  y_pix_count = (y_pix_count + 1) % oled_height_pix;

  if (need_yield) {
      portYIELD_FROM_ISR();
  }
}

int setFramesize(uint16_t out_width, uint16_t out_height){
  int ret = 0;
  ret = writeSCCB(0xFF, 0x00);//bank dsp
  if (!ret) {
    ret = writeSCCB(0x05, 0x00); //R_BYPASS:0x00 DSP use. 
    if(ret) return ret;
  }
  delay(5);

  uint8_t pclk_div2 = 0x01; //PCLK clock divider 2
  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_div2 << 7) | (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?

  writeSCCB(0x11, pclk_div2); //CLKRC 1/2 clock divider
  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_width / 4.0);
  uint16_t OUTH = (uint16_t)floor((double)out_height / 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_bit11 = (uint8_t)((HSIZE >> 11) & 0x0001);
  uint8_t HSIZE_bit2_0 = (uint8_t)(HSIZE & 0x0003);
  uint8_t VSIZE_bit2_0 = (uint8_t)(VSIZE & 0x0003);
  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, 0b00111101); //CTRL2
  writeSCCB(0x50, 0b10000000); //CTRLl 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(0xD3, 0b10000010); //R_DVP_SP

  writeSCCB(0xE0, 0x00); //RESET
  writeSCCB(0x05, 0x00); //R_BYPASS:0x00 DSP use.

  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
  resetI2Sconf();
  // 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_intr_alloc(ETS_I2S0_INTR_SOURCE,
                 ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM,
                 &i2s_isr, NULL, &i2s_intr_handle);
}

int getGpioLevel(gpio_num_t gpio_num){
  if (gpio_num < 32) {
    return (GPIO.in >> gpio_num) & 0x1;
  } else {
    return (GPIO.in1.data >> (gpio_num - 32)) & 0x1;
  }
}

static inline void resetI2Sconf(){
  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) {
    ;
  }
}

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

  Serial.printf("DMA buffer size: %d\r\n", dma_buf_size);

  dma_desc = (lldesc_t*) malloc(sizeof(lldesc_t));
  if (dma_desc == NULL) {
    return ESP_ERR_NO_MEM;
  }

  Serial.printf("Allocating DMA buffer size=%d\r\n", dma_buf_size);

  lldesc_t* pd = dma_desc;
  pd->length = dma_buf_size;
  pd->size = pd->length;
  pd->owner = 1;
  pd->sosf = 1;
  pd->buf = (uint8_t*) malloc(dma_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;

  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, 0b10100000); //COM7:SRST System Reset & CIF mode
    if(ret) return ret;
  }
  delay(10);
  writeRegisterCIF();
  delay(100);
  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(out_camera_w, out_camera_h) != 0) {
    Serial.println("Failed to set frame size");
    err = ESP_FAIL;
    goto fail;
  }

  writeRegisterRGB565();

  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);
  }

  initI2S();

  err = initDMAdesc();
  if (err != ESP_OK) {
    Serial.println("Failed to initialize I2S and DMA");
    goto fail;
  }

  if (!xTaskCreatePinnedToCore(&taskDMA, "taskDMA", 8192, NULL, 10, &task_handl, 0)) {
    Serial.println("Failed to create DMA filter task");
    err = ESP_ERR_NO_MEM;
    goto fail;
  }

  return ESP_OK;

fail:
  disableOutClockToCamera();
  Serial.println("ERROR camera init");
  return 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(){
  Serial.println("writeRegisterCIF");
  writeSCCB(0xff, 0x00);
  delay(5);
  writeSCCB(0x2c, 0xff);
  writeSCCB(0x2e, 0xdf);
  delay(5);
  writeSCCB(0xff, 0x01);
  delay(5);
  writeSCCB(0x3c, 0x32);
  //
  writeSCCB(0x11, 0x00); //CLKRC: none clock divide
  writeSCCB(0x09, 0x02);
  writeSCCB(0x04, 0x28);
  writeSCCB(0x13, 0b11100111); //COM8:default C7, [2]AGC 0:manual 1:auto [0]AEC(自動露出) 0:manual 1:auto
  writeSCCB(0x14, 0x48);
  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(0x36, 0x1a);
  writeSCCB(0x06, 0x88);
  writeSCCB(0x07, 0xc0);
  writeSCCB(0x0d, 0x87);
  writeSCCB(0x0e, 0x41);
  writeSCCB(0x4c, 0x00);
  writeSCCB(0x48, 0x00);
  writeSCCB(0x5B, 0x00);
  writeSCCB(0x42, 0x03);
  //
  writeSCCB(0x4a, 0x81);
  writeSCCB(0x21, 0x99);
  //
  writeSCCB(0x24, 0x40);
  writeSCCB(0x25, 0x38);
  writeSCCB(0x26, 0b10000010); //VV Fast Mode Large Step Threshold. [7:4]High threshold, [3:0]Low threshold
  writeSCCB(0x5c, 0x00);
  writeSCCB(0x63, 0x00);
  writeSCCB(0x46, 0x22);
  writeSCCB(0x0c, 0x3c);
  //
  writeSCCB(0x61, 0x70);
  writeSCCB(0x62, 0x80);
  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(0x12, 0x40); //COM7
  writeSCCB(0x17, 0x11);
  writeSCCB(0x18, 0x43);
  writeSCCB(0x19, 0x00);
  writeSCCB(0x1a, 0x4b);
  writeSCCB(0x32, 0x09);
  writeSCCB(0x37, 0xc0);
  writeSCCB(0x4f, 0xca); //BD50
  writeSCCB(0x50, 0xa8); //BD60
  writeSCCB(0x5a, 0x23);
  writeSCCB(0x6d, 0x00);
  writeSCCB(0x3d, 0x38);
  //
  delay(5);
  writeSCCB(0xff, 0x00);
  delay(5);
  writeSCCB(0xe5, 0x7f);
  writeSCCB(0xf9, 0xc0);
  writeSCCB(0x41, 0x24);
  writeSCCB(0xe0, 0x14);
  writeSCCB(0x76, 0xff);
  writeSCCB(0x33, 0xa0);
  writeSCCB(0x42, 0x20);
  writeSCCB(0x43, 0x18);
  writeSCCB(0x4c, 0x00);
  writeSCCB(0x87, 0xd5);
  writeSCCB(0x88, 0x3f);
  writeSCCB(0xd7, 0x03);
  writeSCCB(0xd9, 0x10);
  writeSCCB(0xd3, 0x82);
  //
  writeSCCB(0xc8, 0x08);
  writeSCCB(0xc9, 0x80);
  //
  writeSCCB(0x7c, 0x00);
  writeSCCB(0x7d, 0x00);
  writeSCCB(0x7c, 0x03);
  writeSCCB(0x7d, 0x48);
  writeSCCB(0x7d, 0x48);
  writeSCCB(0x7c, 0x08);
  writeSCCB(0x7d, 0x20);
  writeSCCB(0x7d, 0x10);
  writeSCCB(0x7d, 0x0e);
  //
  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(0xc3, 0b11100001); //CTRL1
  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(0xc0, 0x64);
  writeSCCB(0xc1, 0x4B);
  writeSCCB(0x8c, 0x00);
  writeSCCB(0x86, 0x3D);
  writeSCCB(0x50, 0x00); //bank00, CTRl
  writeSCCB(0x51, 0xC8);
  writeSCCB(0x52, 0x96);
  writeSCCB(0x53, 0x00);
  writeSCCB(0x54, 0x00);
  writeSCCB(0x55, 0x00);
  writeSCCB(0x5a, 0xC8); //ZMOW(Zoom Output Width) 200px
  writeSCCB(0x5b, 0x96); //ZMOH(Zoom Output Height) 150px
  writeSCCB(0x5c, 0x00);
  writeSCCB(0xd3, 0x82);
  //
  writeSCCB(0xc3, 0b11100001); //CTRL1
  writeSCCB(0x7f, 0x00);
  //
  writeSCCB(0xda, 0x08); //IMAGE_MODE RGB565
  //
  writeSCCB(0xe5, 0x1f);
  writeSCCB(0xe1, 0x67);
  writeSCCB(0xe0, 0x00);
  writeSCCB(0xdd, 0x7f);
  writeSCCB(0x05, 0x00);
}

void writeRegisterRGB565(){
  Serial.println("writeRegisterRGB565");
  writeSCCB(0xff, 0x00); //bank dsp
  writeSCCB(0xE0, 0x04); //RESET DVP
  writeSCCB(0xDA, 0x08); //IMAGE_MODE, RGB565
  writeSCCB(0xE0, 0x00); //RESET
}

void dispOnColorBar(){
  writeSCCB(0xff, 0x01); //bank sensor
  writeSCCB(0x12, 0b00100010); //COM7 CIF mode, colorbar en
}

void dispOffColorBar(){
  writeSCCB(0xff, 0x01); //bank sensor
  writeSCCB(0x12, 0b00100000); //COM7 CIF mode, colorbar en
}

void changeAutoWhiteBalance(uint8_t receive_data){
  Serial.println("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 = receive_data << 3;
  awb_gain_bit = receive_data << 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 changeAutoExposureControl(uint8_t change, uint8_t exposure[2]){
  uint8_t COM8 = 0;
  writeSCCB(0xff, 0x01);
  COM8 = readSCCB(0x13);
  Serial.print("Old COM8=");
  Serial.println(COM8, BIN);
  uint8_t change_aec = 0;
  if(change) {
    change_aec = 0b11100111;
  }else{
    change_aec = 0b11000110;
    exposureResister(exposure);
  }
  if(COM8 != change_aec){
    COM8 = change_aec;
    writeSCCB(0xff, 0x01);
    writeSCCB(0x13, COM8);
    Serial.print("New COM8=");
    Serial.println(COM8, BIN);
  }
}

void exposureResister(uint8_t exposure[2]){
  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] = {};
  uint16_t temp = 0;
  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[1];
  Serial.printf("old_exposure=%d\r\n", old_exposure);
  uint8_t new_REG45 = 0;
  uint8_t new_AEC = 0;
  uint8_t new_REG04 = 0;

  new_REG45 = (old_REG45 & 0b11000000);
  new_REG45 = new_REG45 | (exposure[0] >> 2);
  new_AEC = (exposure[0] << 6) | (exposure[1] >> 6);
  new_REG04 = (new_REG04 & 0b11111100);
  new_REG04 = new_REG04 | (exposure[1] & 0b00000011);
  writeSCCB(0xff, 0x01);
  writeSCCB(0x45, new_REG45);
  writeSCCB(0x10, new_AEC);
  writeSCCB(0x04, new_REG04);
  uint16_t new_exposure = (exposure[0] << 8) | exposure[1];
  Serial.printf("new_exposure=%d\r\n", new_exposure);
}

【簡単な解説】

前々回記事のスケッチからSSD1331表示用プログラムを削除して、前回記事のWiFi, UDP送受信プログラムを追加してちょっとだけアレンジしました。
ですから、詳細な説明は省略します。

M5CameraのESP32はSoftAPモードで動作させるので、IPアドレスは
192.168.4.1
となります。
送信先のM5StackのIPアドレスは、
192.168.4.2
となります。
ポート番号は前回記事と同じです。

●58-59行:
今回の実験のように、PSRAM無しでは送信できる画像サイズはCIFモード(400×296)で200×148 pixel が限界でした。

●66行:
この fix_div=3 という数値は、1パケットで送信できるバイト数最大値が1460byteと決められている(前回記事参照)ので、画像データを3行まで一気にudp.write関数で送信するという数値です。

●133-192行:
M5Camera のESP32のCPU core 1タスクで、WiFi, UDPを送受信しています。
M5CameraがM5StackからUDPで受信するパケットは、141-157行を見てもらえると分かると思います。
今回はM5StackのAボタンを瞬時押しすると、パケットの1byte目に
send_ok = 1
を受信して、M5Cameraから画像を連続してWiFi, UDPで送信します。
159-192行にあるように、ESP32のCPU core 1でUDP送信します。
パケット送信エラーが出ると、169行のように
errno==12
がcore関数から返ってきて、その場合にdelay(3)にしてパケットロスしないようにと考えましたが、あまり効いていないようです。

受信パケット2byte目は、自動ホワイトバランスON-OFFフラグです。
受信パケット3byte目は、自動露出調整ON-OFFフラグです。
受信パケット4~5byte目は16bitの露出時間の数値を8bitに分割して受信しています。
受信パケット6byte目はM5Cameraのリセットフラグです。

136行のように、M5StackのAボタンを長押しするとreceive_resetに値1が入って受信します。それを検知したら、
ESP.restart()
という関数で強制リセットするようにしています。

●208-264行:
これは前々回記事のスケッチと変わりありません。
M5CameraのESP32のCPU core 0タスクでイメージセンサOV2640からの画像を常時取得しています。
ここでは237行でウォッチドッグタイマ動作を無効にしているので、長時間動作し続けると問題あるかも知れません。

●967-986行:changeAutoWhiteBalance関数
M5Camera内のイメージセンサOV2640の自動ホワイトバランス調整(AWB)をON-OFFをチェンジする関数です。
M5Camera内のESP32からイメージセンサOV2640にSCCBでレジスタを叩いています。
自己流でプログラミングしてみました。

●988-1038行:changeAutoExposureControl関数
M5Camera内のイメージセンサOV2640の自動露出調整(AEC)をON-OFFしたり、露出時間を3段階で調整したりする関数です。
これも自己流でプログラミングして、SCCBでレジスタを叩いています。
1010-1038行を見てもらえると分かると思いますが、露出時間の値は16bitで複数のレジスタを跨いで設定するという、複雑なものです。
16bitの露出時間値をレジスタREG45, AEC, REG04の中で設定します。
詳しくはOV2640データシートを参照してください。

以上、ザッとしたM5Camera側のスケッチ解説でした。

次の節では、M5Stack側のスケッチを説明します。

コメント

  1. Şeyh Müslüm İncedal (TURKEY-Istanbul) より:

    I would appreciate it if you fit with ESP32 Camera and ili9341

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