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ポート
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側のスケッチ(ソースコード)を紹介します。
コメント
はじめまして。私は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」でコンパイルが止まります。
なかむらさん
記事をご覧いただき、ありがとうございます。
初心者なのに、こんな難解なコードにチャレンジされたというのには、正直驚いています。
今の私は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 を使って下さい。
ライブラリは最新バージョンでは動かない可能性が大きいです。
丁寧なご回答をありがとうございます。
説明不足で申し訳ありません。
カメラは、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;
うーん、難しいですね。
これ以上は自分の技量では追えそうにないので諦めます。
お忙しい中、ありがとうございました。
なかむらさん
以下の件、
>・ボードマネージャ 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をインストールしてみてください。
同じ問題に遭遇したものですが、自己解決しました。
新しいLovyanLGFXでは、
#include
こちらを加えると、上記のerrorが消えます。
通りすがりの尾和さん
コメント投稿ありがとうございます。
ただ、#include以降が消去されてしまいました。
半角の<>があると、セキュリティ上、それ以降は消去されてしまいますので、全角にしていただけますでしょうか?
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
#include <LGFX_AUTODETECT.hpp>
LGFX lcd;
とすればよいかと思います
通りすがりの尾和さん
再コメント、お手数おかけしました。
全角の<>は当方で半角に修正してアップしました。
LovyanGFXはしばらく使っていなかったので、大変参考になりました。
ありがとうございま~す!
m(_ _)m