NXP EMDA学习(2):串口eDMA接收和发送流程详解

在单片机中,最基础的一个驱动就是串口,本文就以NXP中串口eDMA的收发为例,通过分析源代码来理解eDMA的执行过程。

  • 参考代码:Kinetis K64 Sub-Family SDK 2.11中的uart_edma_transfer.c

文章目录

  • 1 串口基本初始化
  • 2 DMAMUX初始化
  • 3 初始化EDMA模块
  • 4 串口eDMA发送和接收流程分析
    • 4.1 串口发送
      • 4.1.1 UART_SendEDMA
      • 4.1.2 串口数据发送完成中断
    • 4.2 串口接收
      • 4.2.1 UART_ReceiveEDMA
      • 4.2.2 串口数据接收中断
  • 5 总结

1 串口基本初始化

第一步肯定是初始化串口的引脚和时钟,还有波特率、奇偶校验位等参数。

/* 引脚和时钟初始化函数:可以用MCUXpresso生成 */
BOARD_InitBootPins();
BOARD_InitBootClocks();
/* 获取默认配置并初始化串口 */
UART_GetDefaultConfig(&uartConfig);
uartConfig.enableTx = uartConfig.enableRx = true;
UART_Init(UART0, &uartConfig, CLOCK_GetFreq(UART0_CLK_SRC));

2 DMAMUX初始化

前面没有介绍DMAMUX,顾名思义,这个模块的功能就是将DMA的某个通道映射到某一源上,从而形成一一对应的关系。

NXP EMDA学习(2):串口eDMA接收和发送流程详解_第1张图片

再来看看代码

/* 初始化DMAMUX,实际上是使能DMAMUX时钟 */
DMAMUX_Init(DMAMUX0);
/* Set channel for UART */
DMAMUX_SetSource(DMAMUX0, UART_TX_DMA_CHANNEL, UART_TX_DMA_REQUEST);
DMAMUX_SetSource(DMAMUX0, UART_RX_DMA_CHANNEL, UART_RX_DMA_REQUEST);
DMAMUX_EnableChannel(DMAMUX0, UART_TX_DMA_CHANNEL);
DMAMUX_EnableChannel(DMAMUX0, UART_RX_DMA_CHANNEL);
  • 在Kinetis K64芯片中,仅有一个DMAMUX和DMA0,故第一个参数恒为DMAMUX0
  • 第二个参数为DMA通道,在K64中支持16个DMA通道,所以随便填0~15即可,但不能重复
  • 第三个参数UART_TX_DMA_REQUESTUART_RX_DMA_REQUEST则是设置DMAMUX寄存器中的源字段的宏定义,将串口0发送和接收的源对应的值设置到寄存器中即可

上面两个函数实际上都是设置DMAMUX->CHCFG寄存器。

3 初始化EDMA模块

uart_edma_handle_t g_uartEdmaHandle;
edma_handle_t g_uartTxEdmaHandle;
edma_handle_t g_uartRxEdmaHandle;

/* 默认配置:关闭通道循环仲裁,关闭通道连接模式,关闭Debug模式,打开出错时停止EDMA工作的功能 */
EDMA_GetDefaultConfig(&config);
/* 使能DMA时钟,清除DMA中断,中断请求,错误标志,根据上面的默认配置设置DMA->CR寄存器 */
EDMA_Init(DMA0, &config);
/* 初始化EDMA的发送和接收Handle */
EDMA_CreateHandle(&g_uartTxEdmaHandle, DMA0, UART_TX_DMA_CHANNEL);
EDMA_CreateHandle(&g_uartRxEdmaHandle, DMA0, UART_RX_DMA_CHANNEL);
/* 用发送和接收Handle初始化为一个总的EDMA Handle */
UART_TransferCreateHandleEDMA(UART0, &g_uartEdmaHandle, UART_UserCallback, NULL, &g_uartTxEdmaHandle, &g_uartRxEdmaHandle);

(1)EDMA_CreateHandle函数:

typedef struct _edma_handle
{
    edma_callback callback; /*!< Callback function for major count exhausted. */
    void *userData;         /*!< Callback function parameter. */
    DMA_Type *base;         /*!< eDMA peripheral base address. */
    edma_tcd_t *tcdPool;    /*!< Pointer to memory stored TCDs. */
    uint8_t channel;        /*!< eDMA channel number. */
    volatile int8_t header; /*!< The first TCD index. Should point to the next TCD to be loaded into the eDMA engine. */
    volatile int8_t tail;   /*!< The last TCD index. Should point to the next TCD to be stored into the memory pool. */
    volatile int8_t tcdUsed; /*!< The number of used TCD slots. Should reflect the number of TCDs can be used/loaded in
                                the memory. */
    volatile int8_t tcdSize; /*!< The total number of TCD slots in the queue. */
    uint8_t flags;           /*!< The status of the current channel. */
} edma_handle_t;
-----
void EDMA_CreateHandle(edma_handle_t *handle, DMA_Type *base, uint32_t channel)
{
    uint32_t edmaInstance;
    uint32_t channelIndex;
    edma_tcd_t *tcdRegs;

    (void)memset(handle, 0, sizeof(*handle));
	/* 在K64中恒为DMA0 */
    handle->base    = base;
    /* DMA通道,取值范围为0~15 */
    handle->channel = (uint8_t)channel;
    /* 获取DMA的索引,DMAx则返回x,这里仅有DMA0,返回0 */
    edmaInstance = EDMA_GetInstance(base);
    /* 由于仅有一个DMA,故EDMA_GetInstanceOffset偏移返回0,故channelIndex=channel */
    channelIndex = (EDMA_GetInstanceOffset(edmaInstance) * (uint32_t)FSL_FEATURE_EDMA_MODULE_CHANNEL) + channel;
    /* fsl_edma.c中的全局静态变量,每个通道对应一个edma_handle_t */
    s_EDMAHandle[channelIndex] = handle;
    /* 使能通道x的DMA中断DMAx_IRQn */
    (void)EnableIRQ(s_edmaIRQNumber[edmaInstance][channel]);
	/* 复位通道对应的TCD内存 */
    tcdRegs            = (edma_tcd_t *)(uint32_t)&handle->base->TCD[handle->channel];
    tcdRegs->SADDR     = 0;
    tcdRegs->SOFF      = 0;
    tcdRegs->ATTR      = 0;
    tcdRegs->NBYTES    = 0;
    tcdRegs->SLAST     = 0;
    tcdRegs->DADDR     = 0;
    tcdRegs->DOFF      = 0;
    tcdRegs->CITER     = 0;
    tcdRegs->DLAST_SGA = 0;
    tcdRegs->CSR       = 0;
    tcdRegs->BITER     = 0;
}

(2)UART_TransferCreateHandleEDMA函数

typedef void (*uart_edma_transfer_callback_t)(UART_Type *base,uart_edma_handle_t *handle,
                                              status_t status, void *userData);
typedef struct _uart_edma_handle uart_edma_handle_t;
struct _uart_edma_handle
{
    uart_edma_transfer_callback_t callback; /*!< Callback function. */
    void *userData;                         /*!< UART callback function parameter.*/
    size_t rxDataSizeAll;                   /*!< Size of the data to receive. */
    size_t txDataSizeAll;                   /*!< Size of the data to send out. */

    edma_handle_t *txEdmaHandle; /*!< The eDMA TX channel used. */
    edma_handle_t *rxEdmaHandle; /*!< The eDMA RX channel used. */

    uint8_t nbytes; /*!< eDMA minor byte transfer count initially configured. */

    volatile uint8_t txState; /*!< TX transfer state. */
    volatile uint8_t rxState; /*!< RX transfer state */
};
-----
void UART_TransferCreateHandleEDMA(UART_Type *base,
                                   uart_edma_handle_t *handle,
                                   uart_edma_transfer_callback_t callback,
                                   void *userData,
                                   edma_handle_t *txEdmaHandle,
                                   edma_handle_t *rxEdmaHandle)
{
	/* 获得串口的索引,UARTx则返回x,这里传入UART0返回0 */
    uint32_t instance = UART_GetInstance(base);
	/* 在fsl_uart_edma.c中的全局静态变量,保存每个串口的基地址和EDMA Handle */
    s_edmaPrivateHandle[instance].base   = base;
    s_edmaPrivateHandle[instance].handle = handle;
	/* 清零该结构体 */
    (void)memset(handle, 0, sizeof(*handle));
	/* 设置EDMA的发送和接收状态为idle */
    handle->rxState = (uint8_t)kUART_RxIdle;
    handle->txState = (uint8_t)kUART_TxIdle;
	/* 保存前面初始化的DMA发送和接收Handle */
    handle->rxEdmaHandle = rxEdmaHandle;
    handle->txEdmaHandle = txEdmaHandle;
	/* 回调函数:在eDMA发送完成和接收满时调用,具体代码参考fsl_uart_edma.c */
    handle->callback = callback;
    /* 回调函数参数 */
    handle->userData = userData;
	/* 如果打开了DMA接收通道和串口的FIFO,应FIFO的Watermark为1,避免收数据不及时进不了中断 */
    if (rxEdmaHandle != NULL)
    {
        base->RWFIFO = 1U;
    }
    /* 在fsl_uart.c中的全局静态变量,保存每个串口的EDMA Handle,作为中断回调函数中的参数 */
    s_uartHandle[instance] = handle;
    /* 所有串口的中断处理函数,UARTx_RX_TX_DriverIRQHandler中调用 */
    s_uartIsr = UART_TransferEdmaHandleIRQ;
    /* 关闭串口的所有中断:空闲,overrun等标志位 */
    UART_DisableInterrupts(base, (uint32_t)kUART_AllInterruptsEnable);
    /* 使能串口的总中断,具体产生什么中断打开对应的标志位 */
    (void)EnableIRQ(s_uartIRQ[instance]);

    /* 设置TX EDMA Handle中的回调函数为SDK中的UART_SendEDMACallback,参数为总EDMA Handle */
    if (txEdmaHandle != NULL)
    {
        EDMA_SetCallback(handle->txEdmaHandle, UART_SendEDMACallback, &s_edmaPrivateHandle[instance]);
    }

    /* 设置RX EDMA Handle中的回调函数为SDK中的UART_ReceiveEDMACallback,参数为数为总EDMA Handle */
    if (rxEdmaHandle != NULL)
    {
        EDMA_SetCallback(handle->rxEdmaHandle, UART_ReceiveEDMACallback, &s_edmaPrivateHandle[instance]);
    }
}
  • 对于UART_TransferEdmaHandleIRQ来说,SDK中仅打开了发送完成中断,所以这个函数就是处理发送完成再调用设置的callback,如果还要打开空闲、overrun等中断,还需要修改此函数
  • UART_SendEDMACallbackUART_ReceiveEDMACallback后续分析

4 串口eDMA发送和接收流程分析

4.1 串口发送

4.1.1 UART_SendEDMA

在完成上面的初始化工作后,就可以调用UART_SendEDMA向串口发送数据了

uart_transfer_t xfer;
/* 数据地址和长度填充到xfer结构体中 */
xfer.data     = "12345678";
xfer.dataSize = 8;
UART_SendEDMA(UART0, &g_uartEdmaHandle, &xfer);

所以我们就来看看UART_SendEDMA中做了什么:

status_t UART_SendEDMA(UART_Type *base, uart_edma_handle_t *handle, uart_transfer_t *xfer)
{
    edma_transfer_config_t xferConfig;
    status_t status;
    /* 如果之前的发送还没结束则直接返回 */
    if ((uint8_t)kUART_TxBusy == handle->txState)
    {
        status = kStatus_UART_TxBusy;
    }
    else
    {
        handle->txState       = (uint8_t)kUART_TxBusy;
        handle->txDataSizeAll = xfer->dataSize;

        /* 设置传输参数结构体 */
        EDMA_PrepareTransfer(&xferConfig, xfer->data, sizeof(uint8_t), (uint32_t *)UART_GetDataRegisterAddress(base), sizeof(uint8_t), sizeof(uint8_t), xfer->dataSize, kEDMA_MemoryToPeripheral);
        /* 保存eDMA的次循环传输计数到UART handle */
        handle->nbytes = 1U;
        /* 提交传输 */
        (void)EDMA_SubmitTransfer(handle->txEdmaHandle, &xferConfig);
        EDMA_StartTransfer(handle->txEdmaHandle);
        /* 使能UART->C2的TIE位,即使能DMA传输 */
        UART_EnableTxDMA(base, true);
        
        status = kStatus_Success;
    }

    return status;
}

(1)EDMA_PrepareTransfer:填写edma_transfer_config_t结构体

void EDMA_PrepareTransfer(edma_transfer_config_t *config,
                          void *srcAddr,
                          uint32_t srcWidth,
                          void *destAddr,
                          uint32_t destWidth,
                          uint32_t bytesEachRequest,
                          uint32_t transferBytes,
                          edma_transfer_type_t type)
{
    int16_t srcOffset = 0, destOffset = 0;
	/* 根据传输的四种情况填写偏移量 */
    switch (type)
    {
        case kEDMA_MemoryToMemory:
            destOffset = (int16_t)destWidth;
            srcOffset  = (int16_t)srcWidth;
            break;
        case kEDMA_MemoryToPeripheral:
            destOffset = 0;
            srcOffset  = (int16_t)srcWidth;
            break;
        case kEDMA_PeripheralToMemory:
            destOffset = (int16_t)destWidth;
            srcOffset  = 0;
            break;
        case kEDMA_PeripheralToPeripheral:
            destOffset = 0;
            srcOffset  = 0;
            break;
        default:
            assert(false);
            break;
    }

    EDMA_PrepareTransferConfig(config, srcAddr, srcWidth, srcOffset, destAddr, destWidth, destOffset, bytesEachRequest, transferBytes);
}
-----
void EDMA_PrepareTransferConfig(edma_transfer_config_t *config,
                                void *srcAddr,
                                uint32_t srcWidth,
                                int16_t srcOffset,
                                void *destAddr,
                                uint32_t destWidth,
                                int16_t destOffset,
                                uint32_t bytesEachRequest,
                                uint32_t transferBytes)
{
    /* Initializes the configure structure to zero. */
    (void)memset(config, 0, sizeof(*config));
    /* 即UART_GetDataRegisterAddress(base),UART->D数据寄存器地址 */
    config->destAddr = (uint32_t)(uint32_t *)destAddr;
    /* 用户传输的数据的地址 */
    config->srcAddr  = (uint32_t)(uint32_t *)srcAddr;
    /* 即sizeof(uint8_t)=1 */
    config->minorLoopBytes   = bytesEachRequest;
    /* 主循环计数 = 传输的数据大小/1 */
    config->majorLoopCounts  = transferBytes / bytesEachRequest;
    /* srcWidth=destWidth=sizeof(uint8_t),
     * 获取TCD中对应传输长度所对应的宏,宏的值对应TCD寄存器设置的内容,这里为kEDMA_TransferSize1Bytes
     */
    config->srcTransferSize  = EDMA_TransferWidthMapping(srcWidth);
    /* 同上 */
    config->destTransferSize = EDMA_TransferWidthMapping(destWidth);
    /* 对于MemoryToPeripheral来说,destoffset为0 */
    config->destOffset       = destOffset;
    /* 对于MemoryToPeripheral来说,这里传输数据为按字节传输(minorLoopBytes),故为1 */
    config->srcOffset        = srcOffset;
}

(2)EDMA_SubmitTransfer:根据EDMA_PrepareTransfer中填写的edma_transfer_config_t结构体,设置DMA通道对应的TCD

status_t EDMA_SubmitTransfer(edma_handle_t *handle, const edma_transfer_config_t *config)
{
    edma_tcd_t *tcdRegs = (edma_tcd_t *)(uint32_t)&handle->base->TCD[handle->channel];
    /* 对于串口eDMA的例子中没有使用道tcdPool,故在这里省略else部分的代码 */
	if (handle->tcdPool == NULL)
    {
        /* 判断eDMA是否正在执行:(1)TCD->CSR.ACTIVE位=1
         * (2)TCD->CSR.ACTIVE位!=1 但 BITER!=CITER,表示主循环还没结束
         */
        if (((handle->base->TCD[handle->channel].CSR & DMA_CSR_ACTIVE_MASK) != 0U) ||
            (((handle->base->TCD[handle->channel].CITER_ELINKNO & DMA_CITER_ELINKNO_CITER_MASK) !=
              (handle->base->TCD[handle->channel].BITER_ELINKNO & DMA_BITER_ELINKNO_BITER_MASK))))
        {
            return kStatus_EDMA_Busy;
        }
        else
        {
        	/* 见下面分析 */
            EDMA_SetTransferConfig(handle->base, handle->channel, config, NULL);
            /* 使能TCD的DREQ,即主循环结束后由硬件清除DMA_ERQ寄存器中对应通道的DMA请求位 */
            handle->base->TCD[handle->channel].CSR |= DMA_CSR_DREQ_MASK;
            /* 当主循环结束后,产生一个中断 */
            handle->base->TCD[handle->channel].CSR |= DMA_CSR_INTMAJOR_MASK;

            return kStatus_Success;
        }
    }else{
    	/* 需要使用多个TCD,如scatter/gather模式,需要调用EDMA_InstallTCDMemory初始化tcdPool */
    	... 
    }
}
-----
void EDMA_SetTransferConfig(DMA_Type *base, uint32_t channel, const edma_transfer_config_t *config, edma_tcd_t *nextTcd)
{
    EDMA_TcdSetTransferConfig((edma_tcd_t *)(uint32_t)&base->TCD[channel], config, nextTcd);
}

/* 根据之前的config设置TCD相关寄存器的值,具体值的内容见EDMA_PrepareTransferConfig函数注释 */
void EDMA_TcdSetTransferConfig(edma_tcd_t *tcd, const edma_transfer_config_t *config, edma_tcd_t *nextTcd)
{
    /* 源地址SADDR */
    tcd->SADDR = config->srcAddr;
    /* 目标地址DADDR */
    tcd->DADDR = config->destAddr;
    /* 设置tcd->ATTR中的源数据传输大小SSIZE[10:8]和目标数据传输大小[2:0] */
    tcd->ATTR = DMA_ATTR_SSIZE(config->srcTransferSize) | DMA_ATTR_DSIZE(config->destTransferSize);
    /* 设置源地址的偏移,这里为1Byte */
    tcd->SOFF = (uint16_t)config->srcOffset;
    /* 目标地址的偏移,这里为0 */
    tcd->DOFF = (uint16_t)config->destOffset;
    /* 次循环计数值,这里为1;DMA->CR.EMLM默认为0,即没有打开在每次次循环结束后对源/目标地址的偏移增加 */
    tcd->NBYTES = config->minorLoopBytes;
    /* 设置DMA通道的主循环计数;tcd->CITER.ELINK默认为0,即不打开通道链接,故该值可以设置16位 */
    tcd->CITER = (uint16_t)config->majorLoopCounts;
    /* 同上:在每次主循环结束后,重新赋值给CITER */
    tcd->BITER = (uint16_t)config->majorLoopCounts;
    /* 使能scatter/gather特性:这里没有使用 */
    if (nextTcd != NULL)
    {
    	/* 主循环结束后,加到目标地址的补码 */
        tcd->DLAST_SGA = (uint32_t)nextTcd;
		/* 打开ESG(scatter/gather)特性,在该模式下需要清除DMA请求位 */
        tcd->CSR = (tcd->CSR | (uint16_t)DMA_CSR_ESG_MASK) & ~(uint16_t)DMA_CSR_DREQ_MASK;
    }
}

(3)EDMA_StartTransfer

void EDMA_StartTransfer(edma_handle_t *handle)
{
    if (handle->tcdPool == NULL)
    {
    	/* 设置DMA->SERQ[3:0]的SERQ位使能对应DMA通道的请求,在SERQ设置的内容会同步到ERQ寄存器中 */
        handle->base->SERQ = DMA_SERQ_SERQ(handle->channel);
    }
    else /* Use the TCD queue. */
    {
    	/* 没有使用到,后续在其他例子中进行分析 */
        ...
    }
}

4.1.2 串口数据发送完成中断

假设我们将一个uint8_t buffer[256]数组中的内容调用UART_SendEDMA请求发送了,硬件还需要一定时间来发送这些数据。在发送完成之前,如果你修改buffer中的数据,会导致发送的数据出现错误。所以我们需要知道eDMA的数据什么时候发送完毕。

UART_TransferCreateHandleEDMA中,有一行EDMA_SetCallback(handle->txEdmaHandle, UART_SendEDMACallback, &s_edmaPrivateHandle[instance]);设置了一个回调函数UART_SendEDMACallback,下面来看一下这个函数:

static void UART_SendEDMACallback(edma_handle_t *handle, void *param, bool transferDone, uint32_t tcds)
{
    uart_edma_private_handle_t *uartPrivateHandle = (uart_edma_private_handle_t *)param;
    /* Avoid the warning for unused variables. */
    handle = handle;
    tcds   = tcds;

    if (transferDone)
    {
        /* 失能DMA传输 */
        UART_EnableTxDMA(uartPrivateHandle->base, false);
        /* 关闭eDMA传输 */
        EDMA_AbortTransfer(handle);
        /* 使能串口发送完成中断 */
        UART_EnableInterrupts(uartPrivateHandle->base, (uint32_t)kUART_TransmissionCompleteInterruptEnable);
    }
}

很明显这个函数就是在eDMA执行完后,失能DMA传输再停止eDMA传输,最后再打开串口的发送完成中断。也就是说在执行到这个函数的时候,eDMA已经完成了数据的处理,接下来就是等待串口这边的响应。但还是眼见为实,我们想知道UART_SendEDMACallback是在什么时候被调用的,调用流程如下:

DMAx_DriverIRQHandler
	EDMA_HandleIRQ
		if (handle->tcdPool == NULL)
        {
            if (handle->callback != NULL)
            {
                (handle->callback)(handle, handle->userData, transfer_done, 0);
            }
        }

现在我们就恍然大悟,前面我们设置TCD的CSR寄存器时,打开了主循环传输结束中断,这样就会进入到上面的处理函数中。

现在我们就是等待串口这边传输结束,那么串口这边又是怎么处理的呢?流程如下:

UART0_RX_TX_DriverIRQHandler
	UART0_DriverIRQHandler
		s_uartIsr(UART0, s_uartHandle[0]);

前面在UART_TransferCreateHandleEDMA函数中设置了s_uartIsrUART_TransferEdmaHandleIRQ,现在来看一下这个函数:

void UART_TransferEdmaHandleIRQ(UART_Type *base, void *uartEdmaHandle)
{
    uart_edma_handle_t *handle = (uart_edma_handle_t *)uartEdmaHandle;
	/* 设置EDMA结构体的状态位idle */
    handle->txState = (uint8_t)kUART_TxIdle;
    /* 关闭发送完成中断(在每次主循环完成中断中打开) */
    UART_DisableInterrupts(base, (uint32_t)kUART_TransmissionCompleteInterruptEnable);

    if (handle->callback != NULL)
    {
        handle->callback(base, handle, kStatus_UART_TxIdle, handle->userData);
    }
}

其中callback就是前面UART_TransferCreateHandleEDMA中传的第三个参数UART_TransferCreateHandleEDMA,在本例程中,该函数如下:

void UART_UserCallback(UART_Type *base, uart_edma_handle_t *handle, status_t status, void *userData)
{
    userData = userData;

    if (kStatus_UART_TxIdle == status)
    {
        txOnGoing    = false;
    }
	/* 接收中断:稍后分析 */
    if (kStatus_UART_RxIdle == status)
    {
        rxOnGoing    = false;
    }
}

所以我们只要在发送前将txOnGoing设置为true,在调用UART_SendEDMA后阻塞判断这个变量直到其为false即表示发送完成。当然如果有操作系统的话,就在中断中释放一个信号量即可。

4.2 串口接收

4.2.1 UART_ReceiveEDMA

status_t UART_ReceiveEDMA(UART_Type *base, uart_edma_handle_t *handle, uart_transfer_t *xfer)
{
    edma_transfer_config_t xferConfig;
    status_t status;

    /* If previous RX not finished. */
    if ((uint8_t)kUART_RxBusy == handle->rxState)
    {
        status = kStatus_UART_RxBusy;
    }
    else
    {
        handle->rxState       = (uint8_t)kUART_RxBusy;
        handle->rxDataSizeAll = xfer->dataSize;
        /* Prepare transfer. */
        EDMA_PrepareTransfer(&xferConfig, (uint32_t *)UART_GetDataRegisterAddress(base), sizeof(uint8_t), xfer->data, sizeof(uint8_t), sizeof(uint8_t), xfer->dataSize, kEDMA_PeripheralToMemory);
        /* Store the initially configured eDMA minor byte transfer count into the UART handle */
        handle->nbytes = 1U;

        /* Submit transfer. */
        (void)EDMA_SubmitTransfer(handle->rxEdmaHandle, &xferConfig);
        EDMA_StartTransfer(handle->rxEdmaHandle);

        /* Enable UART RX EDMA. */
        UART_EnableRxDMA(base, true);
        status = kStatus_Success;
    }
    return status;
}

可以发现,UART_ReceiveEDMA的内容和UART_SendEDMA如出一辙,只不过传输方向变为kEDMA_PeripheralToMemory,源地址为UART->D,目标地址为用户设置的缓冲区,最后设置TCD由硬件完成这些操作。

4.2.2 串口数据接收中断

同样的,我们需要知道串口的数据什么时候来,什么时候我们可以去设置的buffer中取数据。和发送完成中断一样,在UART_TransferCreateHandleEDMA中,有一行EDMA_SetCallback(handle->rxEdmaHandle, UART_ReceiveEDMACallback, &s_edmaPrivateHandle[instance]);设置了一个回调函数UART_ReceiveEDMACallback,下面来看一下这个函数:

static void UART_ReceiveEDMACallback(edma_handle_t *handle, void *param, bool transferDone, uint32_t tcds)
{
    uart_edma_private_handle_t *uartPrivateHandle = (uart_edma_private_handle_t *)param;

    /* Avoid warning for unused parameters. */
    handle = handle;
    tcds   = tcds;

    if (transferDone)
    {
        /* 关闭eDMA传输 */
        UART_TransferAbortReceiveEDMA(uartPrivateHandle->base, uartPrivateHandle->handle);
        if (uartPrivateHandle->handle->callback != NULL)
        {
        	/* 即UART_UserCallback */
            uartPrivateHandle->handle->callback(uartPrivateHandle->base, uartPrivateHandle->handle, kStatus_UART_RxIdle,
                                                uartPrivateHandle->handle->userData);
        }
    }
}

同样地,在eDMA传输结束后,会调用该回调函数,最终调用的回调函数也是UART_UserCallback,当检测到rxOnGoing==false时,即有数据待处理。该函数的调用时机同样是:DMAx_DriverIRQHandler->EDMA_HandleIRQ

那么什么时候会调用这个接收完成中断呢,代码中也没有开启串口的中断,我们注意到UART_ReceiveEDMAxfer中有设置接收数据的大小,当eDMA接收到指定的这么多数据后,主循环计数为0,则会调用上面的回调函数。

  • 如果想实现串口的不定长数据接收,则应该打开串口的idle空闲中断
  • 对于大数据量的场合,很有必要打开overrun中断

5 总结

在上一篇文章介绍完TCD寄存器后,本文通过对eDMA串口的分析,大致了解了eDMA中对于TCD的配置,算是入门了。eDMA还有很多特性没有使用,后续将通过实际例程的代码分析其他的特性。

你可能感兴趣的:(NXP,嵌入式,嵌入式硬件,arm)