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 条评论

发布
问题