接前一篇文章:ESP32-S3模组上跑通esp32-camera(11)
本文内容参考:
esp32-camera入门(基于ESP-IDF)_esp32 camera-CSDN博客
OV5640手册解读-CSDN博客
ESP32_CAM CameraWebServer例程源码解析笔记(一)_void startcameraserver();-CSDN博客
esp32-cam驱动程序阅读 - 哔哩哔哩
特此致谢!
一、OV5640初始化
2. 相机初始化及图像传感器配置
到上一回为止,讲解完了关于引脚配置的全部内容,本回开始解析camera的初始化及配置。再次贴出https://github.com/espressif/esp32-camera中的示例代码:
#include "esp_camera.h"//WROVER-KIT PIN Map
#define CAM_PIN_PWDN -1 //power down is not used
#define CAM_PIN_RESET -1 //software reset will be performed
#define CAM_PIN_XCLK 21
#define CAM_PIN_SIOD 26
#define CAM_PIN_SIOC 27#define CAM_PIN_D7 35
#define CAM_PIN_D6 34
#define CAM_PIN_D5 39
#define CAM_PIN_D4 36
#define CAM_PIN_D3 19
#define CAM_PIN_D2 18
#define CAM_PIN_D1 5
#define CAM_PIN_D0 4
#define CAM_PIN_VSYNC 25
#define CAM_PIN_HREF 23
#define CAM_PIN_PCLK 22static camera_config_t camera_config = {.pin_pwdn = CAM_PIN_PWDN,.pin_reset = CAM_PIN_RESET,.pin_xclk = CAM_PIN_XCLK,.pin_sccb_sda = CAM_PIN_SIOD,.pin_sccb_scl = CAM_PIN_SIOC,.pin_d7 = CAM_PIN_D7,.pin_d6 = CAM_PIN_D6,.pin_d5 = CAM_PIN_D5,.pin_d4 = CAM_PIN_D4,.pin_d3 = CAM_PIN_D3,.pin_d2 = CAM_PIN_D2,.pin_d1 = CAM_PIN_D1,.pin_d0 = CAM_PIN_D0,.pin_vsync = CAM_PIN_VSYNC,.pin_href = CAM_PIN_HREF,.pin_pclk = CAM_PIN_PCLK,.xclk_freq_hz = 20000000,//EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode.ledc_timer = LEDC_TIMER_0,.ledc_channel = LEDC_CHANNEL_0,.pixel_format = PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG.frame_size = FRAMESIZE_UXGA,//QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates..jpeg_quality = 12, //0-63, for OV series camera sensors, lower number means higher quality.fb_count = 1, //When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode..grab_mode = CAMERA_GRAB_WHEN_EMPTY//CAMERA_GRAB_LATEST. Sets when buffers should be filled
};esp_err_t camera_init(){//power up the camera if PWDN pin is definedif(CAM_PIN_PWDN != -1){pinMode(CAM_PIN_PWDN, OUTPUT);digitalWrite(CAM_PIN_PWDN, LOW);}//initialize the cameraesp_err_t err = esp_camera_init(&camera_config);if (err != ESP_OK) {ESP_LOGE(TAG, "Camera Init Failed");return err;}return ESP_OK;
}esp_err_t camera_capture(){//acquire a framecamera_fb_t * fb = esp_camera_fb_get();if (!fb) {ESP_LOGE(TAG, "Camera Capture Failed");return ESP_FAIL;}//replace this with your own functionprocess_image(fb->width, fb->height, fb->format, fb->buf, fb->len);//return the frame buffer back to the driver for reuseesp_camera_fb_return(fb);return ESP_OK;
}
接下来就来到了第一个核心函数:camera_init。代码片段如下:
esp_err_t camera_init(){//power up the camera if PWDN pin is definedif(CAM_PIN_PWDN != -1){pinMode(CAM_PIN_PWDN, OUTPUT);digitalWrite(CAM_PIN_PWDN, LOW);}//initialize the cameraesp_err_t err = esp_camera_init(&camera_config);if (err != ESP_OK) {ESP_LOGE(TAG, "Camera Init Failed");return err;}return ESP_OK;
}
这段代码的风格是典型的Arduino的风格,我们这里使用的是ESP-IDF。不过没有关系,先把关键的接口函数讲了,后边再切换到ESP-IDF的代码。
camera_init函数中的关键接口函数为esp_camera_init。该函数在components\esp32-camera\driver\esp_camera.c中,代码如下:
esp_err_t esp_camera_init(const camera_config_t *config)
{esp_err_t err;err = cam_init(config);if (err != ESP_OK) {ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);return err;}camera_model_t camera_model = CAMERA_NONE;err = camera_probe(config, &camera_model);if (err != ESP_OK) {ESP_LOGE(TAG, "Camera probe failed with error 0x%x(%s)", err, esp_err_to_name(err));goto fail;}framesize_t frame_size = (framesize_t) config->frame_size;pixformat_t pix_format = (pixformat_t) config->pixel_format;if (PIXFORMAT_JPEG == pix_format && (!camera_sensor[camera_model].support_jpeg)) {ESP_LOGE(TAG, "JPEG format is not supported on this sensor");err = ESP_ERR_NOT_SUPPORTED;goto fail;}if (frame_size > camera_sensor[camera_model].max_size) {ESP_LOGW(TAG, "The frame size exceeds the maximum for this sensor, it will be forced to the maximum possible value");frame_size = camera_sensor[camera_model].max_size;}err = cam_config(config, frame_size, s_state->sensor.id.PID);if (err != ESP_OK) {ESP_LOGE(TAG, "Camera config failed with error 0x%x", err);goto fail;}s_state->sensor.status.framesize = frame_size;s_state->sensor.pixformat = pix_format;ESP_LOGD(TAG, "Setting frame size to %dx%d", resolution[frame_size].width, resolution[frame_size].height);if (s_state->sensor.set_framesize(&s_state->sensor, frame_size) != 0) {ESP_LOGE(TAG, "Failed to set frame size");err = ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE;goto fail;}s_state->sensor.set_pixformat(&s_state->sensor, pix_format);
#if CONFIG_CAMERA_CONVERTER_ENABLEDif(config->conv_mode) {s_state->sensor.pixformat = get_output_data_format(config->conv_mode); // If conversion enabled, change the out data format by conversion mode}
#endifif (s_state->sensor.id.PID == OV2640_PID) {s_state->sensor.set_gainceiling(&s_state->sensor, GAINCEILING_2X);s_state->sensor.set_bpc(&s_state->sensor, false);s_state->sensor.set_wpc(&s_state->sensor, true);s_state->sensor.set_lenc(&s_state->sensor, true);}if (pix_format == PIXFORMAT_JPEG) {s_state->sensor.set_quality(&s_state->sensor, config->jpeg_quality);}s_state->sensor.init_status(&s_state->sensor);cam_start();return ESP_OK;fail:esp_camera_deinit();return err;
}
esp_camera_init函数是乐鑫写好的接口函数,不用自己编写,但是要看懂。网上大多写到这里就不讲了,即使有讲的,也只是在功能层面上大致说了一下。笔者要讲,而且还要花大量力气、大量笔墨来讲。
函数较长,一段一段来看。先来看第一段代码,片段如下:
err = cam_init(config);if (err != ESP_OK) {ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);return err;}
cam_init函数在components\esp32-camera\driver\cam_hal.c中,代码如下:
esp_err_t cam_init(const camera_config_t *config)
{CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG);esp_err_t ret = ESP_OK;cam_obj = (cam_obj_t *)heap_caps_calloc(1, sizeof(cam_obj_t), MALLOC_CAP_DMA);CAM_CHECK(NULL != cam_obj, "lcd_cam object malloc error", ESP_ERR_NO_MEM);cam_obj->swap_data = 0;cam_obj->vsync_pin = config->pin_vsync;cam_obj->vsync_invert = true;ll_cam_set_pin(cam_obj, config);ret = ll_cam_config(cam_obj, config);CAM_CHECK_GOTO(ret == ESP_OK, "ll_cam initialize failed", err);#if CAMERA_DBG_PIN_ENABLEPIN_FUNC_SELECT(GPIO_PIN_MUX_REG[DBG_PIN_NUM], PIN_FUNC_GPIO);gpio_set_direction(DBG_PIN_NUM, GPIO_MODE_OUTPUT);gpio_set_pull_mode(DBG_PIN_NUM, GPIO_FLOATING);
#endifESP_LOGI(TAG, "cam init ok");return ESP_OK;err:free(cam_obj);cam_obj = NULL;return ESP_FAIL;
}
先说cam_init函数的参数const camera_config_t *config,对应的实参就是前边花了10篇文章讲解的static camera_config_t camera_config。
static camera_config_t camera_config = {.pin_pwdn = CAM_PIN_PWDN,.pin_reset = CAM_PIN_RESET,.pin_xclk = CAM_PIN_XCLK,.pin_sccb_sda = CAM_PIN_SIOD,.pin_sccb_scl = CAM_PIN_SIOC,.pin_d7 = CAM_PIN_D7,.pin_d6 = CAM_PIN_D6,.pin_d5 = CAM_PIN_D5,.pin_d4 = CAM_PIN_D4,.pin_d3 = CAM_PIN_D3,.pin_d2 = CAM_PIN_D2,.pin_d1 = CAM_PIN_D1,.pin_d0 = CAM_PIN_D0,.pin_vsync = CAM_PIN_VSYNC,.pin_href = CAM_PIN_HREF,.pin_pclk = CAM_PIN_PCLK,……
};
cam_init函数一上来先检查传入的参数是否为空,如果为空就提示并返回。
接下来为cam_obj_t cam_obj动态分配内存空间,并作检查。代码片段如下:
cam_obj = (cam_obj_t *)heap_caps_calloc(1, sizeof(cam_obj_t), MALLOC_CAP_DMA);CAM_CHECK(NULL != cam_obj, "lcd_cam object malloc error", ESP_ERR_NO_MEM);
cam_obj是全局变量,在components\esp32-camera\driver\cam_hal.c中声明并初始化,代码如下:
static cam_obj_t *cam_obj = NULL;
cam_obj_t的定义在components\esp32-camera\target\private_include\ll_cam.h中,如下:
typedef struct {uint32_t dma_bytes_per_item;uint32_t dma_buffer_size;uint32_t dma_half_buffer_size;uint32_t dma_half_buffer_cnt;uint32_t dma_node_buffer_size;uint32_t dma_node_cnt;uint32_t frame_copy_cnt;//for JPEG modelldesc_t *dma;uint8_t *dma_buffer;cam_frame_t *frames;QueueHandle_t event_queue;QueueHandle_t frame_buffer_queue;TaskHandle_t task_handle;intr_handle_t cam_intr_handle;uint8_t dma_num;//ESP32-S3intr_handle_t dma_intr_handle;//ESP32-S3uint8_t jpeg_mode;uint8_t vsync_pin;uint8_t vsync_invert;uint32_t frame_cnt;uint32_t recv_size;bool swap_data;bool psram_mode;//for RGB/YUV modesuint16_t width;uint16_t height;
#if CONFIG_CAMERA_CONVERTER_ENABLEDfloat in_bytes_per_pixel;float fb_bytes_per_pixel;camera_conv_mode_t conv_mode;
#elseuint8_t in_bytes_per_pixel;uint8_t fb_bytes_per_pixel;
#endifuint32_t fb_size;cam_state_t state;
} cam_obj_t;
在往下继续讲解之前,先要补强一下ESP-IDF的动态内存分配函数heap_caps_calloc。补强之后再往下继续。对于heap_caps_calloc等动态内存分配相关函数的介绍,请看下回。