概要
ESP32-WROVER-Bを使ってOV2640からjpegデータを取得してみました。ESP32-WROVER-Bでサーバーを立てて、ブラウザから各レジスタを制御できるようにしています。
コードは下記に公開しています。
https://github.com/koki-iwaizumi/esp32-ov2640-stream/tree/v1.0
OV7725を使って同様のことをしています。
https://qiita.com/koki-iwaizumi/items/146b8b08f9146327f657
公式
https://github.com/espressif/esp32-camera
設定
esp-idfのフォルダ内でsubmoduleをクローンします。
git submodule add https://github.com/espressif/esp32-camera.git components/esp32-camera
mv esp-idf/components/esp32-camera/driver/private_include/sccb.h esp-idf/components/esp32-camera/driver/include/sccb.h
mv esp-idf/components/esp32-camera/driver/private_include/sensor.h esp-idf/components/esp32-camera/driver/include/sensor.h
ESP32(サーバー)
ESP32でサーバーを立てて、ローカルアドレスでアクセスできるようにしました。ブラウザで<ローカルアドレス>/へアクセスすると画像及び各レジスタの値が表示されます。
image,config0,config1,writeはajax及びXMLHttpRequestでサーバと通信しています。
- <ローカルアドレス>/ html
- <ローカルアドレス>/image image/jpegのバイナリデータ
- <ローカルアドレス>/config0 0xFF==0の場合のレジスタ情報のjson
- <ローカルアドレス>/config1 0xFF==1の場合のレジスタ情報のjson
- <ローカルアドレス>/write レジスタ変更
# include <string.h>
# include <esp_wifi.h>
# include <esp_event_loop.h>
# include <esp_log.h>
# include <esp_system.h>
# include <nvs_flash.h>
# include <sys/param.h>
# include <esp_http_server.h>
# include <esp_camera.h>
# include "mbedtls/base64.h"
# include "cJSON.h"
# include "sensor.h"
# include "sccb.h"
/********* io ***********/
# define CAM_PIN_PWDN 2
# define CAM_PIN_RESET 15
# define CAM_PIN_XCLK 27
# define CAM_PIN_SIOD 25
# define CAM_PIN_SIOC 23
# define CAM_PIN_VSYNC 22
# define CAM_PIN_HREF 26
# define CAM_PIN_PCLK 21
# define CAM_PIN_D9 19
# define CAM_PIN_D8 36
# define CAM_PIN_D7 18
# define CAM_PIN_D6 39
# define CAM_PIN_D5 5
# define CAM_PIN_D4 34
# define CAM_PIN_D3 32
# define CAM_PIN_D2 35
/********* wifi ***********/
# define EXAMPLE_WIFI_SSID "*"
# define EXAMPLE_WIFI_PASS "*"
static const char *TAG = "APP";
sensor_t* sensor = NULL;
static const uint8_t regs_0[][1] = {
{0x05},
{0x44},
{0x50},
{0x51},
{0x52},
{0x53},
{0x54},
{0x55},
{0x56},
{0x57},
{0x5A},
{0x5B},
{0x5C},
{0x7C},
{0x7D},
{0x86},
{0x87},
{0x8C},
{0xC0},
{0xC1},
{0xC2},
{0xC3},
{0xD3},
{0xDA},
{0xE0},
{0xED},
{0xF0},
{0xF7},
{0xF8},
{0xF9},
{0xFA},
{0xFB},
{0xFC},
{0xFD},
{0xFE},
{0xFF}
};
static const uint8_t regs_1[][1] = {
{0x00},
{0x03},
{0x04},
{0x08},
{0x09},
{0x0A},
{0x0B},
{0x0C},
{0x10},
{0x11},
{0x12},
{0x13},
{0x14},
{0x15},
{0x17},
{0x18},
{0x19},
{0x1A},
{0x1C},
{0x1D},
{0x24},
{0x25},
{0x26},
{0x2A},
{0x2B},
{0x2D},
{0x2E},
{0x2F},
{0x32},
{0x34},
{0x45},
{0x46},
{0x47},
{0x48},
{0x49},
{0x4B},
{0x4E},
{0x4F},
{0x50},
{0x5D},
{0x5E},
{0x5F},
{0x60},
{0x61},
{0x62}
};
static camera_config_t camera_config = {
.pin_pwdn = CAM_PIN_PWDN,
.pin_reset = CAM_PIN_RESET,
.pin_xclk = CAM_PIN_XCLK,
.pin_sscb_sda = CAM_PIN_SIOD,
.pin_sscb_scl = CAM_PIN_SIOC,
.pin_d7 = CAM_PIN_D9,
.pin_d6 = CAM_PIN_D8,
.pin_d5 = CAM_PIN_D7,
.pin_d4 = CAM_PIN_D6,
.pin_d3 = CAM_PIN_D5,
.pin_d2 = CAM_PIN_D4,
.pin_d1 = CAM_PIN_D3,
.pin_d0 = CAM_PIN_D2,
.pin_vsync = CAM_PIN_VSYNC,
.pin_href = CAM_PIN_HREF,
.pin_pclk = CAM_PIN_PCLK,
.xclk_freq_hz = 6000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_SVGA, //FRAMESIZE_UXGA
.jpeg_quality = 12,
.fb_count = 1
};
esp_err_t index_get_handler(httpd_req_t *req)
{
extern const unsigned char index_start[] asm("_binary_index_html_start");
extern const unsigned char index_end[] asm("_binary_index_html_end");
const size_t index_size = (index_end - index_start);
httpd_resp_send_chunk(req, (const char *)index_start, index_size);
httpd_resp_sendstr_chunk(req, NULL);
return ESP_OK;
}
esp_err_t image_get_handler(httpd_req_t *req)
{
camera_fb_t *fb = esp_camera_fb_get();
if( ! fb){
ESP_LOGE(TAG, "Camera capture failed");
httpd_resp_send_500(req);
return ESP_FAIL;
}
esp_err_t res = httpd_resp_set_type(req, "image/jpeg") || httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*") || httpd_resp_send(req, (const char *)fb->buf, fb->len);
esp_camera_fb_return(fb);
return res;
}
esp_err_t config0_get_handler(httpd_req_t *req)
{
esp_err_t res = SCCB_Write(sensor->slv_addr, 0xFF, 0);
if(res != ESP_OK){
ESP_LOGE(TAG, "Failed SCCB_Write - 0xFF 0");
httpd_resp_send_500(req);
return ESP_FAIL;
}
int data_json_size = 10 * 1000;
char *data_json = calloc(data_json_size, sizeof(char));
if(data_json == NULL){
ESP_LOGE(TAG, "Failed to allocate frame buffer - data_json");
httpd_resp_send_500(req);
return ESP_FAIL;
}
int regs_0_num = sizeof regs_0 / sizeof regs_0[0];
strcat(data_json, "{");
for(int i = 0; i < regs_0_num; i++){
char data_json_buf[255] = {'\0'};
sprintf(data_json_buf, "\"%d\":\"%d\"", regs_0[i][0], SCCB_Read(sensor->slv_addr, regs_0[i][0]));
strcat(data_json, data_json_buf);
if(i != regs_0_num - 1) strcat(data_json, ",");
}
strcat(data_json, "}");
ESP_LOGI(TAG, "data_json=%s", data_json);
ESP_LOGI(TAG, "data_json length=%d", strlen((const char*)data_json));
res = httpd_resp_set_type(req, "application/json") || httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*") || httpd_resp_send(req, (const char *)data_json, strlen((const char*)data_json));
free(data_json);
return res;
}
esp_err_t config1_get_handler(httpd_req_t *req)
{
esp_err_t res = SCCB_Write(sensor->slv_addr, 0xFF, 1);
if(res != ESP_OK){
ESP_LOGE(TAG, "Failed SCCB_Write - 0xFF 1");
httpd_resp_send_500(req);
return ESP_FAIL;
}
int data_json_size = 10 * 1000;
char *data_json = calloc(data_json_size, sizeof(char));
if(data_json == NULL){
ESP_LOGE(TAG, "Failed to allocate frame buffer - data_json");
httpd_resp_send_500(req);
return ESP_FAIL;
}
int regs_1_num = sizeof regs_1 / sizeof regs_1[0];
strcat(data_json, "{");
for(int i = 0; i < regs_1_num; i++){
char data_json_buf[255] = {'\0'};
sprintf(data_json_buf, "\"%d\":\"%d\"", regs_1[i][0], SCCB_Read(sensor->slv_addr, regs_1[i][0]));
strcat(data_json, data_json_buf);
if(i != regs_1_num - 1) strcat(data_json, ",");
}
strcat(data_json, "}");
ESP_LOGI(TAG, "data_json=%s", data_json);
ESP_LOGI(TAG, "data_json length=%d", strlen((const char*)data_json));
res = httpd_resp_set_type(req, "application/json") || httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*") || httpd_resp_send(req, (const char *)data_json, strlen((const char*)data_json));
free(data_json);
return res;
}
esp_err_t write_get_handler(httpd_req_t *req)
{
size_t buf_len = httpd_req_get_url_query_len(req) + 1;
if(buf_len <= 1){
ESP_LOGE(TAG, "Failed buf_len");
httpd_resp_send_500(req);
return ESP_FAIL;
}
char* buf = malloc(buf_len);
if(httpd_req_get_url_query_str(req, buf, buf_len) != ESP_OK){
free(buf);
ESP_LOGE(TAG, "Failed httpd_req_get_url_query_str");
httpd_resp_send_500(req);
return ESP_FAIL;
}
char bank[4], key[4], value[4];
ESP_LOGI(TAG, "Found URL query => %s", buf);
if(httpd_query_key_value(buf, "bank", bank, sizeof(bank)) != ESP_OK || httpd_query_key_value(buf, "key", key, sizeof(key)) != ESP_OK || httpd_query_key_value(buf, "value", value, sizeof(value)) != ESP_OK){
free(buf);
ESP_LOGE(TAG, "Failed httpd_query_key_value");
httpd_resp_send_500(req);
return ESP_FAIL;
}
free(buf);
int key_i = atoi(key);
int value_i = atoi(value);
int bank_i = atoi(bank);
ESP_LOGI(TAG, "Found URL query parameter => key=%s, value=%s, bank=%s", key, value, bank);
ESP_LOGI(TAG, "Found URL query parameter => key=%d, value=%d, bank=%d", key_i, value_i, bank_i);
if(SCCB_Write(sensor->slv_addr, 0xFF, bank_i) != ESP_OK || SCCB_Write(sensor->slv_addr, key_i, value_i) != ESP_OK){
ESP_LOGE(TAG, "Failed SCCB_Write");
httpd_resp_send_500(req);
return ESP_FAIL;
}
const char *resp = "{\"status\":\"success\"}";
esp_err_t res = httpd_resp_set_type(req, "application/json") || httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*") || httpd_resp_send(req, resp, strlen(resp));
return res;
}
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = index_get_handler,
};
httpd_uri_t image_uri = {
.uri = "/image",
.method = HTTP_GET,
.handler = image_get_handler,
};
httpd_uri_t config0_uri = {
.uri = "/config0",
.method = HTTP_GET,
.handler = config0_get_handler,
};
httpd_uri_t config1_uri = {
.uri = "/config1",
.method = HTTP_GET,
.handler = config1_get_handler,
};
httpd_uri_t write_uri = {
.uri = "/write",
.method = HTTP_GET,
.handler = write_get_handler,
};
httpd_handle_t start_webserver(void)
{
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if(httpd_start(&server, &config) == ESP_OK){
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(server, &index_uri);
httpd_register_uri_handler(server, &image_uri);
httpd_register_uri_handler(server, &config0_uri);
httpd_register_uri_handler(server, &config1_uri);
httpd_register_uri_handler(server, &write_uri);
return server;
}
ESP_LOGI(TAG, "Error starting server!");
return NULL;
}
void stop_webserver(httpd_handle_t server)
{
httpd_stop(server);
}
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
httpd_handle_t *server = (httpd_handle_t *) ctx;
switch(event->event_id){
case SYSTEM_EVENT_STA_START:
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
ESP_ERROR_CHECK(esp_wifi_connect());
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
ESP_LOGI(TAG, "Got IP: '%s'", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
if(*server == NULL){
*server = start_webserver();
}
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
ESP_ERROR_CHECK(esp_wifi_connect());
if(*server){
stop_webserver(*server);
*server = NULL;
}
break;
default:
break;
}
return ESP_OK;
}
static void initialise_wifi(void *arg)
{
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, arg));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_WIFI_SSID,
.password = EXAMPLE_WIFI_PASS,
},
};
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
}
void app_main()
{
static httpd_handle_t server = NULL;
ESP_ERROR_CHECK(nvs_flash_init());
esp_err_t err = esp_camera_init(&camera_config);
if(err != ESP_OK){
ESP_LOGE(TAG, "Camera Init Failed");
return;
}
sensor = esp_camera_sensor_get();
vTaskDelay(1000 / portTICK_RATE_MS);
initialise_wifi(&server);
}
ブラウザ側
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.4/css/all.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>OV2640</title>
<style>
.alert{
display:none;
}
#overlay{
position: fixed;
top: 0;
z-index: 100;
width: 100%;
height:100%;
display: none;
background: rgba(0,0,0,0.6);
}
.cv-spinner{
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.spinner{
width: 40px;
height: 40px;
border: 4px #ddd solid;
border-top: 4px #2e93e6 solid;
border-radius: 50%;
animation: sp-anime 0.8s infinite linear;
}
@keyframes sp-anime{
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.is-hide{
display:none;
}
.ov_a{
height:600px;
overflow-y:auto;
}
</style>
</head>
<body>
<div id="overlay">
<div class="cv-spinner">
<span class="spinner"></span>
</div>
</div>
<div class="text-center mt-3 mb-3">
<h1>OV2640</h1>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-lg-6 text-center mb-4">
<div class="row">
<div class="col-md-12">
<div class="alert alert-success" role="alert"></div>
<div class="alert alert-danger" role="alert"></div>
</div>
<div class="col-md-12">
<img id="img" class="mw-100">
</div>
</div>
</div>
<div class="col-lg-6">
<div class="ov_a">
<div class="text-center mt-1 mb-1">
<span>0xFF=00</span>
</div>
<table id="table_regs0" class="table table-striped">
<tr>
<th>Address(Hex)</th>
<th>Name</th>
<th>Value(Hex)</th>
<th></th>
</tr>
</table>
<div class="text-center mt-1 mb-1">
<span>0xFF=01</span>
</div>
<table id="table_regs1" class="table table-striped">
<tr>
<th>Address(Hex)</th>
<th>Name</th>
<th>Value(Hex)</th>
<th></th>
</tr>
</table>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
var regs0 = {
0x05: "R_BYPASS",
0x44: "Qs",
0x50: "CTRLI",
0x51: "HSIZE",
0x52: "VSIZE",
0x53: "XOFFL",
0x54: "YOFFL",
0x55: "VHYX",
0x56: "DPRP",
0x57: "TEST",
0x5A: "ZMOW",
0x5B: "ZMOH",
0x5C: "ZMHH",
0x7C: "BRADDR",
0x7D: "BPDATA",
0x86: "CTRL2",
0x87: "CTRL3",
0x8C: "SIZEL",
0xC0: "HSIZE8",
0xC1: "VSIZE8",
0xC2: "CTRL0",
0xC3: "CTRL1",
0xD3: "R_DVP_SP",
0xDA: "IMAGE_MODE",
0xE0: "RESET",
0xED: "REGED",
0xF0: "MS_SP",
0xF7: "SS_ID",
0xF8: "SS_CTRL",
0xF9: "MC_BIST",
0xFA: "MC_AL",
0xFB: "MC_AH",
0xFC: "MC_D",
0xFD: "P_CMD",
0xFE: "P_STATUS",
0xFF: "RA_DLMT",
};
var regs1 = {
0x0: "GAIN",
0x3: "COM1",
0x4: "REG04",
0x8: "REG08",
0x9: "COM2",
0xA: "PIDH",
0xB: "PIDL",
0xC: "COM3",
0xC: "COM3",
0x10: "AEC",
0x11: "CLKRC",
0x12: "COM7",
0x13: "COM8",
0x14: "COM9",
0x15: "COM10",
0x17: "HREFST",
0x18: "HREFEND",
0x19: "VSTART",
0x1A: "VEND",
0x1C: "MIDH",
0x1D: "MIDL",
0x24: "AEW",
0x25: "AEB",
0x26: "VV",
0x2A: "REG2A",
0x2B: "FRARL",
0x2D: "ADDVSL",
0x2E: "ADDVSH",
0x2E: "ADDVSH",
0x2F: "YAVG",
0x32: "REG32",
0x34: "ARCOM2",
0x45: "REG45",
0x46: "FLL",
0x47: "FLH",
0x48: "COM19",
0x49: "ZOOMS",
0x4B: "COM22",
0x4E: "COM25",
0x4F: "BD50",
0x50: "BD60",
0x5D: "REG5D",
0x5E: "REG5E",
0x5F: "REG5F",
0x60: "REG60",
0x61: "HISTO_LOW",
0x62: "HISTO_HIGH",
};
function get_image(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(this.readyState == 4 && this.status == 200){
var img = document.getElementById("img");
img.src = window.URL.createObjectURL(this.response);
get_image();
}
}
xhr.open('GET', "./image");
xhr.responseType = 'blob';
xhr.send();
};
function write_config(bank, k, v){
$.ajax({
url: "./write",
type: "GET",
dataType: "json",
data: {
bank: bank,
key: k,
value: v
},
beforeSend: function() {
$(".alert").css("display", "none");
}
})
.done((data) => {
$.each(data, function(i, item){
if(item == "success"){
$(".alert-success").css("display", "block");
$(".alert-success").text('bank:' + bank + ' key:0x' + Number(k).toString(16) + ' value:0x' + Number(v).toString(16) + ' success');
}else{
$(".alert-danger").css("display", "block");
$(".alert-danger").text('bank:' + bank + ' key:0x' + Number(k).toString(16) + ' value:0x' + Number(v).toString(16) + ' failed');
}
});
})
.fail((data) => {
$(".alert-danger").css("display", "block");
$(".alert-danger").text('bank:' + bank + ' key:0x' + Number(k).toString(16) + ' value:0x' + Number(v).toString(16) + ' failed');
});
};
$(document).ready(function(){
$.ajax({
url: "./config0",
type: "GET",
dataType: "json",
async: false,
})
.done((data) => {
$.each(data, function(i, item){
$("#table_regs0").append('<tr><td>0x' + Number(i).toString(16) + '</td><td>' + regs0[i] + '</td><td id="value_0_' + i + '">0x' + Number(item).toString(16) + '(0b' + Number(item).toString(2) + ')</td><td><input type="range" class="w-100 slider0" name="' + i + '" value="' + item + '" min="0" max="255" step="1"></td></tr>');
});
});
$.ajax({
url: "./config1",
type: "GET",
dataType: "json",
async: false,
})
.done((data) => {
$.each(data, function(i, item){
$("#table_regs1").append('<tr><td>0x' + Number(i).toString(16) + '</td><td>' + regs1[i] + '</td><td id="value_1_' + i + '">0x' + Number(item).toString(16) + '(0b' + Number(item).toString(2) + ')</td><td><input type="range" class="w-100 slider1" name="' + i + '" value="' + item + '" min="0" max="255" step="1"></td></tr>');
});
});
get_image();
$(".slider0").on("input change", function(){
$("#value_0_" + $(this).attr("name")).text("0x" + Number($(this).val()).toString(16) + "(0b" + Number($(this).val()).toString(2) + ")");
});
$(".slider1").on("input change", function(){
$("#value_1_" + $(this).attr("name")).text("0x" + Number($(this).val()).toString(16) + "(0b" + Number($(this).val()).toString(2) + ")");
});
$(".slider0").on("change", function(){
write_config(0, $(this).attr("name"), $(this).val())
});
$(".slider1").on("change", function(){
write_config(1, $(this).attr("name"), $(this).val())
});
});
});
</script>
</body>
</html>
これから
・50Hzのバンドフィルターが上手く動作していないため、原因を探る
・色彩が少し淡いので、もう少しはっきりさせたい
・Qsの限界値を探る
・暗闇時の赤外線を当てた場合