【搬运】通过UDP将M5Camera图像发送至M5Stack
本例将介绍通过UDP发送图像至M5Stack,代码分为M5CameraB与M5Stack,由于UDP传输图像的时序很难控制,不可避免的会出现丢包现象,因此作者对ESP32-Camera库进行了拆分重写,视频画面大小为200x148,摄像头代码超过了1000行。ESP32_mgo_tec库文件地址https://github.com/mgo-tec/ESP32_mgo_tec 程序刚启动时可能会出现丢包现象,预热一段时间后基本会稳定。经过测试在无阻挡情况下,wifi图像接收范围在5米左右。简要说明:M5Camera开启了SoftAP,第58-59行配置为CIF模式,图像大小为200x148像素.第66行fix_div = 3的值是1460个字节,这是一个数据包中可以发送的最大字节数。第133-192行,M5Camera的ESP32 CPU核心1任务发送和接收WiFi和UDP。在第141-157行中可以看到M5Camera通过UDP从M5Stack接收到的数据包。现在当立即按下M5Stack的A按钮时, 将在数据包的第一个字节处接收send_ok = 1 ,并且图像将通过WiFi和UDP从M5Camera连续传输。如第159-192行所示,UDP由ESP32的CPU内核1发送。当发生数据包传输错误时,如第169行所示, 从核心函数返回errno == 12,在那种情况下,我认为延迟(3)不会导致数据包丢失,但似乎效果不大。接收到的数据包的第二个字节是自动白平衡开-关标志。接收到的数据包的第三个字节是自动曝光调整ON-OFF标志。通过将16位曝光时间值划分为8位来接收接收到的数据包的第4至第5字节。接收到的数据包的第6个字节是M5Camera复位标志。如第136行所示,如果按住M5Stack上的A按钮,receive_reset将接收值1并,如果检测到它,则通过ESP.restart()强制重置。第208-264行:M5Camera ESP32 CPU core 0任务不断从图像传感器OV2640获取图像。在这种情况下,看门狗定时器操作在第237行被禁用,因此如果长时间继续运行可能会出现问题。第967-986行:改变自动白平衡功能,此功能将M5Camera中图像传感器OV2640的自动白平衡调整(AWB)更改为ON / OFF。该寄存器用SCCB从M5Camera中的ESP32传输到图像传感器ov2640。988-1038行:修改自动曝光功能,该功能打开/关闭M5Camera中的图像传感器OV2640的自动曝光调整(AEC),并分三个阶段调整曝光时间。
#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 <WiFi.h>
#include <WiFiUdp.h>
const char * to_udp_address = "192.168.4.2"; //M5StackIP地址
const int to_server_udp_port = 55556; //接收端口
const int my_server_udp_port = 55555; //发送端口
const char* ssid = "xxxxxxxxx";
const char* password = "xxxxxxxxx";
boolean connected = false;
WiFiUDP udp;
const uint8_t ov2640_i2c_addrs = 0x30;
const int8_t cam_pin_PWDN = -1; //M5Camera引脚参照文档说明
const int8_t cam_pin_RESET = 15;
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;
uint8_t camera_pid = 0;
const uint16_t sensor_resolution_h = 400, sensor_resolution_v = 296; //CIF mode
const uint16_t oled_width_pix = 200, oled_height_pix = 148;
const uint16_t out_camera_w = sensor_resolution_h/2;
const uint16_t out_camera_h = sensor_resolution_v/2;
const uint16_t max_w_pix_buf = out_camera_w * 2;
const uint16_t max_w_disp_buf = oled_width_pix * 2;
uint8_t send_udp_buf = {};
boolean goDrawing_disp = false;
const uint8_t fix_div = 3;
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);
uint8_t send_ok = 0;
uint8_t receive_auto_white_balance = 0;
uint8_t receive_auto_exposure_control = 0;
uint8_t receive_exposure = {};
uint8_t receive_reset = 0;
//*********************************************
void setup() {
Serial.begin(115200);
Serial.println();
delay(1000);
connectToWiFi(ssid, password);
if(connected){
Serial.println("connected!");
}
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(true){
if(millis() - time_out > 5000) break;
sendUDP();
delay(1);
}
dispOffColorBar();
}
void loop() {
sendUDP();
receiveUDP();
if(receive_reset == 1){
ESP.restart();
}
}
void receiveUDP(){
int receive_packetSize = udp.parsePacket();
if(receive_packetSize > 0){
send_ok = udp.read();
receive_auto_white_balance = udp.read();
receive_auto_exposure_control = udp.read();
udp.read(&receive_exposure, 2);
receive_reset = udp.read();
uint16_t exposure = (receive_exposure << 8) | receive_exposure;
Serial.printf("Receive UDP send_ok=%d, AWB=%d, AEC=%d\r\n", send_ok, receive_auto_white_balance, receive_auto_exposure_control);
Serial.printf("Receive Exposure=%d\r\n", exposure);
Serial.printf("Receive Reset=%d\r\n", receive_reset);
changeAutoWhiteBalance(receive_auto_white_balance);
changeAutoExposureControl(receive_auto_exposure_control, receive_exposure);
}
udp.flush();
}
void sendUDP(){
if(send_ok){
if(goDrawing_disp){
if(connected){
uint16_t udp_line = 0;
udp.beginPacket(to_udp_address, to_server_udp_port);
for(int i = 0; i < oled_height_pix; i++){
if(i != 0 && (i % fix_div) == 0){
udp.endPacket();
if(errno ==12) {
delay(3);
break;
}else{
delay(1);
delayMicroseconds(100);
}
udp.beginPacket(to_udp_address, to_server_udp_port);
}
udp.write((const uint8_t *)&send_udp_buf, max_w_disp_buf + 1);
//Serial.printf("max_w_disp_buf=%d\r\n", max_w_disp_buf);
}
udp.endPacket();
if(errno ==12) {
delay(3);
}else{
delay(2);
}
}
goDrawing_disp = false;
}
}
}
//*********************************************
void connectToWiFi(const char * ssid, const char * pwd){
Serial.println("Connecting to WiFi network: " + String(ssid));
WiFi.disconnect(true, true); //WiFi config
WiFi.softAP(ssid, password);
IPAddress myIP = WiFi.softAPIP();
Serial.println("WiFi connected!");
Serial.print("My IP address: ");
Serial.println(myIP);
udp.begin(myIP, my_server_udp_port);
delay(1000);
connected = true;
}
//****************************************
void taskDMA(void *pvParameters){
Serial.println("taskDMA Start!!");
disableCore0WDT(); //禁用看门狗
uint32_t last_time = millis();
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的像素数据通过DMA写入FIFO存储器
}
I2S0.conf.rx_start = 0;
I2S0.in_link.start = 0;
esp_intr_disable(i2s_intr_handle);
resetI2Sconf();
if(millis() - last_time > 10000) {
enableCore0WDT();
delay(5);
disableCore0WDT();
last_time = millis();
}
}
timeout:
vTaskDelete(task_handl);
}
//*********************************************
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 = 0; i < oled_height_pix; i++){
send_udp_buf = i;
}
pix_cnt = 1;
for (i = 2; i < dma_buf_size; i += 4) {
send_udp_buf = (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 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 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,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?
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:DVP ※DVP是并行数据格式
delay(5);
writeSCCB(0xC0, HSIZE_bit10_3); //HSIZE8是11位的高8位。这是h_pixel 400
writeSCCB(0xC1, VSIZE_bit10_3); //VSIZE8是11位的高8位。这是v_pixel 296
writeSCCB(0x8C, 0x00);
writeSCCB(0x86, 0b00111101); //CTRL2
writeSCCB(0x50, 0b10000000); //CTRLl LP_DP EN
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(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)) {
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
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);
}
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); //预先确定宽度像素是否可被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 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);
}
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, 0x11100111); //COM8:default C7, AGC 0:manual 1:auto 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. High threshold, 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, 0b11100001); //CTRL1
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, 0b11100001); //CTRL1
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
}
void changeAutoWhiteBalance(uint8_t receive_data){
Serial.println("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 = receive_data << 3;
awb_gain_bit = receive_data << 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 changeAutoExposureControl(uint8_t change, uint8_t exposure){
uint8_t COM8 = 0;
writeSCCB(0xff, 0x01);
COM8 = readSCCB(0x13);
Serial.print("Old COM8=");
Serial.println(COM8, BIN);
uint8_t change_aec = 0;
if(change) {
change_aec = 0b11100111;
}else{
change_aec = 0b11000110;
exposureResister(exposure);
}
if(COM8 != change_aec){
COM8 = change_aec;
writeSCCB(0xff, 0x01);
writeSCCB(0x13, COM8);
Serial.print("New COM8=");
Serial.println(COM8, BIN);
}
}
void exposureResister(uint8_t exposure){
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 = {};
uint16_t temp = 0;
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 = 0;
uint8_t new_AEC = 0;
uint8_t new_REG04 = 0;
new_REG45 = (old_REG45 & 0b11000000);
new_REG45 = new_REG45 | (exposure >> 2);
new_AEC = (exposure << 6) | (exposure >> 6);
new_REG04 = (new_REG04 & 0b11111100);
new_REG04 = new_REG04 | (exposure & 0b00000011);
writeSCCB(0xff, 0x01);
writeSCCB(0x45, new_REG45);
writeSCCB(0x10, new_AEC);
writeSCCB(0x04, new_REG04);
uint16_t new_exposure = (exposure << 8) | exposure;
Serial.printf("new_exposure=%d\r\n", new_exposure);
}
以下为M5Stack端的代码,作者自己封装了一个显示库改写了默认的字体并增加了菜单样式,可以自行修改。建议先从github进行下载mgo_tec库进行验证
#define MGO_TEC_BV1_M5STACK_SD_SKETCH
#include <mgo_tec_bv1_m5stack_sd_simple1.h> //ESP32_mgo_tec library. beta ver 1.0.69
#include <WiFi.h>
#include <WiFiUdp.h>
const char* utf8sjis_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 描述转换表文件名
const char* shino_half_font_file = "/font/shnm8x16.bdf"; //定义半角字体文件名
const char* shino_full_font_file = "/font/MYshnmk16.bdf"; //修改的双字节Shinonome字体文件
const char * to_udp_address = "192.168.4.1";
const int to_server_udp_port = 55555;
const int my_server_udp_port = 55556;
const char* ssid = "xxxxxxxxx"; //M5Camera的SSID
const char* password = "xxxxxxxxx"; //密码
WiFiUDP udp;
boolean connected = false;
const uint16_t disp_w_pix = 200;
const uint16_t disp_h_pix = 148;
const uint8_t fix_div = 3;
const uint8_t div_h = disp_h_pix / fix_div;
uint16_t udp_line = 0;
boolean isDisp_ok = false;
const uint16_t line_max_bytes = disp_w_pix * 2;
unsigned char line_num_buf = {};
unsigned char pix_buf = {};
TaskHandle_t task_handl;
enum UdpSendStatus {
udp_stop = 0,
udp_start = 1
} UdpSendState = udp_stop;
enum UdpSendAwb {
awb_off = 0,
awb_on = 1
} UdpSendAWB = awb_off;
enum UdpSendAec {
aec_off = 0,
aec_on = 1
} UdpSendAEC = aec_on;
uint8_t udp_send_reset = 0;
int16_t exposure = {0, 192, 256, 8192};
int8_t exposure_count = 0;
String aec_str = {"Auto ",
" - 1 ",
" + 1 ",
" + 2 "};
String stream_str = {"Stop ", "Start", "RESET"};
String awb_str = {" OFF ", " O N "};
uint8_t send_auto_white_balance = 0;
uint8_t send_auto_exposure_control = 0;
uint8_t send_data = {};
uint8_t offset = 1;
boolean isSend_ok = false;
uint16_t btn1_x0 = 0, btn1_x1 = 107;
uint16_t btn2_x0 = 107, btn2_x1 = 212;
uint16_t btn3_x0 = 212, btn3_x1 = 319;
uint16_t btn_y0 = 197, btn_y1 = 233;
//********CPU core 1 task********************
void setup(){
Serial.begin(115200);
delay(1000);
xTaskCreatePinnedToCore(&taskDisplay, "taskDisplay", 8192, NULL, 10, &task_handl, 0);
connectToWiFi();
while(!connected){
delay(1);
}
send_data = (uint8_t)UdpSendAEC;
}
void loop(){
receiveUDP();
sendUDP(send_data);
}
void receiveUDP(){
if(!isDisp_ok){
if(connected){
int packetSize = udp.parsePacket();
if(packetSize > 0){
for(int i = 0; i < fix_div; i++){
udp.read(&line_num_buf, 1);
udp.read(&pix_buf], line_max_bytes);
}
udp_line = (uint16_t)floor((double)line_num_buf / (double)fix_div);
//Serial.printf("udp_line=%2d, packet_size =%d, (", udp_line, packetSize);
if(udp_line == (div_h - 1)) isDisp_ok = true;
}
udp.flush();
delayMicroseconds(500);
}
}
}
void sendUDP(uint8_t send_data){
if(isSend_ok){
udp.beginPacket(to_udp_address, to_server_udp_port);
udp.write(send_data, 6);
udp.endPacket();
isSend_ok = false;
页:
[1]