通过 DMA 方式接收数据,可以最大限度地节省处理器资源,避免了频繁的 UART 接收中断开销。FIFO 是数据接收、解析中常见的一种数据结构,它可以有效地隔离数据接收、解析之间的耦合关系,简化处理方式,实际上是一种典型的 “生产者 — 消费者” 处理模式。
EFM32G MCU 支持 UART 的 DMA 传输模式,但并不直接支持 FIFO 模式。我们可以在此基础上构建 DMA+FIFO 的接收、解析处理模式。如下图所示:通过 DMA 方式将 UART 接收数据放入内存中的环形缓存数组(当 DMA 写完数组尾单元后,写指针回到数组头、准备开始新一轮的写入操作);缓存数组的读指针则由一个定时器中断控制,下图假定为SysTick 中断,在定时中断中读取缓存的数据进行解析处理、并向后移动读指针(当读取到数组尾单元后,读指针回到数组头)。
在采用定时中断读取缓存数据时,应当避免读指针超越写指针,否则会导致 FIFO 读溢出;在采用 DMA 方式写入缓存数据时,也应当避免写指针追上甚至超越读指针,否则会导致 FIFO 写溢出,但在 DMA 传输时无法实时比较读、写指针的相对位置,这里给出一种避免 FIFO 写溢出的思路:根据 UART 接收速率合理设置缓存数组大小、定时中断间隔,只要定时中断读取、解析缓存数组的平均速率大于 DMA 接收、写入缓存数组的平均速率,就可以保证 FIFO 不会被写溢出。
下面给出相关的代码片段:
(1) 初始化 DMA
#define DMACHNL_CNT 2
#define DMACHNL_USART0_RX 0
#define RX_BUF_SIZE 256
/* DMA control block, must be aligned */
#if defined (__ICCARM__)
#pragma data_alignment = 256
DMA_DESCRIPTOR_TypeDef dma_ctrl_blk[DMACHNL_CNT * 2];
#elif defined (__CC_ARM)
DMA_DESCRIPTOR_TypeDef dma_ctrl_blk[DMACHNL_CNT * 2] __attribute__ ((aligned(256)));
#elif defined (__GNUC__)
DMA_DESCRIPTOR_TypeDef dma_ctrl_blk[DMACHNL_CNT * 2] __attribute__ ((aligned(256)));
#else
#error Undefined toolkit, need to define alignment
#endif
/* DMA callback structure, storing call-back function */
DMA_CB_TypeDef dma_cb[DMACHNL_CNT];
/* rx buffer array */
unsigned char rx_buf[RX_BUF_SIZE];
void init_dma(void)
{
/* Initializing the DMA */
DMA_Init_TypeDef dma_init =
{
.hprot = 0,
.controlBlock = dma_ctrl_blk,
};
DMA_Init(&dma_init);
/* Setting up call-back function */
dma_cb[DMACHNL_USART0_RX].cbFunc = dma_transfer_done; // call-back when DMA transfer cycle done
dma_cb[DMACHNL_USART0_RX].userPtr = NULL; // user-ptr for call-back function
/* Setting up channel */
DMA_CfgChannel_TypeDef cfg_chnl =
{
.highPri = false; // 优先级: default
.enableInt = true; // 使能回调函数
.select = DMAREQ_USART0_RXDATAV; // DMA 信号源
.cb = &(dma_cb[DMACHNL_USART0_RX]);
};
DMA_CfgChannel(DMACHNL_USART0_RX, &cfg_chnl);
/* Setting up channel descriptor */
DMA_CfgDescr_TypeDef cfg_descr =
{
.dstInc = dmaDataInc1; // 目的地址递增 1
.srcInc = dmaDataIncNone; // 源地址固定
.size = dmaDataSize1; // DMA transfer unit size(字节)
.arbRate = dmaArbitrate1;
.hprot = 0;
};
DMA_CfgDescr(DMACHNL_USART0_RX, true, &cfg_descr);
/*Starting basic transfer. */
DMA_ActivateBasic(DMACHNL_USART0_RX,
true, // primary
false, // no-burst
(void *)&rx_buf, // 目的地址
(void *)&(USART0->RXDATA), // 数据源
RX_BUF_SIZE - 1);
}
(2) DMA 回调函数。该函数使 DMA 写指针回到缓存数组头,准备开始新一轮写入操作。
void dma_transfer_done(unsigned int channel, bool primary, void *user)
{
(void)user;
DMA_ActivateBasic(DMACHNL_USART0_RX,
true,
false,
NULL,
NULL,
RX_BUF_SIZE - 1);
}
(3) 定时中断处理函数。
// SysTick IRQ-handler
volatile uint32_t rd_idx = 0;
void SysTick_Handler(void)
{
DMA_DESCRIPTOR_TypeDef* dma_descr = ((DMA_DESCRIPTOR_TypeDef*)(DMA->CTRLBASE)) + DMACHNL_USART0_RX;
uint32_t wr_idx = RX_BUF_SIZE - ((dma_descr->CTRL&_DMA_CTRL_N_MINUS_1_MASK)>>_DMA_CTRL_N_MINUS_1_SHIFT) - 1;
while(rd_idx != wr_idx)
{
/* 在这里读取 rx_buf[rd_idx]),并进行必要的解析处理 */
// ...
/* 更新读指针 */
rd_idx = (rd_idx+1) % RX_BUF_SIZE;
}
}