ESP32-S3模组上跑通esp32-camera(21)
接前一篇文章:ESP32-S3模组上跑通esp32-camera(20)
本文内容参考:
esp32-camera入门(基于ESP-IDF)_esp32 camera-CSDN博客
OV5640手册解读-CSDN博客
ESP32_CAM CameraWebServer例程源码解析笔记(一)_void startcameraserver();-CSDN博客
esp32-cam驱动程序阅读 - 哔哩哔哩
特此致谢!
一、OV5640初始化
2. 相机初始化及图像传感器配置
上一回解析了camera_probe函数的第3段代码,本回继续往下解析该函数后续内容。为了便于理解和回顾,再次贴出camera_probe函数源码,在components/esp32-camera/driver/esp_camera.c中,如下:
static esp_err_t camera_probe(const camera_config_t *config, camera_model_t *out_camera_model)
{
esp_err_t ret = ESP_OK;
*out_camera_model = CAMERA_NONE;
if (s_state != NULL) {
return ESP_ERR_INVALID_STATE;
}
s_state = (camera_state_t *) calloc(sizeof(camera_state_t), 1);
if (!s_state) {
return ESP_ERR_NO_MEM;
}
if (config->pin_xclk >= 0) {
ESP_LOGD(TAG, "Enabling XCLK output");
CAMERA_ENABLE_OUT_CLOCK(config);
}
if (config->pin_sccb_sda != -1) {
ESP_LOGD(TAG, "Initializing SCCB");
ret = SCCB_Init(config->pin_sccb_sda, config->pin_sccb_scl);
} else {
ESP_LOGD(TAG, "Using existing I2C port");
ret = SCCB_Use_Port(config->sccb_i2c_port);
}
if(ret != ESP_OK) {
ESP_LOGE(TAG, "sccb init err");
goto err;
}
if (config->pin_pwdn >= 0) {
ESP_LOGD(TAG, "Resetting camera by power down line");
gpio_config_t conf = { 0 };
conf.pin_bit_mask = 1LL << config->pin_pwdn;
conf.mode = GPIO_MODE_OUTPUT;
gpio_config(&conf);
// carefull, logic is inverted compared to reset pin
gpio_set_level(config->pin_pwdn, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_pwdn, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
if (config->pin_reset >= 0) {
ESP_LOGD(TAG, "Resetting camera");
gpio_config_t conf = { 0 };
conf.pin_bit_mask = 1LL << config->pin_reset;
conf.mode = GPIO_MODE_OUTPUT;
gpio_config(&conf);
gpio_set_level(config->pin_reset, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_reset, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
ESP_LOGD(TAG, "Searching for camera address");
//vTaskDelay(10 / portTICK_PERIOD_MS);
uint8_t slv_addr = SCCB_Probe();
if (slv_addr == 0) {
ret = ESP_ERR_NOT_FOUND;
goto err;
}
ESP_LOGI(TAG, "Detected camera at address=0x%02x", slv_addr);
s_state->sensor.slv_addr = slv_addr;
s_state->sensor.xclk_freq_hz = config->xclk_freq_hz;
/**
* Read sensor ID and then initialize sensor
* Attention: Some sensors have the same SCCB address. Therefore, several attempts may be made in the detection process
*/
sensor_id_t *id = &s_state->sensor.id;
for (size_t i = 0; i < sizeof(g_sensors) / sizeof(sensor_func_t); i++) {
if (g_sensors[i].detect(slv_addr, id)) {
camera_sensor_info_t *info = esp_camera_sensor_get_info(id);
if (NULL != info) {
*out_camera_model = info->model;
ESP_LOGI(TAG, "Detected %s camera", info->name);
g_sensors[i].init(&s_state->sensor);
break;
}
}
}
if (CAMERA_NONE == *out_camera_model) { //If no supported sensors are detected
ESP_LOGE(TAG, "Detected camera not supported.");
ret = ESP_ERR_NOT_SUPPORTED;
goto err;
}
ESP_LOGI(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x",
id->PID, id->VER, id->MIDH, id->MIDL);
ESP_LOGD(TAG, "Doing SW reset of sensor");
//vTaskDelay(10 / portTICK_PERIOD_MS);
return s_state->sensor.reset(&s_state->sensor);
err :
CAMERA_DISABLE_OUT_CLOCK();
return ret;
}
接下来来到以下代码片段:
if (config->pin_pwdn >= 0) {
ESP_LOGD(TAG, "Resetting camera by power down line");
gpio_config_t conf = { 0 };
conf.pin_bit_mask = 1LL << config->pin_pwdn;
conf.mode = GPIO_MODE_OUTPUT;
gpio_config(&conf);
// carefull, logic is inverted compared to reset pin
gpio_set_level(config->pin_pwdn, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_pwdn, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
if (config->pin_reset >= 0) {
ESP_LOGD(TAG, "Resetting camera");
gpio_config_t conf = { 0 };
conf.pin_bit_mask = 1LL << config->pin_reset;
conf.mode = GPIO_MODE_OUTPUT;
gpio_config(&conf);
gpio_set_level(config->pin_reset, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_reset, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
按说这是两段代码,但是放在一起说才更好理解。
config->pin_pwdn和config->pin_reset前文书也已经讲过了,参见:ESP32-S3模组上跑通esp32-camera(1)_esp32 s3 性能 比esp32 cam-CSDN博客
回到以上代码片段中:
if (config->pin_pwdn >= 0) {
ESP_LOGD(TAG, "Resetting camera by power down line");
gpio_config_t conf = { 0 };
conf.pin_bit_mask = 1LL << config->pin_pwdn;
conf.mode = GPIO_MODE_OUTPUT;
gpio_config(&conf);
// carefull, logic is inverted compared to reset pin
gpio_set_level(config->pin_pwdn, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_pwdn, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
if (config->pin_reset >= 0) {
ESP_LOGD(TAG, "Resetting camera");
gpio_config_t conf = { 0 };
conf.pin_bit_mask = 1LL << config->pin_reset;
conf.mode = GPIO_MODE_OUTPUT;
gpio_config(&conf);
gpio_set_level(config->pin_reset, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_reset, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
其实代码很好理解,分别对与OV5640的PWDN和RESETB引脚相连接的ESP32-S3的相应GPIO引脚进行初始化,均设置为输出。之后使PWDN先置高10毫秒再置低10毫秒;而RESETB正好反过来,先置低10毫秒后置高10毫秒。
这样操作是为了符合OV5640的上电时序,参见OV5640数据手册的以下内容:
PWDN先置高,10毫秒后由高到低发生跳变。这对应于上图中的PWDN的跳变。对应上边的代码片段:
gpio_set_level(config->pin_pwdn, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_pwdn, 0);
PWDN由高到低发生跳变后,再延时(保持)10毫秒。这对应于上图中的t3。t3应大于等于1毫秒,此处则(至少)为10毫秒。对应上边的代码片段:
gpio_set_level(config->pin_pwdn, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
PWDN由高变为低并保持10毫秒之后,RESETB置为低并保持10毫秒。这也对应于上图中的t3,手册要求大于等于1毫秒,代码中则为20(10+10)毫秒。对应上边的代码片段:
gpio_set_level(config->pin_pwdn, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
……
gpio_set_level(config->pin_reset, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
之后RESET由低变为高,并保持10毫秒,则是为了满足上图中的t4。手册要求大于等于20毫秒,代码中则为10毫秒,这里有点不符合要求。对应上边的代码片段:
gpio_set_level(config->pin_reset, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
如果是OV5640内部供电的情况,则情况也是一样。
这里,乐鑫的代码应该是对两种供电方式都做了兼容,但无论是内部供电还是外部供电的方式,都并未完全契合。
至此,camera_probe函数的第4段代码就解析完了,下一回继续往下解析该函数后续内容。