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