esp32-cameraライブラリを使わずに、Arduino IDE でプログラミングしてみる
ようやくここまで来ました!
以上を全て踏まえて、最初に紹介した動画のように動作させるための Arduino IDE プログラミングを紹介します。
ですが、今回はesp32-cameraライブラリを読み解くと言いながら、esp32-cameraライブラリは使いません。
esp32-cameraライブラリを解体して、必要な部分を抜き出して改変します。
それ以外の Arduino core for the ESP32 の標準ライブラリを主に使っていきます。
OLED SSD1331 表示部分だけは私の自作ライブラリを使いますので、予めインストールしておいてください。
インストール方法は前回の記事を参照してください。
各バージョンは以下で動作確認しています。
Arduino IDE 1.8.9
Arduino core for the ESP32 ver 1.0.2
OLED SSD1331 用自作ライブラリ beta ver 2.0.0
ソースコードは以下の感じです。
これはあくまで esp32-cameraライブラリを読み解くために、私自身が理解し易くなるように改変したソースコードです。
全部で900行近くあるので、ちょっと大変かもしれません。
【ソースコード】 (※無保証 ※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 v1.0.2 * Use esp32_ssd1331_bv2 library beta ver 2.0.0 by mgo-tec. * * 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 <esp32_ssd1331_bv2.h> //beta ver 2.0.0 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 = 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; const uint8_t oled_SCLK = 14; //SCLK const uint8_t oled_MOSI = 13; //MOSI (Master Output Slave Input) const uint8_t oled_MISO = -1; //未使用。MISO (Master Input Slave Output) const uint8_t oled_DC = 16; //OLED DC(Data/Command) const uint8_t oled_RST = 4; //OLED Reset const uint8_t oled_CS = 15; //CS (Chip Select ピン) Esp32Ssd1331Bv2 ssd1331(oled_SCLK, oled_MISO, oled_MOSI, oled_CS, oled_DC, oled_RST); uint8_t camera_pid = 0; const uint16_t sensor_resolution_h = 400, sensor_resolution_v = 296; //CIF mode const uint16_t oled_width_pix = 96, oled_height_pix = 64; const uint16_t out_camera_w = sensor_resolution_h/4; const uint16_t out_camera_h = sensor_resolution_v/4; const uint16_t max_w_pix_buf = out_camera_w * 2; const uint16_t max_w_disp_buf = oled_width_pix * 2; uint8_t oled_pix_buf[oled_height_pix][max_w_pix_buf] = {}; boolean goDrawing_disp = false; 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); //********************************************* void setup() { ssd1331.init65kColor(); ssd1331.clearDisplay(); ssd1331.drawLine(0, 0, 95, 63, 31, 63, 31); ssd1331.drawLine(95, 0, 0, 63, 31, 63, 31); ssd1331.drawRectangleLine(0, 0, 95, 63, 31, 0, 0); delay(1000); Serial.begin(115200); delay(1000); Serial.println(); 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(1){ if(millis() - time_out > 5000) break; drawDisplay(); delay(1); } dispOffColorBar(); } void loop() { drawDisplay(); } //**************************************** void taskDMA(void *pvParameters){ Serial.println("taskDMA Start!!"); disableCore0WDT(); //ウォッチドッグ無効 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(); } timeout: vTaskDelete(task_handl); } void drawDisplay(){ if(goDrawing_disp){ for(int i = 0; i < oled_height_pix; i++){ ssd1331.drawPixel65kColorBytes(0, i, &oled_pix_buf[i][0], max_w_disp_buf); } goDrawing_disp = false; } } 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); } 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 = 2; i < dma_buf_size; i += 4) { oled_pix_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 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, 0b00001000 | 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, 0xed); 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, 0xed); 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 }
どうでしょうか?
これはやっぱりesp32-cameraライブラリをそのまま使った方が簡単ですね。
でも、Arduino core for the ESP32 のサンプルスケッチではCIFサイズより小さい画面サイズにする方法が良く分からないし、スマホ使用前提のプログラミングもあって難解です。
自分の自由にOV2640を制御するには、このようにesp32-cameraライブラリを解体して独自にプログラミングしていくしかありません。
最初は2000行越えでしたが、必要な物だけをピックアップして、自分なりにかなり改変して、何とか900行弱に抑えました。
先に紹介したことをすべて読んでいただければ分かるかと思われますので、詳細な解説は割愛させていただきます。
これをコンパイル書き込み実行させて、最初に紹介した動画のように動作すればOKです。
OV2640 カメラモジュール ( Arducam B0011 )のピント調節
実は、M5Camera も OV2640カメラモジュール (Arducam B0011)もピント調節が可能なんです。
これは、Twitterで紅樹 タカオさんによってタイムラインに流れて来て拝見した方法です。
これは、目から鱗でしたね。
自分も試してみて、下図の様に恐る恐る回してみたら、バッチリでした。
ただ、気を付けて欲しいのは、ラジオペンチを2つ使って、下の四角い黒い部分を押さえながら上の歯車っぽいパーツを慎重に回すことです。
最初は少々「バキっ」て音が鳴りますが、それ以降はスルスル回ると思います。
これは自己責任で回してくださいね。
編集後記
どうでしたでしょうか?
イメージセンサをマイコンで動かすには、こんなにも情報量が多いのかと途方にくれませんか?
私はかれこれ半年以上も費やしましたよ。
DMA や I2S、そして割り込みやSCCBなどなど、盛り沢山すぎますね。
SPIでLCDに表示させるだけの方が圧倒的に簡単ですね。
でも、実はLCD表示もDMA転送機能があるので、それを使うと表示が速くなると思います。
今回、初めてイメージセンサを扱ってDMAやI2Sを使ってみましたが、イメージセンサについてかなり知見が溜まりました。
これなら、OV2640の上位センサも使えそうな気がします。
手作りデジカメも、そう難しいものではないですね。
これでそろそろディープラーニングの勉強に入れそうな気がします。
長い道のりだったなぁ・・・。
ではまた・・・。
コメント
Hi aMiGO,
Excellent job! I appreciate it very much.
Although you don’t know much about DMA, you have come a long way.
Congratulations
Thanks
Gustavo Murta (from Brazil)
amigo (portuguese) = friend
Thanks for watching this blog post!!!