Arbitrary Length Transmit and Receive - DMA

该示例使用 UARTDMA 与 PC 终端进行任意长度数据通信。

SoC 使用 DMA 接收 PC 终端输入的数据,同时在 UART_FLAG_RX_IDLE 中断中获取任意长度数据长度、处理数据、置位 received_flag

一旦 received_flag 被设置,SoC 使用 DMA 将收到的数据发送回 PC 终端。

环境需求

该示例支持以下开发套件:

开发套件

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

更多信息请参考 快速入门

硬件连线

连接 P3_0(UART TX)和 FT232 的 RX,P3_1(UART RX)和 FT232 的 TX。

编译和下载

该示例的工程路径如下:

Project file: board\evb\io_sample\UART\DMA_UnfixedLen\mdk

Project file: board\evb\io_sample\UART\DMA_UnfixedLen\gcc

请按照以下步骤操作构建并运行该示例:

  1. 打开工程文件。

  2. 按照 快速入门编译 APP Image 给出的步骤构建目标文件。

  3. 编译成功后,在路径 mdk\bingcc\bin 下会生成 app bin app_MP_xxx.bin 文件。

  4. 按照 快速入门MP Tool 给出的步骤将 app bin 烧录至 EVB 内。

  5. 按下 reset 按键,开始运行。

测试验证

准备阶段

启动 PuTTY 或 UartAssist 等 PC 终端,连接到使用的 COM 端口,并进行以下 UART 设置:

  • 波特率: 115200

  • 8 数据位

  • 1 停止位

  • 无校验

  • 无硬件流控

测试阶段

  1. 若 PC 端发送的数据长度不超过 DMA 的 BufferSize(该示例中设置为 216),在接收数据完成后会触发 UART 中断,在 Debug Analyzer 内打印中断信息和收到的数据信息。

    uart handler
    UART_FLAG_RX_IDLE
    value is 0x..
    ...
    
  2. 若 PC 端发送的数据长度超过了 DMA 的 BufferSize(该示例中设置为 216),每搬运 216 个数据后,进入 DMA RX 中断,在 Debug Analyzer 内打印 DMA RX 中断信息。

    GDMA0_Channel2_Handler
    
  3. SoC 通过 DMA 将收到的数据重新发送回 PC 端。在 PC 端观察数据信息,在 Debug Analyzer 内打印 DMA TX 中断信息。

    UART_TX_GDMA_Handler
    

代码介绍

该章节分为以下几个部分:

  1. 源码路径

  2. 初始化函数将在 初始化 章节介绍。

  3. 初始化后的功能实现将在 功能实现 章节介绍。

源码路径

  • 工程路径: sdk\board\evb\io_sample\UART\DMA_UnfixedLen

  • 源码路径: sdk\src\sample\io_sample\UART\DMA_UnfixedLen

该工程的工程文件代码结构如下:

└── Project: dma_unfixedlen
    └── secure_only_app
        └── include
            ├── app_define.h
            └── rom_uuid.h
        ├── cmsis                    includes CMSIS header files and startup files
            ├── overlay_mgr.c
            ├── system_rtl876x.c
            └── startup_rtl876x.s
        ├── lib                      includes all binary symbol files that user application is built on
            ├── rtl8752h_sdk.lib
            ├── gap_utils.lib
            └── ROM.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl876x_rcc.c
            ├── rtl876x_pinmux.c
            ├── rtl876x_nvic.c
            ├── rtl876x_gdma.c
            └── rtl876x_uart.c
        ├── profile
        └── app                      includes the ble_peripheral user application implementation
            └── main.c

初始化

当 EVB 复位启动时,执行 main 函数,包含 PAD/PINMUX 设置,UART 外设的初始化等流程。

int main(void)
{
    __enable_irq();
    board_uart_init();
    driver_uart_init();
//    uart_drv_dump_setting((uint32_t)UART2);
    /* GDMA Channel For Rx*/
    driver_gdma2_init();
    /* GDMA Channel For Tx*/
    driver_gdma3_init();

    ...
}

board_uart_init 为 UART 相关的 PAD/PINMUX 设置,包含如下流程:

  1. 配置 PAD:设置引脚,PINMUX 模式,PowerOn,内部上拉,输出失能。

  2. 配置 PINMUX:配置引脚分别为 UART2_TX 和 UART2_RX 功能。

driver_uart_init 为 UART 外设的初始化,包含如下流程:

  1. 使能 RCC 时钟。

  2. 配置 UART 的波特率为 115200。

  3. 使能 UART TX/RX DMA 传输,配置对应的 waterLevel。

  4. 配置 UART 接收空闲中断 UART_INT_RX_IDLE 和线接收状态中断 UART_INT_LINE_STS

void driver_uart_init(void)
{
    UART_DeInit(UART2);

    RCC_PeriphClockCmd(APBPeriph_UART2, APBPeriph_UART2_CLOCK, ENABLE);
    /* uart init */
    UART_InitTypeDef UART_InitStruct;
    UART_StructInit(&UART_InitStruct);

    UART_InitStruct.UART_Parity         = UART_PARITY_NO_PARTY;
    UART_InitStruct.UART_StopBits       = UART_STOP_BITS_1;
    UART_InitStruct.UART_WordLen        = UART_WORD_LENGTH_8BIT;
    UART_InitStruct.UART_RxThdLevel         = 8;      //1~29
    UART_InitStruct.UART_IdleTime       =
        UART_RX_IDLE_2BYTE;                    //idle interrupt wait time
    UART_InitStruct.UART_TxWaterLevel   = 15;
    UART_InitStruct.UART_RxWaterLevel   =
        1;                                                                                                     //Better to equal GDMA_MSize
    UART_InitStruct.TxDmaEn        = ENABLE;
    UART_InitStruct.RxDmaEn        = ENABLE;
    UART_InitStruct.dmaEn          = UART_DMA_ENABLE;

    UART_Init(UART2, &UART_InitStruct);
    UART_MaskINTConfig(UART2, UART_INT_RX_IDLE, DISABLE);
    UART_MaskINTConfig(UART2, UART_INT_ID_RX_LEVEL_REACH, DISABLE);
    UART_MaskINTConfig(UART2, UART_INT_LINE_STS, DISABLE);
    UART_INTConfig(UART2, UART_INT_RX_IDLE, ENABLE);
    UART_INTConfig(UART2, UART_INT_ID_RX_LEVEL_REACH, ENABLE);
    UART_INTConfig(UART2, UART_INT_LINE_STS, ENABLE);
    /*  Enable UART IRQ  */
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = UART2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    return;
}

driver_gdma2_init 为 DMA RX 外设的初始化,包含如下流程:

  1. 设置数据传输方向为外设到内存。

  2. 设置数据传输总量 GDMA_BLOCK_SIZE 为 216。

  3. 设置源端地址固定,目的端地址自增。

  4. 设置数据宽度为 8 位,每次 burst 传输一个数据。

  5. 设置源端地址为 (&(UART2->RB_THR)),目的端地址为 GDMA_Rx_Buf

  6. 配置 DMA 总传输完成中断 GDMA_INT_Transfer

  7. 使能 DMA RX 通道。

void driver_gdma2_init(void)
{
    /* Initialize GDMA */
    RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE);
    GDMA_InitTypeDef GDMA_InitStruct;
    GDMA_StructInit(&GDMA_InitStruct);
    GDMA_InitStruct.GDMA_ChannelNum          = DMA_RX_CHANNEL_NUM;
    GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_PeripheralToMemory;
    GDMA_InitStruct.GDMA_BufferSize          = GDMA_BLOCK_SIZE;
    GDMA_InitStruct.GDMA_SourceInc           = DMA_SourceInc_Fix;
    GDMA_InitStruct.GDMA_DestinationInc      = DMA_DestinationInc_Inc;
    GDMA_InitStruct.GDMA_SourceDataSize      = GDMA_DataSize_Byte;
    GDMA_InitStruct.GDMA_DestinationDataSize = GDMA_DataSize_Byte;
    GDMA_InitStruct.GDMA_SourceMsize         = GDMA_Msize_1;
    GDMA_InitStruct.GDMA_DestinationMsize    = GDMA_Msize_1;
    GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)(&(UART2->RB_THR));
    GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)GDMA_Rx_Buf;
    GDMA_InitStruct.GDMA_SourceHandshake     = GDMA_Handshake_UART2_RX;

    GDMA_Init(DMA_RX_CHANNEL, &GDMA_InitStruct);
    /* Enable transfer finish interrupt */
    GDMA_INTConfig(DMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
    /* Configure NVIC of GDMA */
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = DMA_RX_IRQ;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    GDMA_Cmd(DMA_RX_CHANNEL_NUM, ENABLE);
}

driver_gdma3_init 为 DMA TX 外设的初始化,包含如下流程:

  1. 设置数据传输方向为内存到外设。

  2. 设置源端地址自增,目的端地址固定。

  3. 设置数据宽度为 8 位,每次 burst 传输一个数据。

  4. 设置源端地址为 GDMA_Tx_Buf,目的端地址为 (&(UART2->RB_THR))

  5. 配置 DMA 总传输完成中断 GDMA_INT_Transfer

void driver_gdma3_init(void)
{
    /* Initialize GDMA */
    RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE);
    GDMA_InitTypeDef GDMA_InitStruct;
    GDMA_StructInit(&GDMA_InitStruct);
    GDMA_InitStruct.GDMA_ChannelNum          = DMA_TX_CHANNEL_NUM;
    GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_MemoryToPeripheral;
    GDMA_InitStruct.GDMA_BufferSize          = 0;
    GDMA_InitStruct.GDMA_SourceInc           = DMA_SourceInc_Inc;
    GDMA_InitStruct.GDMA_DestinationInc      = DMA_DestinationInc_Fix;
    GDMA_InitStruct.GDMA_SourceDataSize      = GDMA_DataSize_Byte;
    GDMA_InitStruct.GDMA_DestinationDataSize = GDMA_DataSize_Byte;
    GDMA_InitStruct.GDMA_SourceMsize         = GDMA_Msize_1;
    GDMA_InitStruct.GDMA_DestinationMsize    = GDMA_Msize_1;
    GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)GDMA_Tx_Buf;
    GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)(&(UART2->RB_THR));
    GDMA_InitStruct.GDMA_DestHandshake       = GDMA_Handshake_UART2_TX;

    GDMA_Init(DMA_TX_CHANNEL, &GDMA_InitStruct);
    /* Enable transfer finish interrupt */
    GDMA_INTConfig(DMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
    /* Configure NVIC of GDMA */
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = DMA_TX_IRQ;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

}

功能实现

  1. 初始化阶段完成后,PC 端向 SoC 发送数据信息。SoC 端通过 DMA 接收 PC 端发来的数据。

  2. 若 PC 端发送的数据长度超过了设置的 BufferSize(示例中设置 GDMA_BLOCK_SIZE 为 216),则每搬运 216 个数据,触发 DMA RX 通道总传输完成中断,进入中断处理函数 GDMA0_Channel2_Handler

    1. 失能 DMA 传输。

    2. 记录收到的数据个数 receive_offset 和 DMA 总传输次数 count

    3. 将 DMA 接收到的数据 GDMA_Rx_Buf 保存至待发送数组 GDMA_Tx_Buf 内。

    4. 清除中断标志位,重新设置目的端地址,重新使能 DMA RX 通道传输。

    void GDMA0_Channel2_Handler(void)
    {
        DBG_DIRECT("GDMA0_Channel2_Handler");
        /*  Clear interrupt */
        GDMA_Cmd(DMA_RX_CHANNEL_NUM, DISABLE);
        GDMA_ClearAllTypeINT(DMA_RX_CHANNEL_NUM);
        receive_offset += GDMA_BLOCK_SIZE;
        count += 1;
    
        /*print information*/
    //    for (uint32_t i = 0; i < GDMA_BLOCK_SIZE; i++)
    //    {
    //        DBG_DIRECT("Rxvalue is 0x%x", GDMA_Rx_Buf[i]);
    //    }
        memcpy(GDMA_Tx_Buf + GDMA_BLOCK_SIZE * (count - 1), GDMA_Rx_Buf, GDMA_BLOCK_SIZE);
    
        GDMA_ClearINTPendingBit(DMA_RX_CHANNEL_NUM, GDMA_INT_Transfer);
        /*  reset gdma param    */
        GDMA_SetDestinationAddress(DMA_RX_CHANNEL, (uint32_t)GDMA_Rx_Buf);
        GDMA_Cmd(DMA_RX_CHANNEL_NUM, ENABLE);
    }
    
  3. 若 PC 端发送的数据长度小于设置的 BufferSize(示例中设置 GDMA_BLOCK_SIZE 为 216),或剩余未接收的数据长度小于 BufferSize,则会触发 UART 接收空闲中断,进入中断处理函数 UART2_Handler

    1. 暂停 DMA RX 通道传输。

    2. 获取 DMA RX 通道传输的数据长度。

      1. 若有数据,则将数据保存至待发送数组 GDMA_Tx_Buf 内。失能 DMA RX 通道传输,清除 DMA RX 通道悬挂,重新初始化 DMA RX 通道,置位 receiveflgtrue

      2. 若无数据,清除 DMA RX 通道悬挂,置位 receiveflgtrue

    3. 清空 UART RX FIFO,重新使能 UART_INT_RX_IDLE 中断。

    void UART2_Handler(void)
    {
        DBG_DIRECT("uart handler");
        uint8_t tmp;
        uint32_t data_len = 0;
        uint32_t int_status = UART_GetIID(UART2);
        if (UART_GetFlagState(UART2, UART_FLAG_RX_IDLE) == SET)
        {
            DBG_DIRECT("UART_FLAG_RX_IDLE");
    
            /*  Suspend GDMA_Channel4   */
            GDMA_SuspendCmd(DMA_RX_CHANNEL, ENABLE);
            UART_INTConfig(UART2, UART_INT_RX_IDLE, DISABLE);
    
            data_len = GDMA_GetTransferLen(DMA_RX_CHANNEL);
            for (uint32_t i = 0; i < data_len; i++)
            {
                DBG_DIRECT("value is 0x%x", GDMA_Rx_Buf[i]);
            }
    
            if (data_len)
            {
                receive_offset += data_len;
                memcpy(GDMA_Tx_Buf + GDMA_BLOCK_SIZE * count, GDMA_Rx_Buf, data_len);
    
    #if NOT_ALLOW_DEINIT
                uint32_t time_out = 0x1f;
                while ((RESET == GDMA_GetSuspendChannelStatus(DMA_RX_CHANNEL)) && time_out)
                {
                    time_out--;
                }
                time_out = 0x0f;
                while ((RESET == GDMA_GetSuspendCmdStatus(DMA_RX_CHANNEL)) && time_out)
                {
                    time_out--;
                }
                GDMA_Cmd(DMA_RX_CHANNEL_NUM, DISABLE);
                GDMA_SuspendCmd(DMA_RX_CHANNEL, DISABLE);
    #else
                GDMA_DeInit();
    #endif
    
                driver_gdma2_init();
                /* GDMA TX flag */
                receiveflg = true;
            }
            /* Run here if data length = N * GDMA_BLOCK_SIZE,  */
            else
            {
                GDMA_SuspendCmd(DMA_RX_CHANNEL, DISABLE);
                receiveflg = true;
            }
            UART_ClearRxFIFO(UART2);
            UART_INTConfig(UART2, UART_INT_RX_IDLE, ENABLE);
        }
    
        UART_INTConfig(UART2, UART_INT_RD_AVA | UART_INT_RX_LINE_STS, DISABLE);
    
        ...
    }
    
  4. 循环检测 receive_flag 位。当检测到该标志位被置为 true 时,代表 SoC 端数据接收完毕。根据接收的数据长度设置 TX BufferSize,使能 DMA TX 通道,将数据发送回 PC 端。

while (1)
{
    if (receiveflg)
    {
        GDMA_SetBufferSize(DMA_TX_CHANNEL, receive_offset);
        GDMA_Cmd(DMA_TX_CHANNEL_NUM, ENABLE);
        receive_offset = 0;
        count = 0;
        receiveflg = false;
    }
}
  1. 当 DMA TX 搬运数据完毕时,触发 GDMA_INT_Transfer 中断,进入中断处理函数 GDMA0_Channel3_Handler ,失能 DMA TX 通道传输,清除 DMA TX 通道全部中断。

    void GDMA0_Channel3_Handler(void)
    {
        DBG_DIRECT("UART_TX_GDMA_Handler");
        GDMA_Cmd(DMA_TX_CHANNEL_NUM, DISABLE);
        GDMA_ClearAllTypeINT(DMA_TX_CHANNEL_NUM);
    }