Fixed Length Transmit and Receive - DMA

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

SoC 使用 DMA 将数据发送到 PC 终端程序(如 PUTTY 或 UartAssist),DMA 将数据从 Memory 传输到 UART TX FIFO 外设。

SoC 使用 DMA 接收 PC 终端输入的数据,DMA 将数据从 UART RX FIFO 外设传输到 Memory。

一旦 DMA 传输完成,SoC 将缓冲数据发送回 PC 终端。

在该示例中,PC 终端程序必须发送大于等于 UART_RX_GDMA_BUFFER_SIZE 个字节的数据,才会触发 DMA 中断。

环境需求

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

开发套件

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\GDMA\mdk

Project file: board\evb\io_sample\UART\GDMA\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. EVB 复位后,SoC 首先发送 100 个字节的数据至 PC 端,其中后 10 个字节为 FF。随后 SoC 通过 DMA 发送 4095 个字节的数据(该示例中 RX Buffer Size UART_RX_GDMA_BUFFER_SIZE 设置为 4095,数据从 0x00-0xFF 循环),观察 PC 终端上出现的数据信息,在 Debug Analyzer 内打印 DMA TX 中断信息。

    TX DMA HANDLER
    
  2. 在 PC 中断上输入超过 4095 个字节的数据(该示例中 RX Buffer Size UART_RX_GDMA_BUFFER_SIZE 设置为 4095),会触发 DMA 接收中断,SoC 会将相同数据发回至 PC 端,观察 PC 终端上出现的数据信息,在 Debug Analyzer 内打印 DMA RX 中断信息。

    RX DMA HANDLER
    

代码介绍

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

  1. 源码路径

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

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

源码路径

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

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

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

└── Project: gdma
    └── 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
            ├── ancs.c
            ├── app.c
            ├── app_task.c
            └── io_uart.c

初始化

当 EVB 复位启动时,调用 main() 函数,将执行以下流程:

int main(void)
{
    extern uint32_t random_seed_value;
    srand(random_seed_value);
    global_data_init();
    board_init();
    le_gap_init(APP_MAX_LINKS);
    gap_lib_init();
    app_le_gap_init();
    app_le_profile_init();
    pwr_mgr_init();
    task_init();
    os_sched_start();

    return 0;
}

备注

le_gap_init()gap_lib_init()app_le_gap_initapp_le_profile_init 等为 privacy 管理模块相关的初始化,参考 LE Peripheral Privacy 中的初始化流程介绍。

与外设相关的初始化流程具体如下:

  1. board_init 中,执行 board_uart_init ,该函数为 PAD/PINMUX 设置,包含如下流程:

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

    2. 配置 PINMUX:配置引脚分别为 UART0_TX 和 UART0_RX 功能。

  2. 在执行 os_sched_start() 开启任务调度后,在 app_main_task 主任务内,执行 driver_init 对外设驱动进行初始化配置。

  3. driver_init 中执行 driver_uart_init ,该函数为 UART 外设的初始化,包含如下流程:

    1. 使能 RCC 时钟。

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

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

    void driver_uart_init(void)
    {
        UART_DeInit(UART0);
    
        RCC_PeriphClockCmd(APBPeriph_UART0, APBPeriph_UART0_CLOCK, ENABLE);
    
        /* uart init */
        UART_InitTypeDef UART_InitStruct;
        UART_StructInit(&UART_InitStruct);
    
        UART_InitStruct.UART_Div            = BaudRate_Table[BAUD_RATE_115200].div;
        UART_InitStruct.UART_Ovsr           = BaudRate_Table[BAUD_RATE_115200].ovsr;
        UART_InitStruct.UART_OvsrAdj        = BaudRate_Table[BAUD_RATE_115200].ovsr_adj;
    
        UART_InitStruct.UART_RxThdLevel     = 16;                      //1~29
        UART_InitStruct.UART_IdleTime       = UART_RX_IDLE_2BYTE;      //idle interrupt wait time
        UART_InitStruct.UART_DmaEn          = UART_DMA_ENABLE;
        UART_InitStruct.UART_TxWaterLevel   = 15;     //Better to equal TX_FIFO_SIZE(16)- GDMA_MSize
        UART_InitStruct.UART_RxWaterLevel   = 1;      //Better to equal GDMA_MSize
    #if (UART_GDMA_TX_ENABLE == EVB_ENABLE)
        UART_InitStruct.UART_TxDmaEn   = ENABLE;
    #endif
    #if (UART_GDMA_RX_ENABLE == EVB_ENABLE)
        UART_InitStruct.UART_RxDmaEn   = ENABLE;
    #endif
        UART_Init(UART0, &UART_InitStruct);
    }
    
  4. 在主函数中执行 os_sched_start() ,开启任务调度。当 stack 准备好时,执行 app_handle_dev_state_evt ,执行 io_uart_demo

  5. io_uart_demo 内,执行 UART_SendDataByGDMAUART_ReceiveDataByGDMA 对 DMA TX/RX 外设进行初始化。

  6. UART_SendDataByGDMA 内对 DMA TX 外设进行初始化,执行以下步骤:

    1. 初始化 DMA 发送数组。

    2. 设置数据传输方向为内存至外设。

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

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

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

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

    7. 使能 DMA TX,开始发送数据。

    void UART_SendDataByGDMA(void)
    {
        /*--------------initialize test buffer which for sending data to UART---------------------*/
        for (uint32_t i = 0; i < UART_TX_GDMA_BUFFER_SIZE; i++)
        {
            GDMA_SendData_Buffer[i] = 0x10 + i;
        }
    
        /*--------------GDMA init-----------------------------*/
        GDMA_InitTypeDef GDMA_InitStruct;
        GDMA_StructInit(&GDMA_InitStruct);
        GDMA_InitStruct.GDMA_ChannelNum      = UART_TX_GDMA_CHANNEL_NUM;
        GDMA_InitStruct.GDMA_DIR             = GDMA_DIR_MemoryToPeripheral;
        GDMA_InitStruct.GDMA_BufferSize      = UART_TX_GDMA_BUFFER_SIZE;//determine total transfer size
        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_SendData_Buffer;
        GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)(&(UART0->RB_THR));
        GDMA_InitStruct.GDMA_DestHandshake   = GDMA_Handshake_UART0_TX;
        GDMA_InitStruct.GDMA_ChannelPriority = 2;//channel prority between 0 to 5
        GDMA_Init(UART_TX_GDMA_CHANNEL, &GDMA_InitStruct);
    
        GDMA_INTConfig(UART_TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
    
        /*-----------------GDMA IRQ init-------------------*/
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = UART_TX_GDMA_CHANNEL_IRQN;
        NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    
        /*-----------------start to send data-----------*/
        GDMA_Cmd(UART_TX_GDMA_CHANNEL_NUM, ENABLE);
    }
    
  7. UART_ReceiveDataByGDMA 内对 DMA RX 外设进行初始化,执行以下步骤:

    1. 初始化 DMA 接收数组。

    2. 设置数据传输方向为外设至内存。

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

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

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

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

    7. 使能 DMA RX,开始接收数据。

    void UART_ReceiveDataByGDMA(void)
    {
        /*--------------initialize test buffer which for sending data to UART---------------------*/
        for (uint32_t i = 0; i < UART_RX_GDMA_BUFFER_SIZE; i++)
        {
            GDMA_ReceiveData_Buffer[i] = 0;
        }
    
        /*--------------GDMA init-----------------------------*/
        GDMA_InitTypeDef GDMA_InitStruct;
        GDMA_StructInit(&GDMA_InitStruct);
        GDMA_InitStruct.GDMA_ChannelNum         = UART_RX_GDMA_CHANNEL_NUM;
        GDMA_InitStruct.GDMA_DIR                = GDMA_DIR_PeripheralToMemory;
        GDMA_InitStruct.GDMA_BufferSize         = UART_RX_GDMA_BUFFER_SIZE;//determine total transfer 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)(&(UART0->RB_THR));
        GDMA_InitStruct.GDMA_SourceHandshake    = GDMA_Handshake_UART0_RX;
        GDMA_InitStruct.GDMA_DestinationAddr    = (uint32_t)GDMA_ReceiveData_Buffer;
        GDMA_InitStruct.GDMA_ChannelPriority    = 2;//channel prority between 0 to 5
        GDMA_Init(UART_RX_GDMA_CHANNEL, &GDMA_InitStruct);
    
        GDMA_INTConfig(UART_RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
    
        /*-----------------GDMA IRQ init-------------------*/
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = UART_RX_GDMA_CHANNEL_IRQN;
        NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    
        /*-----------------start to send data-----------*/
        GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, ENABLE);
    }
    

功能实现

  1. io_uart_demo 中,首先执行 UART_SendData_Continuous ,发送 100 个字节的数据至 PC 端,在 PC 端串口助手可以看到发送数据的信息。

  2. 执行 UART_SendDataByGDMA ,DMA TX 通道将预设的 4095 个字节的数据发送至 PC 端,在 PC 端串口助手可以看到发送数据的信息。

  3. 执行 UART_ReceiveDataByGDMA ,开启 DMA RX 通道进行数据接收。当 PC 端发送的数据大于等于 4095 个字节时,触发 DMA 中断,进入中断处理函数 UART_RX_GDMA_Handler

    1. 清除中断标志位。

    2. 定义消息类型为 IO_MSG_TYPE_GDMA,消息子类型为 1,发送消息至 app task。主 task 检测到消息后,执行 io_handle_gdma_msg,对消息进行处理。

    3. 若检测到的子消息类型为 1,代表是 DMA RX 中断的消息处理,执行 UART_SendData_Continuous,将 DMA 接收到的数据发回至 PC 端。

    void UART_RX_GDMA_Handler(void)
    {
        GDMA_ClearINTPendingBit(UART_RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer);
    
        DBG_DIRECT("RX DMA HANDLER");
        T_IO_MSG int_uart_msg;
    
        int_uart_msg.type = IO_MSG_TYPE_GDMA;
        int_uart_msg.subtype = 1;
        int_uart_msg.u.buf = (void *)(GDMA_SendData_Buffer);
    
        if (false == app_send_msg_to_apptask(&int_uart_msg))
        {
            APP_PRINT_ERROR0("[io_adc]UART_GDMA_Channel_Handler: Send int_uart_msg failed!");
            //Add user code here!
    
            GDMA_ClearINTPendingBit(UART_TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer);
            return;
        }
    }
    
    void io_handle_gdma_msg(T_IO_MSG *io_gdma_msg)
    {
        uint16_t subtype = io_gdma_msg->subtype;
        if (subtype == 0)
        ...
        else if (subtype == 1)
        {
            APP_PRINT_INFO0("io_handle_gdma_msg: uart rx done ");
            UART_SendData_Continuous(UART0, GDMA_ReceiveData_Buffer, UART_RX_GDMA_BUFFER_SIZE);
        }
    }