摄像头代码
Step:
dma_filter_task
将 DMA 数据转换成像素数据并存储到图像缓冲区,这里也会检查是否有脏数据产生,判断是否接收完一帧完整的图像函数说明:
esp_camera_init
函数:
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;
}
函数说明:
camera_probe
函数:
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 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);
#if CONFIG_OV2640_SUPPORT
} else {
//reset OV2640
SCCB_Write(0x30, 0xFF, 0x01);//bank sensor
SCCB_Write(0x30, 0x12, 0x80);//reset
vTaskDelay(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_SUPPORT
case OV2640_PID:
*out_camera_model = CAMERA_OV2640;
ov2640_init(&s_state->sensor);
break;
#endif
#if CONFIG_OV7725_SUPPORT
case OV7725_PID:
*out_camera_model = CAMERA_OV7725;
ov7725_init(&s_state->sensor);
break;
#endif
default:
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;
}
函数说明:
camera_init
函数:
dma_filter_task
将 DMA 数据转换成像素数据并存储到图像缓冲区,这里也会检查是否有脏数据产生,判断是否接收完一帧完整的图像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 YUYV
s_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 YUYV
s_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:
i2s_run()
,等待 VSYNC 信号变为高时,即表示摄像头端已经开始一帧图像传输,需要跳过 VSYNC 为低时的数据,再采集过程中出现 VSYNC 由高到低变化,则表示一帧图像传输完成,VSYNC 中断服务函数 vsync_isr
i2s_isr()
,中断服务函数中,首先清楚中断标志,之后通过 signal_dma_buf_received
函数发出一次 DMA 数据采集完成信号,若一帧图像数据接收完成就停止 i2sdma_filter_task
任务中会等待 DMA 数据采集完成信号,并处理 DMA buffer 中的数据放入图像缓冲区中函数说明:
i2s_run
函数:
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 frame
camera_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 vsync
ESP_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();
}
}
函数说明:
dma_desc_init
函数:
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;
}
函数说明:
vsync_isr
函数:
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 done
if (_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();
}
}
函数说明:
i2s_isr
函数:
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();
}
}
函数说明:
signal_dma_buf_received
函数:
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);
}
函数说明:
dma_filter_task
函数:
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 frame
dma_finish_frame();
} else {
dma_filter_buffer(buf_idx);
}
}
}
}
函数说明:
dma_finish_frame
函数:
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 discarded
if(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 frame
camera_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;
}
函数说明:
dma_filter_buffer
函数:
static void IRAM_ATTR dma_filter_buffer(size_t buf_idx)
{
//no need to process the data if frame is in use or is bad
if(s_state->fb->ref || s_state->fb->bad) {
return;
}
//check if there is enough space in the frame buffer for the new data
size_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 buffer
if(!s_state->dma_filtered_count) {
//check for correct JPEG header
if(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 properties
s_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++;
}
函数说明:
camera_fb_done
函数:
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 reference
fb->ref = 1;
//check if the queue is full
if(xQueueIsQueueFullFromISR(s_state->fb_out) == pdTRUE) {
//pop frame buffer from the queue
if(xQueueReceiveFromISR(s_state->fb_out, &fb2, &taskAwoken) == pdTRUE) {
//free the popped buffer
fb2->ref = 0;
fb2->len = 0;
//push the new frame to the end of the queue
xQueueSendFromISR(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 queue
xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken);
}
} else {
//frame was referenced or empty
}
//return buffers to be filled
while(xQueueReceiveFromISR(s_state->fb_in, &fb2, &taskAwoken) == pdTRUE) {
fb2->ref = 0;
fb2->len = 0;
}
//advance frame buffer only if the current one has data
if(s_state->fb->len) {
s_state->fb = s_state->fb->next;
}
//try to find the next free frame buffer
while(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 empty
s_state->fb->len = 0;
*((uint32_t *)s_state->fb->buf) = 0;
} else {
//stay at the previous buffer
s_state->fb = fb;
}
}