EFM32G UART 接收方向的 DMA+FIFO 处理方式

        通过 DMA 方式接收数据,可以最大限度地节省处理器资源,避免了频繁的 UART 接收中断开销。FIFO 是数据接收、解析中常见的一种数据结构,它可以有效地隔离数据接收、解析之间的耦合关系,简化处理方式,实际上是一种典型的 “生产者 — 消费者” 处理模式。

        EFM32G MCU 支持 UART 的 DMA 传输模式,但并不直接支持 FIFO 模式。我们可以在此基础上构建 DMA+FIFO 的接收、解析处理模式。如下图所示:通过 DMA 方式将 UART 接收数据放入内存中的环形缓存数组(当 DMA 写完数组尾单元后,写指针回到数组头、准备开始新一轮的写入操作);缓存数组的读指针则由一个定时器中断控制,下图假定为SysTick 中断,在定时中断中读取缓存的数据进行解析处理、并向后移动读指针(当读取到数组尾单元后,读指针回到数组头)。

        在采用定时中断读取缓存数据时,应当避免读指针超越写指针,否则会导致 FIFO 读溢出;在采用 DMA 方式写入缓存数据时,也应当避免写指针追上甚至超越读指针,否则会导致 FIFO 写溢出,但在 DMA 传输时无法实时比较读、写指针的相对位置,这里给出一种避免 FIFO 写溢出的思路:根据 UART 接收速率合理设置缓存数组大小、定时中断间隔,只要定时中断读取、解析缓存数组的平均速率大于 DMA 接收、写入缓存数组的平均速率,就可以保证 FIFO 不会被写溢出。

EFM32G UART 接收方向的 DMA+FIFO 处理方式_第1张图片

        下面给出相关的代码片段:

        (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;
    }
}

你可能感兴趣的:(EFM32G UART 接收方向的 DMA+FIFO 处理方式)