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

news2024/9/27 19:22:27

在单片机中,最基础的一个驱动就是串口,本文就以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的某个通道映射到某一源上,从而形成一一对应的关系。

在这里插入图片描述

再来看看代码

/* 初始化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还有很多特性没有使用,后续将通过实际例程的代码分析其他的特性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/139818.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go语言 WaitGroup 源码知多少

前面的文章我们写协程的时候有用到 WaitGroup 我们的写法大概是这样的 func main() {...dothing()wg : sync.WaitGroup{}// 控制 多个子协程的声明周期wg.Add(xx)for i : 0; i < xx; i {go func(ctx context.Context) {defer wg.Done()...dothing()}(ctx)}...dothing()// …

【Android春招】Android基础

一、填空题 1&#xff0e;Android是基于__ 的移动端开源操作系统。 Linux 2&#xff0e;Android系统是由__公司推出的。 谷歌 3&#xff0e;Android 11对应的API编号是__。 30 4&#xff0e;App除了在手机上运行&#xff0c;还能在电脑的__上运行。 模拟器&#xff08;AVD&…

Pytorch SoftMax回归

目录 数据集 从零实现 简单实现 数据集 导入所需库 torchvision计算机视觉所用torch的库 %matplotlib inline import torch import torchvision from torch.utils import data from torchvision import transforms from d2l import torch as d2l # 用SVG清晰度高 d2l.use_…

华为交换机OSPF对接思科交换机EIGRP,牛逼配置!

简介 思科交换机OSPF分别与华为交换机OSPF以及思科交换机EIGRP进行路由交互&#xff0c;间接实现华为交换机OSPF对接思科交换机EIGRP的功能。 配置注意事项 该案例适用于支持OSPF的华为交换机。该案例仅提供OSPF对接EIGRP的基本配置。思科交换机与华为交换机对接替换时&…

固定资产年结操作步骤 及常见问题处理:AJAB 关闭资产年度报错问题处理

目录 第一步、打开新的资产会计年度 TCODE &#xff1a; AJRW 第二步、 关闭资产年度 TCODE&#xff1a;AJAB 三、结果校验 四、 常见问题 分析 第一步、打开新的资产会计年度 TCODE &#xff1a; AJRW 输入公司编码&#xff0c;输入新的资产会计年度 先测试运行&a…

【计算机网络-数据链路层】差错控制(检错编码、纠错编码)

文章目录1 检错编码——奇偶校验码1.1 奇偶校验码1.2 相关例题2 检错编码——循环冗余码&#xff08;CRC&#xff09;2.1 发送端——生成冗余码2.2 接收端——检错2.3 相关例题3 纠错编码——海明码3.1 确定海明码的位数3.2 确定校验位的分布3.3 对校验码进行分组3.4 求出校验码…

LCHub:中国企业数字化门槛持续降低,数字化转型成本下降达80%

12月27日,钉钉联合中国信息通信研究院发布《“小快轻准”持续降低数字化转型门槛》研究报告(以下简称“报告”)。报告指出,中国企业数字化门槛正持续降低,数字化转型成本已降低80%;以钉钉为代表的数字平台,为中小企业提供了一条普惠、敏捷、低成本的数字化转型新路径;中国…

一种可远程监控的无线压力传感器

压力是工业生产中的重要参数之一&#xff0c;压力传感器是工业实践中最为常用的一种传感器。无线压力传感器TSM-04P是一款外接电源供电、具有无线通讯功能的高精度智能测压设备&#xff0c;采用4G通信方式&#xff0c;可选太阳能供电或电源供电。内置扩散硅传感器&#xff0c;能…

【java入门系列二】java基础

学习记录&#x1f914;变量&#xff08;小数计算为近似值&#xff09;运算符Scanner类接收输入进制位运算&#xff08;对补码进行操作再输出原码&#xff09;JavaAPI异常类型命名规范讨论总结谢谢点赞交流&#xff01;(❁◡❁)更多代码&#xff1a; Gitee主页&#xff1a;https…

Exynos_4412——ADC实验

目录 一、ADC简介 二、Exynos_4412下的ADC控制器 三、ADC寄存器详解 四、ADC编程 一、ADC简介 ADC(Analog to Digital Converter)即模数转换器&#xff0c;指一个能将模拟信号转化为数字信号的电子元件 对于CPU来说只能处理数字信号&#xff0c;而很多外围输入信号都是模拟…

CADD药物设计;QSAR模型

1、CADD药物设计 计算药物设计&#xff08;CADD&#xff09;是一个使用计算技术来帮助设计和开发新药的领域。它涉及使用计算机程序来模拟潜在药物分子与体内靶蛋白之间的相互作用&#xff0c;以及预测这些分子的性质和行为。这可以帮助研究人员识别新的药物候选物&#xff0c;…

STM32使用红外测温

红外测温 文章目录红外测温前言一、原理二、STM32代码1.MLX90614.c2.MLX90614.h总结前言 一、原理 红外测温的原理可以直接去看卖家的手册&#xff0c;手册多余的话太多了&#xff0c;知道他是IIC通信的就行了&#xff0c; 下面直接给出代码 二、STM32代码 1.MLX90614.c …

synchronzied

synchronzied的作用 原子性&#xff1a;所谓原子性就是一个操作或者多个操作&#xff0c;要么全部执行并且执行的过程不会被任何因素打断&#xff0c;要么都不执行。被synchronzied修饰的类或对象的所有操作都是原子的&#xff0c;因为在执行之前必须先获得类或对象的锁、直到…

直播运营|如何打造可复制的直播增长闭环?

作为当下最热门的营销模式&#xff0c;直播带货对人员、场地及流程的把控等都提出了严格要求。而要提升直播运营、促成更高转化&#xff0c;直播复盘是关键的一环。 那么&#xff0c;直播后到底该如何高效复盘&#xff0c;为带货提效呢&#xff1f; 「帷幄开播 Whale Cast」新功…

_Linux 进程信号-基础篇

文章目录信号入门1. 生活角度2. Linux技术应用角度3. 知识小点4. 信号概念5. kill -l命令6. 信号处理常见方式产生信号1. 通过终端按键产生信号Core Dump2. 调用系统函数向进程发信号系统调用接口概述3. 由软件条件产生信号软件条件给进程发送信号概述4. 硬件异常产生信号理解除…

VS2012编译libjson库过程

下载libjson库 https://sourceforge.net/projects/libjson/ 最新版是2012-06-25的libjson_7.6.1.zip,大小为759.5 kB 解压缩包 由于是旧版本VS创建的项目,无法用新版VS直接打开项目编译了 使用VS2012新建一个Win32项目libjson 选择静态库,点完成 创建完成后项目列表 将l…

【Linux】-- 操作系统进程的状态

目录 描述进程-PCB 状态理论 Linux内核源代码的描述 R运行状态与S睡眠状态&#xff1a; 前台进程与后台进程 D磁盘休眠状态&#xff1a; T停止状态 X死亡状态 Z(zombie)-僵尸进程 僵尸进程的危害 进程状态总结 孤儿进程 进程优先级 Linux具体的优先级做法 PRI …

driftingblues6靶机(脏牛漏洞)

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;463a 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2021.1 信息收集 1.arp-scan -l 探测目标靶机 2.nmap -p- -A -T4 192.168.1.107 探测目标靶机开放端口和服务 …

Internet Download Manager2023稳定版下载器

Internet Download Manager&#xff08;简称IDM&#xff09; 是Windows平台老牌而功能强大的下载工具&#xff0c;一种将下载速度提高多达5倍。那如果想要使用这款软件&#xff0c;那就需要安装这款软件&#xff0c;如何正确的下载和安装呢&#xff1f;今天&#xff0c;小编就教…

【ES】 es | Elasticsearch 教程 | DSL命令 | 命令操作es

一、说明 1、通过kibana操作es 2、使用dsl命令操作es 3、需要已经安装es&#xff0c;必须 4、需要已经安装kibana&#xff0c;非必须 5、若是没有装kibana&#xff0c;可以用PostMan之类的请求工具 二、基础知识 1、Elasticsearch和RDBMS比较 说明1&#xff1a; es与传统关系数…