Unfixed length DMA

该示例使用 UARTDMA 与PC终端进行任意长度数据通信。 MCU使用DMA接收PC终端输入的数据,同时在 UART_FLAG_RX_IDLE 中断中获取任意长度数据长度、处理数据、置位received_flag。 一旦received_flag被设置,MCU使用DMA将收到的数据发送回PC终端。

环境需求

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

开发套件

Hardware Platforms

Board Name

RTL87x2G HDK

RTL87x2G EVB

更多信息请参考 快速入门

硬件连线

连接P3_0和RX,P3_1和TX。

编译和下载

该示例的工程路径如下:

Project file: samples\peripheral\uart\unfixedlen_gdma\proj\rtl87x2g\mdk

Project file: samples\peripheral\uart\unfixedlen_gdma\proj\rtl87x2g\gcc

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

  1. 打开工程文件。

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

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

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

  5. 按下复位按键,开始运行。

测试验证

准备阶段

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

  • 波特率: 115200

  • 8 数据位

  • 1 停止位

  • 无校验

  • 无硬件流控

测试阶段

  1. 当EVB启动后,在DebugAnalyzer工具内观察如下LOG。

    Start uart tx rx unfixedlen by gdma test!
    
  2. 在PC终端上输入字符串,并观察PC终端上是否出现相同的字符串。同时在DebugAnalyzer工具上会显示接收到的数据和中断信息。假设PC terminal输入数据长度为number,当number处于不同case时打印中断LOG情况如下:

    GDMA0_Channel4_Handler  /* time = (number / block_size) > 0 触发time次中断并打印LOG */
    
    UART_FLAG_RX_IDLE       /* time = (number % block_size) > 0 且满足 UART_FLAG_RX_IDLE 中断条件触发该中断并打印LOG */
    value is 0x..
    value is 0x..
    value is 0x..
    ...
    
    UART_TX_GDMA_Handler    /* 一旦received_flag被设置,MCU使用GDMA将number长度的数据发送回PC终端,发送完成触发该中断并打印LOG */
    

代码介绍

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

  1. 源码路径

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

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

源码路径

  • 工程路径: sdk\samples\peripheral\uart\unfixedlen_gdma\proj

  • 源码路径: sdk\samples\peripheral\uart\unfixedlen_gdma\src

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

└── Project: output_toggle
    └── secure_only_app
        └── Device                   includes startup code
            ├── startup_rtl.c
            └── system_rtl.c
        ├── CMSIS                    includes CMSIS header files
        ├── CMSE Library             Non-secure callable lib
        ├── lib                      includes all binary symbol files that user application is built on
            └── rtl87x2g_io.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl_gdma.c
            ├── rtl_nvic.c
            ├── rtl_pinmux.c
            ├── rtl_rcc.c
            └── rtl_uart.c
        └── APP                      includes the ble_peripheral user application implementation
            ├── io_uart.c
            └── main_ns.c

初始化

初始化流程包括了 board_uart_initdriver_uart_initdriver_gdma3_initdriver_gdma4_init


board_uart_init 中包含了PAD与PINMUX设置:

  1. 配置PAD:设置引脚、PINMUX模式、PowerOn、内部上拉。

  2. 配置PINMUX:分配引脚分别为UART3_TX、UART3_RX功能。


driver_uart_init 包含了对uart外设的初始化:

  1. 使能PCC时钟源。

  2. 设置波特率为115200,校验位为无奇偶校验,停止位为1位,数据长度为8bits。

  3. 使能UART DMA传输。

  4. 设置UART_TxWaterLevel为1,推荐设置为 GDMA_MSize

  5. 设置UART_RxWaterLevel为1,推荐设置为 GDMA_MSize

  6. 使能UART TX DMA, RX DMA。

  7. 使能UART 接收 IDLE 中断 UART_INT_RX_IDLE

RCC_PeriphClockCmd(APBPeriph_UART3, APBPeriph_UART3_CLOCK, ENABLE);
...
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_IdleTime       = UART_RX_IDLE_2BYTE; //idle interrupt wait time
UART_InitStruct.UART_TxWaterLevel   = 1; /* Better to equal: GDMA_MSize */
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(UART_DEMO, &UART_InitStruct);

UART_INTConfig(UART_DEMO, UART_INT_RX_IDLE | UART_INT_LINE_STS, ENABLE);
...

driver_gdma3_init 包含了对GDMA TX的初始化。

  1. 使用GDMA通道3。

  2. GDMA传输方向为内存到外设传输。

  3. 源端地址为 GDMA_Tx_Buf ,目的端地址为 (&(UART_DEMO->UART_RBR_THR)

  4. 使能GDMA通道3总传输完成中断 GDMA_INT_Transfer

GDMA_InitStruct.GDMA_ChannelNum          = UART_TX_GDMA_CHANNEL_NUM;
GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_MemoryToPeripheral;
...
GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)GDMA_Tx_Buf;
GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)(&(UART_DEMO->UART_RBR_THR));
GDMA_InitStruct.GDMA_DestHandshake       = GDMA_Handshake_UART3_TX;
GDMA_Init(UART_TX_GDMA_CHANNEL, &GDMA_InitStruct);
/* Enable transfer finish interrupt */
GDMA_INTConfig(UART_TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
...

driver_gdma4_init 包含了对GDMA RX的初始化。

  1. 使用GDMA通道4。

  2. GDMA传输方向为 外设 到 内存 传输。

  3. 源端地址为 (&(UART_DEMO->UART_RBR_THR) ,目的端地址为 GDMA_Rx_Buf

  4. 使能GDMA通道4总传输完成中断 GDMA_INT_Transfer

  5. 使能GDMA通道4传输。

GDMA_InitStruct.GDMA_ChannelNum          = UART_RX_GDMA_CHANNEL_NUM;
GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_PeripheralToMemory;
GDMA_InitStruct.GDMA_BufferSize          = GDMA_BLOCK_SIZE;
...
GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)(&(UART_DEMO->UART_RBR_THR));
GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)GDMA_Rx_Buf;
GDMA_InitStruct.GDMA_SourceHandshake     = GDMA_Handshake_UART3_RX;

GDMA_Init(UART_RX_GDMA_CHANNEL, &GDMA_InitStruct);
/* Enable transfer finish interrupt */
GDMA_INTConfig(UART_RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
...
GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, ENABLE);

功能实现

在 PC 终端上输入字符会启动从 UART RX FIFO 到内存的 GDMA 传输。

假设 PC 终端上的输入数据长度为 number, 对于不同情况的中断如下:

  • 如果 number / block_size > 0,则触发 n_time 次 GDMA_INT_Transfer 中断。

  • 如果 number % block_size > 0 且满足 UART_FLAG_RX_IDLE 中断条件(若在读完 RX FIFO 中的所有数据后的 RX 空闲超时时间内没有数据放入 RX FIFO),则触发 n_time 次 UART_INT_RX_IDLE 中断。

  1. 当触发 UART_FLAG_RX_IDLE 中断并进入UART中断处理函数 UART3_Handler 时:

    1. 暂停UART_RX_GDMA_CHANNEL通道传输。

    2. 关闭 UART_INT_RX_IDLE 中断。

    3. 获取GDMA传输长度(即GDMA接收到的数据长度)并将接收到的数据保存到GDMA_Tx_Buf中。

    4. 置位接收标志receive_offset以指示接收完成。

    5. 在恢复和禁用GDMA传输后重新初始化GDMA以接收下一个数据。

    6. 在清空UART FIFO后再次使能 UART_INT_RX_IDLE 中断。

  if (UART_GetFlagState(UART3, UART_FLAG_RX_IDLE) == SET)
  {
    DBG_DIRECT("UART_FLAG_RX_IDLE");

    /*  Suspend GDMA_Channel2   */
    GDMA_SuspendCmd(UART_RX_GDMA_CHANNEL, ENABLE);
    UART_INTConfig(UART3, UART_INT_RX_IDLE, DISABLE);

    data_len = GDMA_GetTransferLen(UART_RX_GDMA_CHANNEL);
    if (data_len)
    {
        receive_offset += data_len;
        memcpy(GDMA_Tx_Buf + GDMA_BLOCK_SIZE * count, GDMA_Rx_Buf, data_len);
        ...
        GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, DISABLE);
        GDMA_SuspendCmd(UART_RX_GDMA_CHANNEL, DISABLE);
        driver_gdma4_init();
        /* GDMA TX flag */
        receiveflg = true;
    }
    ......
    UART_ClearRxFIFO(UART3);
    UART_INTConfig(UART3, UART_INT_RX_IDLE, ENABLE);
  }
  ...
}
  1. 当触发 GDMA_INT_Transfer 中断并进入GDMA中断处理函数 GDMA0_Channel4_Handler 时:

    1. 关闭GDMA RX通道并清除GDMA通道3中断标志位。

    2. 将接收到的数据保存到GDMA_Tx_Buf中。

    3. 重新设置目的端地址并使能GDMA RX通道传输。

GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, DISABLE);
GDMA_ClearAllTypeINT(UART_RX_GDMA_CHANNEL_NUM);
...
memcpy(GDMA_Tx_Buf + GDMA_BLOCK_SIZE * (count - 1), GDMA_Rx_Buf, GDMA_BLOCK_SIZE);
...
GDMA_SetDestinationAddress(UART_RX_GDMA_CHANNEL, (uint32_t)GDMA_Rx_Buf);
GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, ENABLE);
  1. 一旦置位received_flag, 执行 GDMA_SetBufferSize() 更新GDMA TX Block Size, 执行 GDMA_Cmd() 即开始GDMA从Memory向UART TX FIFO传输数据。

    1. 设GDMA TX Block size为 PC终端输入字符数 number 。

    2. 重新使能GDMA TX通道。

while (1)
{
    if (receiveflg)
    {
        GDMA_SetBufferSize(UART_TX_GDMA_CHANNEL, receive_offset);
        GDMA_Cmd(UART_TX_GDMA_CHANNEL_NUM, ENABLE);
        ...
    }
}
  1. 当GDMA传输完成时,会触发 GDMA_INT_Transfer 中断,进入中断处理函数 GDMA0_Channel3_Handler

    1. 打印GDMA搬运UART接收数据信息。

    2. 关闭GDMA TX通道传输并清除GDMA TX通道中断标志位。

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