esp-camera DMA及中断配置

ESP——Camera 配置DMA及其中断

一些数据:

cam_hal: buffer_size: 32768, half_buffer_size: 4096, node_buffer_size: 2048, node_cnt: 16, total_cnt: 3

DMA 配置

申请一段空间,让DMA用来存放数据:

cam_obj->dma_buffer = (uint8_t *)heap_caps_malloc(cam_obj->dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA);

申请完空间之后,配置DMA链表(in_link):

cam_obj->dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->dma_buffer);

进去看看:

static lldesc_t * allocate_dma_descriptors(uint32_t count, uint16_t size, uint8_t * buffer)
{
    lldesc_t *dma = (lldesc_t *)heap_caps_malloc(count * sizeof(lldesc_t), MALLOC_CAP_DMA);
    if (dma == NULL) {
        return dma;
    }

    for (int x = 0; x < count; x++) {
        dma[x].size = size;
        dma[x].length = 0;
        dma[x].sosf = 0;
        dma[x].eof = 0;
        dma[x].owner = 1;
        dma[x].buf = (buffer + size * x);
        dma[x].empty = (uint32_t)&dma[(x + 1) % count];
    }
    return dma;
}

可以看到,基本原理就是先申请一大段空间,然后再分成16份给每个in_link用。注意,这里的emptry是next descriptor address,虽然不知道为啥会用empty这个缩写。

/*
 *  SLC2 DMA Desc struct, aka lldesc_t
 *
 * --------------------------------------------------------------
 * | own | EoF | sub_sof | 5'b0   | length [11:0] | size [11:0] |
 * --------------------------------------------------------------
 * |            buf_ptr [31:0]                                  |
 * --------------------------------------------------------------
 * |            next_desc_ptr [31:0]                            |
 * --------------------------------------------------------------
 */

/* this bitfield is start from the LSB!!! */
typedef struct lldesc_s {
    volatile uint32_t size  : 12,
             length: 12,
             offset: 5, /* h/w reserved 5bit, s/w use it as offset in buffer */
             sosf  : 1, /* start of sub-frame */
             eof   : 1, /* end of frame */
             owner : 1; /* hw or sw */
    volatile const uint8_t *buf;       /* point to buffer data */
    union {
        volatile uint32_t empty;
        STAILQ_ENTRY(lldesc_s) qe;  /* pointing to the next desc */
    };
} lldesc_t;

总之就是把这些链表的节点串起来,并且首尾相接。

中断

esp-camera中主要有两种中断,分别是vsync中断和行中断。其中vsync显然是每帧开始时产生的中断,而行中断则是配置每接收1行后,产生中断。

bool ll_cam_start(cam_obj_t *cam, int frame_pos)
{
    I2S0.conf.rx_start = 0;

    I2S_ISR_ENABLE(in_suc_eof);

    I2S0.conf.rx_reset = 1;
    I2S0.conf.rx_reset = 0;
    I2S0.conf.rx_fifo_reset = 1;
    I2S0.conf.rx_fifo_reset = 0;
    I2S0.lc_conf.in_rst = 1;
    I2S0.lc_conf.in_rst = 0;
    I2S0.lc_conf.ahbm_fifo_rst = 1;
    I2S0.lc_conf.ahbm_fifo_rst = 0;
    I2S0.lc_conf.ahbm_rst = 1;
    I2S0.lc_conf.ahbm_rst = 0;

    I2S0.rx_eof_num = cam->dma_half_buffer_size / sizeof(dma_elem_t);
    I2S0.in_link.addr = ((uint32_t)&cam->dma[0]) & 0xfffff;

    I2S0.in_link.start = 1;
    I2S0.conf.rx_start = 1;
    return true;
}

这里rx_eof_num时配置成了1024,单位为字,换成字节还得X4,也就是4096字节,而上面每个DMA链表节点的空间是2048字节。
所以如果把rx_eof_num收满了,触发了suc_eof中断,应该会收满了2个DMA链表节点,不过一般不会收满,因为满一行就发生中断了,不会等到这个收满。

那么行中断时怎么实现的呢?

当ESP的I2S 为 Camera 从机接收模式时,并且当 I2Sn_H_SYNC、I2S_V_SYNC 和 I2S_H_REF 均为高电平时,认为
主机开始传输数据。因此,只要这三个信号有一个为低电平,就会中断DMA数据接收(也即完成一次DMA传输)

在中断处理函数中,可以看到,每次发生中断后,会发送一个event:

ll_cam_send_event(cam, CAM_IN_SUC_EOF_EVENT, &HPTaskAwoken);

在某个任务线程cam_task中,会处理这个event,主要就是将数据从DMA的buffer中搬到其他地方:

                if (cam_event == CAM_IN_SUC_EOF_EVENT) {
                    if(!cam_obj->psram_mode){
                        if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) {
                            ESP_LOGW(TAG, "FB-OVF");
                            ll_cam_stop(cam_obj);
                            DBG_PIN_SET(0);
                            continue;
                        }
                        frame_buffer_event->len += ll_cam_memcpy(cam_obj,
                            &frame_buffer_event->buf[frame_buffer_event->len],
                            &cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size],
                            cam_obj->dma_half_buffer_size);
                    }
                    //Check for JPEG SOI in the first buffer. stop if not found
                    if (cam_obj->jpeg_mode && cnt == 0 && cam_verify_jpeg_soi(frame_buffer_event->buf, frame_buffer_event->len) != 0) {
                        ll_cam_stop(cam_obj);
                        cam_obj->state = CAM_STATE_IDLE;
                    }
                    cnt++;

发表评论