【搬运】使用 OV2640 JPEG 输出的 M5Camera 和 M5Stack WiFi 视频流...
M5Camera 图像传感器 OV2640 我已处理了很长时间,能够检测 JPEG 输出,使用 DMA 提取图像,并将 WiFi 传输到 M5Stack,从而提供高帧速率的 Motion JPEG (MJPEG) 视频流。此外,我能够实时切换帧大小和 JPEG 图像质量。当然,曝光也可以实时切换。视角比以前的 200 x 148 像素更大,现在高达 240 x 176 像素。没有 PSRAM,闪存设置仍然默认。有一个亮点是您可以实时更改帧大小(视角),不是通过 M5Stack LCD 控制,而是通过控制 OV2640 来确定 JPEG 帧大小。M5Camera代码/* 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 = 240;
uint16_t out_camera_h = 176;
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 isStartedBus = 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 quality = 10;
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);
//jpg_buf = (uint8_t *)ps_malloc(sizeof(uint8_t) * jpg_buf_size);
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(){
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;
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 = (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;
uint32_t now_ptr = jpg_buf_cnt - cnt;
while(bf >= (jpg_buf + now_ptr)){
if(bf == 0xff && bf == 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 >= jpg_buf_size){
dma_filtered_count = 0;
canResetDmaDesc = true;
Serial.println("bad frame!");
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;
res = httpd_resp_set_type(req, stream_content_type);
if(res != ESP_OK){
return res;
}
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
esp_err_t res1 = ESP_FAIL, res2 = ESP_FAIL, res3 = ESP_OK;
uint32_t time_out = millis();
while(true){
if(canStartStream){
if(canSendImage){
if(res3 == ESP_OK){
res1 = httpd_resp_send_chunk(req, stream_boundary, strlen(stream_boundary));
time_out = millis();
res3 = ESP_FAIL;
}
if(res1 == ESP_OK){
size_t hlen = snprintf((char *)part_buf, 64, stream_part, jpg_buf_len);
res2 = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
time_out = millis();
res1 = ESP_FAIL;
}
if(res2 == ESP_OK){
res3 = httpd_resp_send_chunk(req, (const char *)&jpg_buf, jpg_buf_len);
time_out = millis();
canSendImage = false;
dma_filtered_count = 0;
canResetDmaDesc = true;
fps_count++;
res2 = ESP_FAIL;
}
}
}
if(millis() - time_out > 5000){
canSendImage = false;
canResetDmaDesc = true;
time_out = millis();
}
if(isCloseConnection){
Serial.println("Loop Out Stream!");
break;
}
delay(1);
}
return res;
}
//****************************************
static esp_err_t cmd_handler(httpd_req_t *req){
char*buf;
size_t buf_len;
char id_txt = {0,};
char value_txt = {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;
}
uint8_t val = atoi(value_txt);
int res = 0;
if(!strcmp(id_txt, "aec")) {
changeExposureControl(val);
Serial.printf("%s = %d\r\n", id_txt, val);
}else if(!strcmp(id_txt, "awb")) {
changeAutoWhiteBalance(val);
Serial.printf("%s = %d\r\n", id_txt, val);
}else if(!strcmp(id_txt, "quality")) {
quality = val;
changeQuality(val);
}else if(!strcmp(id_txt, "framesize")){
frame_size_num = val;
isChangeFramesize = true;
}else if(!strcmp(id_txt, "pclk_div")){
pclk_div2 = val;
changePclkDivider(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)'>160 x 120</button>\r\n";
html_body += "<button style='border-radius:25px;' onclick='changeCtrlCam(\"framesize\",5)'>240 x 176</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;
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;
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 = 80;
out_camera_h = 160;
break;
case 2:
out_camera_w = 160;
out_camera_h = 80;
break;
case 3:
out_camera_w = 160;
out_camera_h = 120;
break;
case 4:
out_camera_w = 192;
out_camera_h = 144;
break;
case 5:
out_camera_w = 240;
out_camera_h = 176;
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: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);
//これは必ず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 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,10:PCLK frequency devide by 2, 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: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 (real/4)
writeSCCB(0x5B, OUTH_bit7_0); //ZMOH (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:Internal frequency doublers OFF, clk=XVCLK/(CLKRC + 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:auto mode, bit PCLK system_clock 48MHz/bit(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)) {
rtc_gpio_deinit(pins);
}
conf.pin_bit_mask = 1LL << pins;
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;
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(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 BPC:1, WPC:1
writeSCCB(0xC3, 0b00000000 | s_value2); //CTRL1 AWB, LENC
writeSCCB(0xC2, 0b10001100); //CTRL0 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: bitHorizontal Mirror, bitVertical Flip
writeSCCB(0x13, 0b11100101); //COM8:default C7, AGC 0:manual 1:auto 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. High threshold, 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:jpeg output, Bit: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 = 1: auto, Bit=1:ON set min exp time 1/120s
}else{
tmp_com8 = 0b11000110; //Bit=0:OFF set min exp time 1/120s
uint16_t exposure =
{0, 8, 64, 192, 8192};
exposureResister(exposure);
}
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 = REG45(0x45)
* AEC = AEC(0x10)
* AEC = REG04(0x04)
*/
uint8_t exposure = {(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 = {};
old_ex = (((uint16_t)old_REG45 & 0x003f)) << 10;
old_ex = ((uint16_t)old_AEC) << 2;
old_ex = ((uint16_t)old_REG04) & 0x0003;
uint16_t old_exposure = old_ex | old_ex | old_ex;
Serial.printf("old_exposure=%d\r\n", old_exposure);
uint8_t new_REG45 = (old_REG45 & 0b11000000);
new_REG45 = new_REG45 | (exposure >> 2);
uint8_t new_AEC = (exposure << 6) | (exposure >> 2);
uint8_t new_REG04 = (old_REG04 & 0b11111100);
new_REG04 = new_REG04 | (exposure & 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);
}
接收端的代码
/* 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
*/
#define MGO_TEC_BV1_M5STACK_SD_SKETCH
#include <mgo_tec_bv1_m5stack_sd_simple1.h> //ESP32_mgo_tec library. beta ver 1.0.71
#include <WiFi.h>
#include <rom/tjpgd.h> //TJpgDec – Tiny JPEG Decompressor. (C)ChaN, 2011
const char* utf8sjis_file = "/font/Utf8Sjis.tbl"; //预先记载UTF 8 Shift JIS转换表文件名
const char* shino_half_font_file = "/font/shnm8x16.bdf"; //定义半角字体文件名
const char* shino_full_font_file = "/font/shnmk16.bdf"; //全角东云字体文件
const char* ssid = "xxxxxxxxx"; //请填写您自己路由器SSID
const char* password = "xxxxxxxxx"; //请改写成您自己路由器的密码
const char* host = "192.168.0.22"; //M5Camera(对方服务器)的地址
enum SelCamCtrl {
PING80 = 0,
CHANGE_PCLK = 10,
CHANGE_AEC = 20,
CHANGE_FRAMESIZE = 100,
CHANGE_JPG_QUALITY = 110,
STOP_STREAM = 200, START_STREAM = 201,
RESET_CAM = 255,
} SelectCamCtrl = PING80;
enum StateStream{
OFF_STREAM = 0, ON_STREAM = 1, CLOSE_CONNECTION = 2
} StatusStream = OFF_STREAM;
uint8_t pclk_div_cnt = 4;
int8_t exposure_count = 0;
const char pclk_div_c = {"div 2", "div 4", "div 8", "div16", "div32", "div64"};
const char aec_c = {"Auto "," - 2 "," - 1 "," +-0 "," + 1 "," + 2 "};
const char stream_c = {"Stop ", "Start", "Intrv"};
char framesize_c = {" 96 x 96 ", "80 x 160", "160 x 80", "160 x 120", "192 x 144", "240 x 176"};
uint8_t frame_size_num = 5; //default 240 x 176
uint32_t fps_timer = 0;
uint8_t fps_count = 0;
uint32_t ping80_lasttime = 0;
bool isWiFiConnected = false;
bool isPort80Handshake = false;
bool isPort81Handshake = false;
bool canSendCamCtrl = false;
bool canDisplayLCD = false;
//-------------------------
uint8_t offset = 1;
uint16_t btnA_x0 = 0, btnA_x1 = 107;
uint16_t btnB_x0 = 107, btnB_x1 = 212;
uint16_t btnC_x0 = 212, btnC_x1 = 319;
uint16_t btn_y0 = 197, btn_y1 = 233;
//----------------------
typedef struct {
const void *fp; /* File pointer for input function */
BYTE *fbuf = NULL; /* Pointer to the frame buffer for output function */
size_t fbuf_size = 240 * 2 * 176;
UINT wfbuf = 240; /* Width of the frame buffer */
UINT hfbuf = 176;
} IODEV;
void *work = NULL; /* Pointer to the decompressor work area */
JDEC jdec; /* Decompression object */
JRESULT res; /* Result code of TJpgDec API */
IODEV devid; /* User defined device identifier */
uint8_t *jpg_buf = NULL;
uint32_t jpg_len = 0;
uint32_t jpg_buf_seek = 0;
uint8_t quality = 10;
uint16_t old_w = 240;
uint16_t old_h = 176;
//********CPU core 1 task********************
void setup(){
Serial.begin(115200);
delay(1000);
initDisplay();
TaskHandle_t taskClientCtrl_handl, taskClientStrm_handl;
xTaskCreatePinnedToCore(&taskClientControl, "taskClientControl", 4096, NULL, 5, &taskClientCtrl_handl, 0);
xTaskCreatePinnedToCore(&taskClientStream, "taskClientStream", 8192, NULL, 20, &taskClientStrm_handl, 0);
while(!isWiFiConnected){
Serial.print('.');
delay(500);
}
connectedWifiDisp();
while(!isPort81Handshake){ //等待与ESP32服务器的MJPEG握手结束
actionButton();
delay(1);
}
}
void loop(){
if(canDisplayLCD){
if(old_w == jdec.width && old_h == jdec.height){
log_v("w=%d, h=%d, size=%d\r\n", jdec.width, jdec.height, devid.fbuf_size);
log_v("befor LCD heap=%d\r\n", esp_get_free_heap_size());
LCD.drawPixel65kColRGB565Bytes(offset, offset, jdec.width, jdec.height, (uint8_t*)devid.fbuf, devid.fbuf_size);
if(devid.fbuf){
free(devid.fbuf);
devid.fbuf = NULL;
}
log_v("after LCD heap=%d\r\n", esp_get_free_heap_size());
canDisplayLCD = false;
}else{
displayFrameSize(jdec.width, jdec.height);
LCD.displayClear(0, 0, 240 + offset, 176 + offset);
LCD.drawRectangleLine(0, 0, devid.wfbuf + offset, devid.hfbuf + offset, 31, 63, 31);
old_w = jdec.width, old_h = jdec.height;
}
}
if((StatusStream == ON_STREAM) && (millis() - fps_timer > 1000)){
Serial.printf("%d (fps)\r\n", fps_count);
char fps_c, jpglen_c;
sprintf(fps_c, "%2d", fps_count);
if(jpg_len > 0 && jpg_len < 10000){
sprintf(jpglen_c, "%6d", jpg_len);
}
mM5.disp_fnt.dispText( mM5.font, String(jpglen_c) );
mM5.disp_fnt.dispText( mM5.font, String(fps_c) );
fps_count = 0;
fps_timer = millis();
}
actionButton(); //按键操作
}
//********CPU core 0 task********************
void taskClientControl(void *pvParameters){
connectToWiFi();
while(!isWiFiConnected){
delay(1);
}
while(true){
WiFiClient client80;
connectClient80(client80);
if (client80) {
Serial.println("new client80");
String get_request = "GET / HTTP/1.1\r\n";
get_request += "Host: " + String(host);
get_request += "\r\n";
get_request += "Connection: keep-alive\r\n\r\n";
while (client80.connected()){
client80.print(get_request);
get_request = "";
String req_str;
while(true){
if(client80.available()){
req_str =client80.readStringUntil('\n');
if(req_str.indexOf("200 OK") > 0){
Serial.println(req_str);
req_str = "";
if(!receiveToBlankLine(client80)){
delay(1); continue;
}
Serial.println("complete server responce!");
Serial.println("Go stream!");
isPort80Handshake = true;
ping80_lasttime = millis();
while(true){
changeCamControl(client80);
sendPing80(client80, 60000);
while(client80.available()){
Serial.write(client80.read());
delay(1);
}
if(!isPort80Handshake) break;
delay(1);
}
goto exit_1;
}
}
delay(1);
}
delay(1);
}
}
exit_1:
while(client80.available()){
Serial.write(client80.read());
delay(1);
}
Serial.println("!!!!!!!!!!!!!!!!!!!! client80.stop");
delay(10);
client80.stop();
client80.flush();
delay(10);
}
}
//********CPU core 0 task********************
void taskClientStream(void *pvParameters){
while(true){
if(isPort80Handshake){
WiFiClient client81;
if(!connectStreamClient(client81)){
canSendCamCtrl = true;
Serial.println("Loop Out... wait client81 available");
delay(10);
client81.stop();
client81.flush();
Serial.println("!!!!!!!!!!!!!!!!!!!!! client81.stop");
delay(10);
SelectCamCtrl = PING80;
isPort80Handshake = false;
StatusStream = CLOSE_CONNECTION;
}
}
delay(1);
}
}
//********************************************
bool connectStreamClient(WiFiClient &client81){
int stream_port = 81;
while(true){
if (!client81.connect(host, stream_port)) {
Serial.print(',');
}else{
Serial.printf("client81.connected(2) %s\r\n", host);
break;
}
delay(500);
}
String get_request = "GET /stream HTTP/1.1\r\n";
get_request += "Host: " + String(host);
get_request += ":81\r\n";
get_request += "Connection: keep-alive\r\n\r\n";
client81.print(get_request);
get_request = "";
if(!receiveToBlankLine(client81)){
Serial.println("connectStreamClient FALSE");
return false;
}
Serial.println("stream header receive OK!");
isPort81Handshake = true;
StatusStream = ON_STREAM;
return receiveStream(client81);
}
//*********************************************
bool receiveStream(WiFiClient &client81){
uint32_t time_out;
while(StatusStream == ON_STREAM){
jpg_rcv0:
if(!receiveBoundary(client81)){
delay(1); continue;
}
if(client81.available()){
time_out = millis();
while(StatusStream == ON_STREAM){
if((char)client81.read() == '\n') {
if((char)client81.read() == '\r'){
if((char)client81.read() == '\n') {
if((char)client81.read() == '\r'){
if((char)client81.read() == '\n') {
String res_str = client81.readStringUntil('\n');
jpg_len = strtol(res_str.c_str(), NULL, 16);
log_v("jpg receive LEN:=%d\r\n", jpg_len);
if(jpg_len) {
jpg_buf = (uint8_t *)malloc(jpg_len);
}else{
delay(1);
goto jpg_rcv0;
}
break;
}
}
}
}
}
if(millis() - time_out > 1000){
receiveBoundary(client81);
time_out = millis();
delay(1);
}
}
receiveStreamJPG(client81);
while(StatusStream == ON_STREAM){
if(!canDisplayLCD) break;
delay(1);
}
}
delay(1);
}
return false;
}
//********************************************
bool receiveBoundary(WiFiClient &client81){
char cstr = {};
while(StatusStream == ON_STREAM){
if(client81.available()){
if((char)client81.read() == '-'){
if(client81.read((uint8_t*)cstr, 15)){
//Serial.write(cstr);
//Serial.println();
if(strcmp(cstr, "-myboundary\r\n\r\n") == 0){
return true;
}
}else{
delay(1); continue;
}
}else{
delay(1); continue;
}
}
delay(1);
}
return false;
}
//********************************************
bool receiveStreamJPG(WiFiClient &client81){
bool ret = false;
if(!canDisplayLCD){
if(isPort81Handshake){
while(StatusStream == ON_STREAM){
if(client81.available()) {
uint32_t ptr_addrs = 0;
uint32_t remain_bytes = jpg_len;
int tmp = 0;
log_v("before receive jpg heap=%d\r\n", esp_get_free_heap_size());
while(true){
if(StatusStream != ON_STREAM) return false;
if(client81.available() > 0) {
tmp = client81.read(jpg_buf + ptr_addrs, remain_bytes);
//Serial.printf("tmp=%d\r\n",tmp);
if(tmp < 0){
delay(1); continue;
}
ptr_addrs += tmp;
remain_bytes = remain_bytes - tmp;
if(remain_bytes <= 0) break;
}
delay(1);
}
log_v("after receive jpg heap=%d\r\n", esp_get_free_heap_size());
log_v("%02x, %02x\r\n", jpg_buf, jpg_buf);
ret = decodeJPG();
log_v("after free(jpg_buf) heap=%d\r\n", esp_get_free_heap_size());
return ret;
}
delay(1);
}
}
}
return ret;
}
//********************************************
void connectClient80(WiFiClient &client80){
int httpPort = 80;
while(true){
if(SelectCamCtrl == START_STREAM){
if (client80.connect(host, httpPort)) {
Serial.printf("client80.connected! %s\r\n", host);
canDisplayLCD = false;
break;
}else{
Serial.print(',');
}
}
delay(1);
}
}
//********************************************
void sendPing80(WiFiClient &client80, uint32_t interval_time){
if((millis() - ping80_lasttime) > interval_time){
sendCamCtrlRequest(client80, "ping80", "0");
ping80_lasttime = millis();
}
}
//********************************************
void changeCamControl(WiFiClient &client80){
if(canSendCamCtrl){
String id_str = "", val_str = "0";
switch(SelectCamCtrl){
case CHANGE_PCLK:
id_str = "pclk_div", val_str = String(pclk_div_cnt);
break;
case CHANGE_AEC:
id_str = "aec", val_str = String(exposure_count);
break;
case CHANGE_FRAMESIZE:
id_str = "framesize";
val_str = String(frame_size_num);
break;
case CHANGE_JPG_QUALITY:
id_str = "quality", val_str = String(quality);
break;
case STOP_STREAM:
id_str = "stop_stream";
StatusStream = OFF_STREAM;
isPort80Handshake = false;
break;
case START_STREAM:
id_str = "start_stream";
break;
case RESET_CAM:
id_str = "reset";
break;
default:
id_str = "ping80";
break;
}
sendCamCtrlRequest(client80, id_str, val_str);
SelectCamCtrl = PING80;
}
}
//********************************************
void sendCamCtrlRequest(WiFiClient &client80, String id_str, String val_str){
String get_request = "GET /control?var=" + id_str;
get_request += "&val=" + val_str;
get_request += " HTTP/1.1\r\n";
get_request += "Host: " + String(host);
get_request += "\r\nConnection: keep-alive\r\n\r\n";
client80.print(get_request);
Serial.print(get_request);
get_request = "";
id_str = "";
val_str = "";
receiveToBlankLine(client80);
canSendCamCtrl = false;
}
//*********************************************
bool receiveToBlankLine(WiFiClient &client){
String req_str = "";
uint32_t time_out = millis();
while(StatusStream == ON_STREAM){
if(client.available()){
req_str =client.readStringUntil('\n');
Serial.println(req_str);
if(req_str.indexOf("\r") == 0) return true;
}
if(millis() - time_out > 10000) {
Serial.println("--------error Time OUT receiveToBlankLine");
break;
}
delay(1);
}
return false;
}
//********************************************
void initDisplay(){
mM5.init( utf8sjis_file, shino_half_font_file, shino_full_font_file );
LCD.ILI9341init();
LCD.brightness(255);
LCD.displayClear();
LCD.drawRectangleLine(0, 0, devid.wfbuf + offset, devid.hfbuf + offset, 31, 63, 31);
LCD.drawRectangleLine(btnA_x0, btn_y0, btnA_x1, btn_y1, "#ffffff");
LCD.drawRectangleLine(btnB_x0, btn_y0, btnB_x1, btn_y1, "#ffffff");
LCD.drawRectangleLine(btnC_x0, btn_y0, btnC_x1, btn_y1, "#ffffff");
mM5.font.x0 = 250; mM5.font.y0 = 1;
mM5.font.htmlColorCode( "#FFFFFF" );
mM5.font.Xsize = 1, mM5.font.Ysize = 2;
mM5.disp_fnt.dispText( mM5.font, " Wait." );
mM5.font.x0 = 10; mM5.font.y0 = 200;
mM5.font.htmlColorCode( "red" );
mM5.font.Xsize = 2, mM5.font.Ysize = 2;
mM5.disp_fnt.dispText( mM5.font, String(stream_c) );
mM5.font.x0 = 127; mM5.font.y0 = 200;
mM5.font.htmlColorCode( "white" );
mM5.font.Xsize = 2, mM5.font.Ysize = 2;
mM5.disp_fnt.dispText( mM5.font, "Qs10" );
mM5.font.x0 = 230; mM5.font.y0 = 200;
mM5.font.htmlColorCode( "#ff00ff" );
mM5.font.Xsize = 2, mM5.font.Ysize = 2;
mM5.disp_fnt.dispText( mM5.font, String(aec_c) );
}
//********************************************
void connectedWifiDisp(){
mM5.font.x0 = 250; mM5.font.y0 = 1;
mM5.font.htmlColorCode( "#FFFFFF" );
mM5.font.htmlBgColorCode( "#008800" );
mM5.font.Xsize = 1, mM5.font.Ysize = 2;
LCD.drawRectangleFill(242, 0, 319, 63, "#008800");
mM5.disp_fnt.dispText( mM5.font, " Wi-Fi" );
mM5.font.x0 = 250; mM5.font.y0 = 33;
mM5.disp_fnt.dispText( mM5.font, "TCP OK!" );
LCD.drawRectangleLine(242, 0, 319, 63, "#FFFFFF");
IODEV dev;
displayFrameSize(dev.wfbuf, dev.hfbuf);
mM5.font.x0 = 250; mM5.font.y0 = 96;
mM5.font.htmlColorCode( "#ffffff" );
mM5.font.Xsize = 1, mM5.font.Ysize = 2;
mM5.disp_fnt.dispText( mM5.font, " B" );
mM5.font.x0 = 250; mM5.font.y0 = 132;
mM5.font.htmlColorCode( "#00ff00" );
mM5.font.Xsize = 1, mM5.font.Ysize = 2;
String cam_fps_str = " FPS";
mM5.disp_fnt.dispText( mM5.font, cam_fps_str );
mM5.font.x0 = 250;
}
//********************************************
void displayFrameSize(uint16_t w, uint16_t h){
char w_c, h_c;
sprintf(w_c, "%3d", w);
sprintf(h_c, "%3d", h);
mM5.font.x0 = 243; mM5.font.y0 = 65;
mM5.font.htmlColorCode( "#FFFFFF" );
mM5.font.Xsize = 1, mM5.font.Ysize = 2;
String str = String(w_c)+" x ";
str += String(h_c);
mM5.disp_fnt.dispText( mM5.font, str);
}
//********************************************
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 changeStateStreamBtnDisp(){
mM5.font.htmlBgColorCode( "black" );
mM5.font.Xsize = 2, mM5.font.Ysize = 2;
LCD.drawRectangleFill(btnA_x0+1, btn_y0+1, btnA_x1-1, btn_y1-1, "black");
if(StatusStream == OFF_STREAM || StatusStream == CLOSE_CONNECTION){
SelectCamCtrl = START_STREAM;
StatusStream = ON_STREAM;
mM5.font.htmlColorCode( "green" );
mM5.disp_fnt.dispText( mM5.font, String(stream_c) );
}else if(StatusStream == ON_STREAM){
SelectCamCtrl = STOP_STREAM;
StatusStream = CLOSE_CONNECTION;
mM5.font.htmlColorCode( "red" );
mM5.disp_fnt.dispText( mM5.font, String(stream_c) );
}
canSendCamCtrl = true;
Serial.printf("SelCamCtrl=%d\r\n", (uint8_t)SelectCamCtrl);
}
void changeFrameSizeBtnDisp(){
frame_size_num++;
if(frame_size_num > 5) frame_size_num = 0;
mM5.font.htmlColorCode( "white" );
mM5.font.htmlBgColorCode( "blue" );
mM5.font.Xsize = 1, mM5.font.Ysize = 2;
LCD.drawRectangleFill(btnA_x0+1, btn_y0+1, btnA_x1-1, btn_y1-1, "blue");
mM5.disp_fnt.dispText( mM5.font, String(framesize_c) );
SelectCamCtrl = CHANGE_FRAMESIZE;
canSendCamCtrl = true;
Serial.println("Send CHANGE_FRAMESIZE");
}
void changeQualityBtnDisp(){
quality += 10;
if(quality > 63) quality = 10;
LCD.drawRectangleFill(btnB_x0+1, btn_y0+1, btnB_x1-1, btn_y1-1, "black");
mM5.font.htmlColorCode( "white" );
mM5.font.htmlBgColorCode( "black" );
mM5.font.x0 = 127;
mM5.disp_fnt.dispText( mM5.font, "Qs" );
mM5.font.x0 = 159;
mM5.disp_fnt.dispText( mM5.font, String(quality) );
Serial.printf("Qs=%d\r\n", quality);
SelectCamCtrl = CHANGE_JPG_QUALITY;
canSendCamCtrl = true;
}
void changeAEC(){
exposure_count++;
if(exposure_count > 5) exposure_count = 0;
if(exposure_count == 0) {
mM5.font.htmlColorCode( "#ff00ff" );
Serial.println("exposure=AUTO");
}else{
mM5.font.htmlColorCode( "yellow" );
Serial.printf("exposure=%d\r\n", exposure_count);
}
mM5.disp_fnt.dispText( mM5.font, String(aec_c) );
SelectCamCtrl = CHANGE_AEC;
canSendCamCtrl = true;
}
//********************************************
bool decodeJPG(){
bool ret = false;
work = malloc(3100);
res = jd_prepare(&jdec, in_func, work, 3100, &devid);
log_v("after jd_prepare heap=%d\r\n", esp_get_free_heap_size());
if (res == JDR_OK) {
log_v("Image dimensions: %u by %u. %u bytes used.\r\n", jdec.width, jdec.height, 3100 - jdec.sz_pool);
devid.wfbuf = jdec.width;
devid.hfbuf = jdec.height;
devid.fbuf_size = jdec.width * 2 * jdec.height; //RGB565
devid.fbuf = (BYTE *)malloc(devid.fbuf_size);
log_v("after devid.fbuf malloc heap=%d\r\n", esp_get_free_heap_size());
if(devid.fbuf){
res = jd_decomp(&jdec, out_func, 0); /* Start to decompress with 1/1 scaling */
log_v("after jd_decomp heap=%d\r\n", esp_get_free_heap_size());
if (res == JDR_OK) {
canDisplayLCD = true;
fps_count++; //帧率显示计数
ret = true;
} else {
Serial.printf("Failed to decompress: rc=%d\n", res);
if(devid.fbuf){
free(devid.fbuf);
devid.fbuf = NULL;
}
ret = false;
}
}else{
Serial.println("devid.fbuf malloc FAILED.");
ret = false;
}
} else {
Serial.printf("%02x, %02x\r\n", jpg_buf, jpg_buf);
Serial.printf("Failed to prepare: rc=%d\n", res);
ret = false;
}
jpg_buf_seek = 0;
free(work);
work = NULL;
if(jpg_buf) {
free(jpg_buf);
jpg_buf = NULL;
}
jpg_len = 0;
return ret;
}
UINT in_func (JDEC* jd, BYTE* buff, UINT nbyte){
if (buff) {
//Serial.printf("jpg_buf_seek=%d, nbyte=%d\r\n", jpg_buf_seek, nbyte);
memcpy(buff, jpg_buf + jpg_buf_seek, nbyte);
jpg_buf_seek += nbyte;
//Serial.printf("buff0=%02x, buff1=%02x\r\n", buff, buff);
return nbyte;
}else{
jpg_buf_seek += nbyte;
return nbyte;
}
}
UINT out_func (JDEC* jd, void* bitmap, JRECT* rect){
IODEV *dev = (IODEV*)jd->device;
uint8_t *src, *dst;
uint16_t x, y, bwd;
src = (uint8_t*)bitmap;
//use RGB565
dst = dev->fbuf + 2 * (rect->top * dev->wfbuf + rect->left);;
//bws = 2 * (rect->right - rect->left + 1);
bwd = 2 * dev->wfbuf;
uint8_t red, green, blue;
int16_t xcnt;
for (y = rect->top; y <= rect->bottom; y++) {
xcnt = 0, red = 0, green = 0, blue = 0;
for (x = rect->left; x <= rect->right; x++){
red = *(src++) & 0xF8;
green = *(src++) & 0xFC;
blue = *(src++) & 0xF8;
*(dst + xcnt++) = (red) | (green >> 5);
*(dst + xcnt++) = (green << 3) | (blue >> 3);
}
dst += bwd;
}
return 1; /* Continue to decompress */
}
//*************************************************
void actionButton(){
mM5.btnA.buttonAction();
switch( mM5.btnA.ButtonStatus ){
case mM5.btnA.MomentPress:
Serial.println("\r\nButton A Moment Press");
changeFrameSizeBtnDisp();
break;
case mM5.btnA.ContPress:
Serial.println("\r\n-------------Button A Cont Press");
changeStateStreamBtnDisp();
break;
default:
break;
}
mM5.btnB.buttonAction();
switch( mM5.btnB.ButtonStatus ){
case mM5.btnB.MomentPress:
Serial.println("\r\nButton B Moment Press");
changeQualityBtnDisp();
break;
case mM5.btnB.ContPress:
Serial.println("\r\n-------------Button B Cont Press");
SelectCamCtrl = RESET_CAM;
canSendCamCtrl = true;
mM5.font.x0 = 127;
mM5.font.htmlColorCode( "white" );
mM5.font.htmlBgColorCode( "red" );
LCD.drawRectangleFill(btnB_x0+1, btn_y0+1, btnB_x1-1, btn_y1-1, "red");
mM5.disp_fnt.dispText( mM5.font, "RESET" );
Serial.println("Send !!!");
break;
default:
break;
}
mM5.btnC.buttonAction();
switch( mM5.btnC.ButtonStatus ){
case mM5.btnC.MomentPress:
Serial.println("\r\nButton C Moment Press");
changeAEC();
break;
case mM5.btnC.ContPress:
Serial.println("\r\n-------------Button C Cont Press");
mM5.font.x0 = 230;
mM5.font.htmlColorCode( "#00ffff" );
pclk_div_cnt++;
if(pclk_div_cnt > 5){
pclk_div_cnt = 0;
}
SelectCamCtrl = CHANGE_PCLK;
canSendCamCtrl = true;
mM5.disp_fnt.dispText( mM5.font, String(pclk_div_c) );
break;
default:
break;
}
}
操作说明:
通过切换画质和画面大小可以得到不同的帧率,QS是OV2640的质量缩放因子,数值越小越好,QS10是最高画质,QS60是最低画质
QS10是这个样子的
QS60画面很粗糙,JPEG数据会压缩的相当厉害
你还可以通过按下A键修改画面大小,下图是96x96,由于数据很小,帧率达到25
80*160像素为垂直拉伸,你可以在M5StickC上显示
接下来是160*120,跑出了24帧的效果
页:
[1]