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++;