Arbitrary Length Transmit and Receive - DMA
该示例使用 UART 和 DMA 与 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
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译 APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下 reset 按键,开始运行。
测试验证
准备阶段
启动 PuTTY 或 UartAssist 等 PC 终端,连接到使用的 COM 端口,并进行以下 UART 设置:
波特率: 115200
8 数据位
1 停止位
无校验
无硬件流控
测试阶段
若 PC 端发送的数据长度不超过 DMA 的 BufferSize(该示例中设置为 216),在接收数据完成后会触发 UART 中断,在 Debug Analyzer 内打印中断信息和收到的数据信息。
uart handler UART_FLAG_RX_IDLE value is 0x.. ...
若 PC 端发送的数据长度超过了 DMA 的 BufferSize(该示例中设置为 216),每搬运 216 个数据后,进入 DMA RX 中断,在 Debug Analyzer 内打印 DMA RX 中断信息。
GDMA0_Channel2_Handler
SoC 通过 DMA 将收到的数据重新发送回 PC 端。在 PC 端观察数据信息,在 Debug Analyzer 内打印 DMA TX 中断信息。
UART_TX_GDMA_Handler
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
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 设置,包含如下流程:
配置 PAD:设置引脚,PINMUX 模式,PowerOn,内部上拉,输出失能。
配置 PINMUX:配置引脚分别为 UART2_TX 和 UART2_RX 功能。
driver_uart_init
为 UART 外设的初始化,包含如下流程:
使能 RCC 时钟。
配置 UART 的波特率为 115200。
使能 UART TX/RX DMA 传输,配置对应的 waterLevel。
配置 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 外设的初始化,包含如下流程:
设置数据传输方向为外设到内存。
设置数据传输总量
GDMA_BLOCK_SIZE
为 216。设置源端地址固定,目的端地址自增。
设置数据宽度为 8 位,每次 burst 传输一个数据。
设置源端地址为
(&(UART2->RB_THR))
,目的端地址为GDMA_Rx_Buf
。配置 DMA 总传输完成中断
GDMA_INT_Transfer
。使能 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 外设的初始化,包含如下流程:
设置数据传输方向为内存到外设。
设置源端地址自增,目的端地址固定。
设置数据宽度为 8 位,每次 burst 传输一个数据。
设置源端地址为
GDMA_Tx_Buf
,目的端地址为(&(UART2->RB_THR))
。配置 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); }
功能实现
初始化阶段完成后,PC 端向 SoC 发送数据信息。SoC 端通过 DMA 接收 PC 端发来的数据。
若 PC 端发送的数据长度超过了设置的 BufferSize(示例中设置
GDMA_BLOCK_SIZE
为 216),则每搬运 216 个数据,触发 DMA RX 通道总传输完成中断,进入中断处理函数GDMA0_Channel2_Handler
。失能 DMA 传输。
记录收到的数据个数
receive_offset
和 DMA 总传输次数count
。将 DMA 接收到的数据
GDMA_Rx_Buf
保存至待发送数组GDMA_Tx_Buf
内。清除中断标志位,重新设置目的端地址,重新使能 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); }
若 PC 端发送的数据长度小于设置的 BufferSize(示例中设置
GDMA_BLOCK_SIZE
为 216),或剩余未接收的数据长度小于 BufferSize,则会触发 UART 接收空闲中断,进入中断处理函数UART2_Handler
。暂停 DMA RX 通道传输。
获取 DMA RX 通道传输的数据长度。
若有数据,则将数据保存至待发送数组
GDMA_Tx_Buf
内。失能 DMA RX 通道传输,清除 DMA RX 通道悬挂,重新初始化 DMA RX 通道,置位receiveflg
为true
。若无数据,清除 DMA RX 通道悬挂,置位
receiveflg
为true
。
清空 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); ... }
循环检测
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; } }
当 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); }