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