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 條評論

發布
問題