W800如何使用DMA的链表模式

发布于 2024-04-02 15:10:49

W800的DMA驱动里并没有链表的使用方式,如果在开发过程中想要使用链表模式,可以按照如下方式实现。
首先了解下链表模式,寄存器手册中有相关说明,类似于双buff的功能,只不过这里可能是多个buff,通过链表的方式串在了一起,我们要做的就是让硬件能找到这些链表里每个buff的地址以及按照对应的设置来收发数据。
17.png

链表里有一个结构体是依据DMA寄存器来定义的,如下所示。valid值代表链表的这一项是否有有效数据,值可以设定为0或者0x80000000。

typedef struct _wm_dma_desc
{
    unsigned int valid;
    unsigned int dma_ctrl;
    unsigned int src_addr;
    unsigned int dest_addr;
    struct _wm_dma_desc * next;
}wm_dma_desc;

当valid为0时,对于发送来说,表示该buff空闲可用写入数据,写完后需要将该buff的valid值设置为0x80000000,硬件会在发送完后将该值置为0。
当valid为0时,对于接收来说,表示该buff已经被硬件接收到数据了,需要应用层来将数据处理,处理完后需要将valid的值设置为0x80000000,等下次硬件接收完数据后,会再次将valid的值置为0。
dma_ctrl为DMA数据流控制寄存器的值左移一位。需要设置数据长度、DMA搬运单位、每次搬运几个单位、源地址/目的地址是否自加。注意要整体左移一位。
src_addr代表要搬运的数据源地址。
dest_addr代表要搬运的数据目的地址。
next为指向的下一个链表地址,要注意,最后一个链表的next指向,如果指向了第一个链表的地址,则会循环发送,如果指向了NULL,则是单次发送,检测到NULL后就停止了。
这里以寄存器直接设置为例,尽量不使用DMA驱动里的接口,以I2S发送一定长度的16bit数据为例说明,使用双buff的方式循环发送。

static wm_dma_desc g_dma_desc_tx[2];
static uint32_t dma_channel = 4;
void DmaInit(uint16_t *data1, uint32_t len1, uint16_t *data2, uint32 len2)
{
    uint32_t dma_ctr = 0;
    wm_dma_desc *dma_desc = &g_dma_desc_tx[0];
    
    // 设置第一个buff的控制寄存器和结构体
    dma_ctr  = ((len1 / 2) << 8) | DMA_CTRL_BURST_SIZE1 | DMA_CTRL_DATA_SIZE_WORD | DMA_CTRL_SRC_ADDR_INC;
    dma_desc[0].valid = 0;
    dma_desc[0].dma_ctrl = dma_ctr >> 1;
    dma_desc[0].src_addr = (uint32_t)data1;
    dma_desc[0].dest_addr = HR_I2S_TX;
    dma_desc[0].next = &dma_desc[1];
    
    // 设置第二个buff的控制寄存器和结构体
    dma_ctr &= ~0xFFFF00;
    dma_ctr |= (len2 << 8);
    dma_desc[1].valid = 0;
    dma_desc[1].dma_ctrl = dma_ctr >> 1;
    dma_desc[1].src_addr = (uint32_t)data2;
    dma_desc[1].dest_addr = HR_I2S_TX;
    dma_desc[1].next = &dma_desc[0];
    
    // 打开DMA的时钟
    tls_open_peripheral_clock(TLS_PERIPHERAL_TYPE_DMA);
    // 设置模式寄存器,使能链表模式、硬件模式、I2S发送
    DMA_MODE_REG(dma_channel) = DMA_MODE_SEL_I2S_TX | DMA_MODE_CHAIN_MODE | DMA_MODE_HARD_MODE | DMA_MODE_CHAIN_LINK_EN;
    // 设置链表的起始地址
    tls_reg_write32(HR_DMA_CHNL0_LINK_DEST_ADDR + 0x30 * dma_channel, (uint32_t)dma_desc);
    // 使能DMA中断并注册回调函数
    tls_dma_irq_register(dma_channel, i2s_DMA_TX_Channel_IRQHandler, NULL, TLS_DMA_IRQ_TRANSFER_DONE);
    // 启动DMA
    DMA_CHNLCTRL_REG(dma_channel) = DMA_CHNL_CTRL_CHNL_ON;
}

中断回调函数的实现如下:

void i2s_DMA_TX_Channel_IRQHandler(void *p)
{
    wm_dma_desc *dma_desc = &g_dma_desc_tx[0];
    if(dma_desc[0].valid == 0)
    {
        dma_desc[0].valid = 0x80000000;
        // 这里可以发消息给任务,往buff0里填充数据
    }
    else if(dma_desc[1].valid == 0)
    {
        dma_desc[1].valid = 0x80000000;
        // 这里可以发消息给任务,往buff1里填充数据
    }
}

启动传输可以向buff0和buff1里分别填充数据后将对应的valid设置为0x80000000即可开始传输,当传输完成后会调用相关的中断回调函数,在中断函数里,已经将valid的值修改为0x80000000了,所以在处理中只需要填充数据即可,填充数据的速度要大于发送的速度,这样才不会出错,也可以将valid的值修改放在处理中。
接收的初始化接口和发送的是一样的,只需要将DMA_MODE_SEL_I2S_TX 修改为DMA_MODE_SEL_I2S_RX,中断回调函数i2s_DMA_TX_Channel_IRQHandler修改为i2s_DMA_RX_Channel_IRQHandler。中断回调函数的实现是一样的,valid的值修改也可放在数据处理的任务中。

void i2s_DMA_RX_Channel_IRQHandler(void *p)
{
    wm_dma_desc *dma_desc = &g_dma_desc_tx[0];
    if(dma_desc[0].valid == 0)
    {
        // 这里可以发消息给任务,处理buff0中的数据
        dma_desc[0].valid = 0x80000000;
    }
    else if(dma_desc[1].valid == 0)
    {
        // 这里可以发消息给任务,处理buff1中的数据
        dma_desc[1].valid = 0x80000000;
    }
}
1 条评论

发布
问题