ESP-WHO(一)ESP32 摄像头驱动分析

  • ESP32 摄像头驱动分析
    • 摄像头初始化
    • 数据采集

ESP32 摄像头驱动分析

摄像头代码

摄像头初始化

Step:

  1. 寻找摄像头

    1. 提供摄像头时钟、初始化 SCCB 总线、硬件复位摄像头
    2. 轮询地址寻找摄像头,通过 SCCB 总线读取摄像头 ID 等信息
    3. 更改摄像头的 ID 判断型号,并绑定对应的相关函数(摄像头传感器配置相关函数)
  2. 初始化摄像头
    1. 根据选择的图像格式、和是否是高速模式,选择对应的 DMA BUF 处理函数和 DMA FIFO 模式
    2. 初始化 I2S 总线,使能 I2S_IN_DONE_INT 中断:当前 DMA 接收链表描述符被处理时即触发此中断 3. 初始化 DMA 相关变量(链表描述符、DMA 使用的数据缓冲区链表等),DMA 单次最多 4KB 、每行 DMA 采集几次
    3. 初始化存储图像的数据缓冲区(添加到一个链表中)并清空
    4. 初始化相关信号量:DMA 数据采集完成、一帧图像采集完成信号量、图像数据缓冲区进出信号量
    5. 创建 dma_filter_task 将 DMA 数据转换成像素数据并存储到图像缓冲区,这里也会检查是否有脏数据产生,判断是否接收完一帧完整的图像
    6. 初始化 vsync io 中断:每一帧图像开始结尾都会发生电平翻转
    7. 摄像头传感器相关配置(图像大小、格式等)

函数说明:

  1. esp_camera_init 函数:

    1. 寻找摄像头
    2. 摄像头相关初始化
esp_err_t esp_camera_init(const camera_config_t* config)
{camera_model_t camera_model = CAMERA_NONE;esp_err_t err = camera_probe(config, &camera_model);if (err != ESP_OK) {ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err);goto fail;}err = camera_init(config);if (err != ESP_OK) {ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);return err;}return ESP_OK;fail:free(s_state);s_state = NULL;camera_disable_out_clock();return err;
}

函数说明:

  1. camera_probe 函数:

    1. 摄像头时钟配置、SCCB 总线初始化、硬件复位摄像头
    2. 轮询 127 以内的地址进行寻找摄像头、读取基本信息 PID、VER、MIDL、MIDH
    3. 根据摄像头 PID 判断设备类型,并绑定相应的函数、软件复位摄像头
esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera_model)
{if (s_state != NULL) {return ESP_ERR_INVALID_STATE;}s_state = (camera_state_t*) calloc(sizeof(*s_state), 1);if (!s_state) {return ESP_ERR_NO_MEM;}ESP_LOGD(TAG, "Enabling XCLK output");camera_enable_out_clock(config);ESP_LOGD(TAG, "Initializing SSCB");SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl);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 pingpio_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);
#if CONFIG_OV2640_SUPPORT} else {//reset OV2640SCCB_Write(0x30, 0xFF, 0x01);//bank sensorSCCB_Write(0x30, 0x12, 0x80);//resetvTaskDelay(10 / portTICK_PERIOD_MS);
#endif}ESP_LOGD(TAG, "Searching for camera address");vTaskDelay(10 / portTICK_PERIOD_MS);uint8_t slv_addr = SCCB_Probe();if (slv_addr == 0) {*out_camera_model = CAMERA_NONE;camera_disable_out_clock();return ESP_ERR_CAMERA_NOT_DETECTED;}s_state->sensor.slv_addr = slv_addr;//s_state->sensor.slv_addr = 0x30;ESP_LOGD(TAG, "Detected camera at address=0x%02x", s_state->sensor.slv_addr);sensor_id_t* id = &s_state->sensor.id;id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID);id->VER = SCCB_Read(s_state->sensor.slv_addr, REG_VER);id->MIDL = SCCB_Read(s_state->sensor.slv_addr, REG_MIDL);id->MIDH = SCCB_Read(s_state->sensor.slv_addr, REG_MIDH);vTaskDelay(10 / portTICK_PERIOD_MS);ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x",id->PID, id->VER, id->MIDH, id->MIDL);switch (id->PID) {#if CONFIG_OV2640_SUPPORTcase OV2640_PID:*out_camera_model = CAMERA_OV2640;ov2640_init(&s_state->sensor);break;
#endif
#if CONFIG_OV7725_SUPPORTcase OV7725_PID:*out_camera_model = CAMERA_OV7725;ov7725_init(&s_state->sensor);break;
#endifdefault:id->PID = 0;*out_camera_model = CAMERA_UNKNOWN;camera_disable_out_clock();ESP_LOGE(TAG, "Detected camera not supported.");return ESP_ERR_CAMERA_NOT_SUPPORTED;}ESP_LOGD(TAG, "Doing SW reset of sensor");s_state->sensor.reset(&s_state->sensor);return ESP_OK;
}

函数说明:

  1. camera_init 函数:

    1. 根据选择的图像格式、和是否是高速模式,选择对应的 DMA BUF 处理函数和 DMA FIFO 模式
    2. 初始化 I2S 总线,相关引脚配置并使能 I2S_IN_DONE_INT 中断:当前 DMA 接收链表描述符被处理时即触发此中断 3. 初始化 DMA 相关变量(链表描述符、DMA 使用的数据缓冲区链表等),DMA 单次最多 4KB 、每行 DMA 采集几次
    3. 初始化存储图像的数据缓冲区(添加到一个链表中)并清空
    4. 初始化相关信号量:DMA 数据采集完成、一帧图像采集完成信号量、图像数据缓冲区进出信号量
    5. 创建 dma_filter_task 将 DMA 数据转换成像素数据并存储到图像缓冲区,这里也会检查是否有脏数据产生,判断是否接收完一帧完整的图像
    6. 初始化 vsync io 中断:每一帧图像开始结尾都会发生电平翻转
    7. 摄像头传感器相关配置(图像大小、格式等)
    8. 跳过前面几帧数据
esp_err_t camera_init(const camera_config_t* config)
{if (!s_state) {return ESP_ERR_INVALID_STATE;}if (s_state->sensor.id.PID == 0) {return ESP_ERR_CAMERA_NOT_SUPPORTED;}memcpy(&s_state->config, config, sizeof(*config));esp_err_t err = ESP_OK;framesize_t frame_size = (framesize_t) config->frame_size;pixformat_t pix_format = (pixformat_t) config->pixel_format;s_state->width = resolution[frame_size][0];s_state->height = resolution[frame_size][1];if (pix_format == PIXFORMAT_GRAYSCALE) {s_state->fb_size = s_state->width * s_state->height;if (is_hs_mode()) {s_state->sampling_mode = SM_0A00_0B00;s_state->dma_filter = &dma_filter_grayscale_highspeed;} else {s_state->sampling_mode = SM_0A0B_0C0D;s_state->dma_filter = &dma_filter_grayscale;}s_state->in_bytes_per_pixel = 2;       // camera sends YUYVs_state->fb_bytes_per_pixel = 1;       // frame buffer stores Y8} else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) {s_state->fb_size = s_state->width * s_state->height * 2;if (is_hs_mode()) {s_state->sampling_mode = SM_0A00_0B00;s_state->dma_filter = &dma_filter_yuyv_highspeed;} else {s_state->sampling_mode = SM_0A0B_0C0D;s_state->dma_filter = &dma_filter_yuyv;}s_state->in_bytes_per_pixel = 2;       // camera sends YUYVs_state->fb_bytes_per_pixel = 2;       // frame buffer stores Y8} else if (pix_format == PIXFORMAT_JPEG) {if (s_state->sensor.id.PID != OV2640_PID) {ESP_LOGE(TAG, "JPEG format is only supported for ov2640");err = ESP_ERR_NOT_SUPPORTED;goto fail;}int qp = config->jpeg_quality;int compression_ratio_bound = 1;if (qp > 10) {compression_ratio_bound = 16;} else if (qp > 5) {compression_ratio_bound = 10;} else {compression_ratio_bound = 4;}(*s_state->sensor.set_quality)(&s_state->sensor, qp);s_state->in_bytes_per_pixel = 2;s_state->fb_bytes_per_pixel = 2;s_state->fb_size = (s_state->width * s_state->height * s_state->fb_bytes_per_pixel) / compression_ratio_bound;s_state->dma_filter = &dma_filter_jpeg;s_state->sampling_mode = SM_0A00_0B00;} else {ESP_LOGE(TAG, "Requested format is not supported");err = ESP_ERR_NOT_SUPPORTED;goto fail;}ESP_LOGD(TAG, "in_bpp: %d, fb_bpp: %d, fb_size: %d, mode: %d, width: %d height: %d",s_state->in_bytes_per_pixel, s_state->fb_bytes_per_pixel,s_state->fb_size, s_state->sampling_mode,s_state->width, s_state->height);i2s_init();err = dma_desc_init();if (err != ESP_OK) {ESP_LOGE(TAG, "Failed to initialize I2S and DMA");goto fail;}//s_state->fb_size = 75 * 1024;err = camera_fb_init(s_state->config.fb_count);if (err != ESP_OK) {ESP_LOGE(TAG, "Failed to allocate frame buffer");goto fail;}s_state->data_ready = xQueueCreate(16, sizeof(size_t));if (s_state->data_ready == NULL) {ESP_LOGE(TAG, "Failed to dma queue");err = ESP_ERR_NO_MEM;goto fail;}if(s_state->config.fb_count == 1) {s_state->frame_ready = xSemaphoreCreateBinary();if (s_state->frame_ready == NULL) {ESP_LOGE(TAG, "Failed to create semaphore");err = ESP_ERR_NO_MEM;goto fail;}} else {s_state->fb_in = xQueueCreate(s_state->config.fb_count, sizeof(camera_fb_t *));s_state->fb_out = xQueueCreate(1, sizeof(camera_fb_t *));if (s_state->fb_in == NULL || s_state->fb_out == NULL) {ESP_LOGE(TAG, "Failed to fb queues");err = ESP_ERR_NO_MEM;goto fail;}}//ToDo: core affinity?if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 1)) {ESP_LOGE(TAG, "Failed to create DMA filter task");err = ESP_ERR_NO_MEM;goto fail;}vsync_intr_disable();gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM);err = gpio_isr_handler_add(s_state->config.pin_vsync, &vsync_isr, NULL);if (err != ESP_OK) {ESP_LOGE(TAG, "vsync_isr_handler_add failed (%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", s_state->width, s_state->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 (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);}skip_frame();//todo: for some reason the first set of the quality does not work.if (pix_format == PIXFORMAT_JPEG) {(*s_state->sensor.set_quality)(&s_state->sensor, config->jpeg_quality);}s_state->sensor.init_status(&s_state->sensor);return ESP_OK;fail:esp_camera_deinit();return err;
}

数据采集

Step:

  1. DMA 链表描述符初始化,初始化 4 行图像数据大小的 buf 空间
  2. 开始接收数据时,先运行 i2s_run() ,等待 VSYNC 信号变为高时,即表示摄像头端已经开始一帧图像传输,需要跳过 VSYNC 为低时的数据,再采集过程中出现 VSYNC 由高到低变化,则表示一帧图像传输完成,VSYNC 中断服务函数 vsync_isr
  3. 当前 DMA 接收链表描述符被处理完成时将触发 I2S_IN_DONE_INT 中断,进入 i2s_isr(),中断服务函数中,首先清楚中断标志,之后通过 signal_dma_buf_received 函数发出一次 DMA 数据采集完成信号,若一帧图像数据接收完成就停止 i2s
  4. dma_filter_task 任务中会等待 DMA 数据采集完成信号,并处理 DMA buffer 中的数据放入图像缓冲区中
  5. 循环 3、4 步骤直到一帧图像数据接收完全,发出帧接收完信号

函数说明:

  1. i2s_run 函数:

    1. 跳过当前这一帧(VSYNC 为高的这段数据丢掉),等待 VSYNC 为低,开始 i2s 接收数据
    2. 并在 i2s_start_bus 函数中使能 VSYNC 中断
static void i2s_run()
{for (int i = 0; i < s_state->dma_desc_count; ++i) {lldesc_t* d = &s_state->dma_desc[i];ESP_LOGV(TAG, "DMA desc %2d: %u %u %u %u %u %u %p %p",i, d->length, d->size, d->offset, d->eof, d->sosf, d->owner, d->buf, d->qe.stqe_next);memset(s_state->dma_buf[i], 0, d->length);}// wait for framecamera_fb_int_t * fb = s_state->fb;while(s_state->config.fb_count > 1) {while(s_state->fb->ref && s_state->fb->next != fb) {s_state->fb = s_state->fb->next;}if(s_state->fb->ref == 0) {break;}vTaskDelay(2);}// wait for vsyncESP_LOGV(TAG, "Waiting for negative edge on VSYNC");while (_gpio_get_level(s_state->config.pin_vsync) != 0) {;}ESP_LOGV(TAG, "Got VSYNC");i2s_start_bus();
}
static void IRAM_ATTR i2s_start_bus()
{s_state->dma_desc_cur = 0;s_state->dma_received_count = 0;//s_state->dma_filtered_count = 0;esp_intr_disable(s_state->i2s_intr_handle);i2s_conf_reset();I2S0.rx_eof_num = s_state->dma_sample_count;I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[0];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(s_state->i2s_intr_handle);I2S0.conf.rx_start = 1;if (s_state->config.pixel_format == PIXFORMAT_JPEG) {vsync_intr_enable();}
}

函数说明:

  1. dma_desc_init 函数:

    1. DMA 链表描述符初始化,初始化 4 行图像数据大小的 buf 空间
    2. 分配 DMA buf 空间
static esp_err_t dma_desc_init()
{assert(s_state->width % 4 == 0);size_t line_size = s_state->width * s_state->in_bytes_per_pixel *i2s_bytes_per_sample(s_state->sampling_mode);ESP_LOGD(TAG, "Line width (for DMA): %d bytes", line_size);size_t dma_per_line = 1;size_t buf_size = line_size;while (buf_size >= 4096) {buf_size /= 2;dma_per_line *= 2;}size_t dma_desc_count = dma_per_line * 4;s_state->dma_buf_width = line_size;s_state->dma_per_line = dma_per_line;s_state->dma_desc_count = dma_desc_count;ESP_LOGD(TAG, "DMA buffer size: %d, DMA buffers per line: %d", buf_size, dma_per_line);ESP_LOGD(TAG, "DMA buffer count: %d", dma_desc_count);ESP_LOGD(TAG, "DMA buffer total: %d bytes", buf_size * dma_desc_count);s_state->dma_buf = (dma_elem_t**) malloc(sizeof(dma_elem_t*) * dma_desc_count);if (s_state->dma_buf == NULL) {return ESP_ERR_NO_MEM;}s_state->dma_desc = (lldesc_t*) malloc(sizeof(lldesc_t) * dma_desc_count);if (s_state->dma_desc == NULL) {return ESP_ERR_NO_MEM;}size_t dma_sample_count = 0;for (int i = 0; i < dma_desc_count; ++i) {ESP_LOGD(TAG, "Allocating DMA buffer #%d, size=%d", i, buf_size);dma_elem_t* buf = (dma_elem_t*) malloc(buf_size);if (buf == NULL) {return ESP_ERR_NO_MEM;}s_state->dma_buf[i] = buf;ESP_LOGV(TAG, "dma_buf[%d]=%p", i, buf);lldesc_t* pd = &s_state->dma_desc[i];pd->length = buf_size;if (s_state->sampling_mode == SM_0A0B_0B0C &&(i + 1) % dma_per_line == 0) {pd->length -= 4;}dma_sample_count += pd->length / 4;pd->size = pd->length;pd->owner = 1;pd->sosf = 1;pd->buf = (uint8_t*) buf;pd->offset = 0;pd->empty = 0;pd->eof = 1;pd->qe.stqe_next = &s_state->dma_desc[(i + 1) % dma_desc_count];}s_state->dma_sample_count = dma_sample_count;return ESP_OK;
}

函数说明:

  1. vsync_isr 函数:

    1. 当摄像头一帧数据发送完, VSYNC 由高变低触发中断
    2. 如果是多帧缓存的方式,将继续下一次采集
static void IRAM_ATTR vsync_isr(void* arg)
{GPIO.status1_w1tc.val = GPIO.status1.val;GPIO.status_w1tc = GPIO.status;bool need_yield = false;//if vsync is low and we have received some data, frame is doneif (_gpio_get_level(s_state->config.pin_vsync) == 0) {if(s_state->dma_received_count > 0) {signal_dma_buf_received(&need_yield);//ets_printf("end_vsync\n");if(s_state->dma_filtered_count > 1 || s_state->config.fb_count > 1) {i2s_stop(&need_yield);}}if(s_state->config.fb_count > 1 || s_state->dma_filtered_count < 2) {I2S0.conf.rx_start = 0;I2S0.in_link.start = 0;I2S0.int_clr.val = I2S0.int_raw.val;i2s_conf_reset();s_state->dma_desc_cur = (s_state->dma_desc_cur + 1) % s_state->dma_desc_count;//I2S0.rx_eof_num = s_state->dma_sample_count;I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[s_state->dma_desc_cur];I2S0.in_link.start = 1;I2S0.conf.rx_start = 1;s_state->dma_received_count = 0;}}if (need_yield) {portYIELD_FROM_ISR();}
}

函数说明:

  1. i2s_isr 函数:

    1. 当前 DMA 接收链表描述符被处理完成时将触发 I2S_IN_DONE_INT 中断,在中断服务函数中判断是否接收完成(非 JPEG 格式),并调用 signal_dma_buf_received 函数
static void IRAM_ATTR i2s_isr(void* arg)
{I2S0.int_clr.val = I2S0.int_raw.val;bool need_yield = false;signal_dma_buf_received(&need_yield);if (s_state->config.pixel_format != PIXFORMAT_JPEG&& s_state->dma_received_count == s_state->height * s_state->dma_per_line) {i2s_stop(&need_yield);}if (need_yield) {portYIELD_FROM_ISR();}
}

函数说明:

  1. signal_dma_buf_received 函数:

    1. 发出 DMA 接收完成信号,给出当前处理的 DMA 链表描述符索引
static void IRAM_ATTR signal_dma_buf_received(bool* need_yield)
{size_t dma_desc_filled = s_state->dma_desc_cur;s_state->dma_desc_cur = (dma_desc_filled + 1) % s_state->dma_desc_count;s_state->dma_received_count++;if(!s_state->fb->ref && s_state->fb->bad){*need_yield = false;return;}BaseType_t higher_priority_task_woken;BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &dma_desc_filled, &higher_priority_task_woken);if (ret != pdTRUE) {if(!s_state->fb->ref) {s_state->fb->bad = 1;}//ESP_EARLY_LOGW(TAG, "qsf:%d", s_state->dma_received_count);//ets_printf("qsf:%d\n", s_state->dma_received_count);}*need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE);
}

函数说明:

  1. dma_filter_task 函数:

    1. 接收 DMA 接收完成信号,判断是否为最后一次处理,是则调用 dma_finish_frame 函数,否则,调用 dma_filter_buffer 函数将 DMA BUF 中的数据放入图像数据缓冲区中
static void IRAM_ATTR dma_filter_task(void *pvParameters)
{s_state->dma_filtered_count = 0;while (true) {size_t buf_idx;if(xQueueReceive(s_state->data_ready, &buf_idx, portMAX_DELAY) == pdTRUE) {if (buf_idx == SIZE_MAX) {//this is the end of the framedma_finish_frame();} else {dma_filter_buffer(buf_idx);}}}
}

函数说明:

  1. dma_finish_frame 函数:

    1. 如果是脏数据重新采集一次数据
static void IRAM_ATTR dma_finish_frame()
{size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line;if(!s_state->fb->ref) {// is the frame bad?if(s_state->fb->bad){s_state->fb->bad = 0;s_state->fb->len = 0;*((uint32_t *)s_state->fb->buf) = 0;if(s_state->config.fb_count == 1) {i2s_start_bus();}} else {s_state->fb->len = s_state->dma_filtered_count * buf_len;if(s_state->fb->len) {//find the end marker for JPEG. Data after that can be discardedif(s_state->fb->format == PIXFORMAT_JPEG){uint8_t * dptr = &s_state->fb->buf[s_state->fb->len - 1];while(dptr > s_state->fb->buf){if(dptr[0] == 0xFF && dptr[1] == 0xD9 && dptr[2] == 0x00 && dptr[3] == 0x00){dptr += 2;s_state->fb->len = dptr - s_state->fb->buf;if((s_state->fb->len & 0x1FF) == 0){s_state->fb->len += 1;}if((s_state->fb->len % 100) == 0){s_state->fb->len += 1;}break;}dptr--;}}//send out the framecamera_fb_done();} else if(s_state->config.fb_count == 1){//frame was empty?i2s_start_bus();}}} else if(s_state->fb->len) {camera_fb_done();}s_state->dma_filtered_count = 0;
}

函数说明:

  1. dma_filter_buffer 函数:

    1. 调用 DMA BUF 处理函数,将数据转换(根据 DMA FIFO 模式)并放入图像数据缓冲区
    2. 通过检查第一次 DMA 采集的数据是否错误判断本次数据是否为脏数据
static void IRAM_ATTR dma_filter_buffer(size_t buf_idx)
{//no need to process the data if frame is in use or is badif(s_state->fb->ref || s_state->fb->bad) {return;}//check if there is enough space in the frame buffer for the new datasize_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line;size_t fb_pos = s_state->dma_filtered_count * buf_len;if(fb_pos > s_state->fb_size - buf_len) {//size_t processed = s_state->dma_received_count * buf_len;//ets_printf("[%s:%u] ovf pos: %u, processed: %u\n", __FUNCTION__, __LINE__, fb_pos, processed);return;}//convert I2S DMA buffer to pixel data(*s_state->dma_filter)(s_state->dma_buf[buf_idx], &s_state->dma_desc[buf_idx], s_state->fb->buf + fb_pos);//first frame bufferif(!s_state->dma_filtered_count) {//check for correct JPEG headerif(s_state->sensor.pixformat == PIXFORMAT_JPEG) {uint32_t sig = *((uint32_t *)s_state->fb->buf) & 0xFFFFFF;if(sig != 0xffd8ff) {//ets_printf("bad header\n");s_state->fb->bad = 1;return;}}//set the frame propertiess_state->fb->width = resolution[s_state->sensor.status.framesize][0];s_state->fb->height = resolution[s_state->sensor.status.framesize][1];s_state->fb->format = s_state->sensor.pixformat;}s_state->dma_filtered_count++;
}

函数说明:

  1. camera_fb_done 函数:

    1. 帧缓存完成,若是单帧缓存模式,将给出帧已经接收完的信号
    2. 若是多帧缓存模式,将当前的这一帧放入到出队的队列中,在其放入入队的队列前将不可用以缓存图像数据
static void IRAM_ATTR camera_fb_done()
{camera_fb_int_t * fb = NULL, * fb2 = NULL;BaseType_t taskAwoken = 0;if(s_state->config.fb_count == 1) {xSemaphoreGive(s_state->frame_ready);return;}fb = s_state->fb;if(!fb->ref && fb->len) {//add referencefb->ref = 1;//check if the queue is fullif(xQueueIsQueueFullFromISR(s_state->fb_out) == pdTRUE) {//pop frame buffer from the queueif(xQueueReceiveFromISR(s_state->fb_out, &fb2, &taskAwoken) == pdTRUE) {//free the popped bufferfb2->ref = 0;fb2->len = 0;//push the new frame to the end of the queuexQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken);} else {//queue is full and we could not pop a frame from it}} else {//push the new frame to the end of the queuexQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken);}} else {//frame was referenced or empty}//return buffers to be filledwhile(xQueueReceiveFromISR(s_state->fb_in, &fb2, &taskAwoken) == pdTRUE) {fb2->ref = 0;fb2->len = 0;}//advance frame buffer only if the current one has dataif(s_state->fb->len) {s_state->fb = s_state->fb->next;}//try to find the next free frame bufferwhile(s_state->fb->ref && s_state->fb->next != fb) {s_state->fb = s_state->fb->next;}//is the found frame buffer free?if(!s_state->fb->ref) {//buffer found. make sure it's emptys_state->fb->len = 0;*((uint32_t *)s_state->fb->buf) = 0;} else {//stay at the previous buffers_state->fb = fb;}
}

ESP-WHO(一)ESP32 摄像头驱动分析相关推荐

  1. s5k4ba摄像头驱动分析

    s5k4ba摄像头驱动分析 注释: 本驱动是基于S5PV310的,但是全天下的摄像头驱动都是采用V4L2,因此驱动框架流程基本差不多.其中fimc_init_camera()函数会回调.init函数, ...

  2. CMOS摄像头驱动分析

    CMOS摄像头驱动分析 文章目录 CMOS摄像头驱动分析 ov2640_probe_dt从设备树中获取ov2640的GPIO引脚并进行初始化 v4l2_i2c_subdev_init初始化v4l2子设 ...

  3. CMOS摄像头驱动分析-i2c驱动

    CMOS摄像头驱动分析-i2c驱动 文章目录 CMOS摄像头驱动分析-i2c驱动 设备树内容 module_i2c_driver宏分析 ov2640_i2c_driver ov2640_probe 设 ...

  4. 【Linux驱动】Linux--USB免驱摄像头驱动分析(基于5.4内核)

    Linux--USB免驱摄像头驱动分析(基于5.4内核) Linux摄像头系列文章 Linux--USB免驱摄像头驱动分析 一.UVC简介 二.UVC功能 三.如何写一个USB摄像头驱动 四.Linu ...

  5. 深入学习Linux摄像头(三)虚拟摄像头驱动分析

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  6. wince5.0 2440 BSP之摄像头驱动分析

    现在看驱动就看那几个接口函数即可,现在先看初始化函数. DWORD CIS_Init(DWORD dwContext) { DWORD dwErr = ERROR_SUCCESS, bytes; RE ...

  7. 通过虚拟驱动vivi分析摄像头驱动

    Linux摄像头驱动学习之:(二)通过虚拟驱动vivi分析摄像头驱动 一.通过指令 "strace -o xawtv.log xawtv" 得到以下调用信息: // 1~7都是在v ...

  8. linux v4l2系统详解,Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  9. 二十四、V4L2框架主要结构体分析和虚拟摄像头驱动编写

    一.V4L2框架主要结构体分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括两层 ...

最新文章

  1. python-字符串和文本
  2. mysql 查询某字段值全是数字
  3. Linux下编译build的命令,【linux基础】20、内核的编译
  4. 第三次学JAVA再学不好就吃翔(part1)--初识JAVA
  5. Android之如何设置背景的透明度
  6. 使用Elasticsearch和C#理解和实现CRUD APP的初学者教程——第2部分
  7. Python使用数学形态学方法处理图像
  8. 【程序员眼中的统计学(12)】相关与回归:我的线条如何?
  9. postgresql 9.1 下的 pg_dump 的初步研究
  10. tcp/ip协议初识
  11. lvds传输距离标准_电平标准整理
  12. python生成wifi字典_python生成密码字典的方法
  13. Window 平台下添加 tree 命令
  14. 关于Excel表格快捷键
  15. win7升级旗舰版密钥_WIN7无人值守应答文件的制作
  16. 将多个excel表合并到一个excel表
  17. Unity MMORPG游戏的设计(一)前期简单分析
  18. 求树的最大宽度(层次遍历法)
  19. C语言 键盘编码 及 用法
  20. python读书心得体会范文_读书心得体会范文10篇完美版

热门文章

  1. QT5制作MD5加密校验工具
  2. windows系统多线程同步机制原理总结
  3. (更新时间)2021年5月12日 redis数据库 Redis面试题
  4. 常用linux指令集
  5. 温湿度传感器——室内温湿度检测环境系统
  6. USB摄像头方案及应用
  7. STM32的升级--ICP/ISP/IAP以及Ymodem协议分析
  8. 非线性最优化问题求解器Ipopt介绍
  9. matlab stem 属性,matlab中stem函数用法_常见问题解析
  10. 2022年全球新兴市场物流整体竞争力排名中国、印度、阿联酋列前三 | 美通社头条...