Arbitrary Length Transmit and Receive - DMA
This example uses UART and DMA to communicate with the PC terminal for arbitrary length data transmission.
The SoC uses DMA to receive data input from the PC terminal, while obtaining the arbitrary length data, processing it, and setting the received_flag
in the UART_FLAG_RX_IDLE
interrupt.
Once the received_flag
is set, the SoC uses DMA to send the received data back to the PC terminal.
Requirements
The sample supports the following development kits:
Hardware Platforms |
Board Name |
---|---|
RTL8752H HDK |
RTL8752H EVB |
For more requirements, please refer to Quick Start.
Wiring
Connect P3_0 (UART TX) to the RX pin of the FT232 and P3_1 (UART RX) to the TX pin of the FT232.
Building and Downloading
This sample can be found in the SDK folder:
Project file: board\evb\io_sample\UART\DMA_UnfixedLen\mdk
Project file: board\evb\io_sample\UART\DMA_UnfixedLen\gcc
Please follow these steps to build and run the example:
Open sample project file.
To build the target, follow the steps listed on the Generating App Image in Quick Start.
After a successful compilation, the app bin
app_MP_xxx.bin
will be generated in the directorymdk\bin
orgcc\bin
.To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.
Press reset button on EVB board and it will start running.
Experimental Verification
Preparation Phase
Launch PuTTY or UartAssist or other PC terminals, connect to the used COM port, and configure the following UART settings:
Baud rate: 115200
8 data bits
1 stop bit
No parity
No hardware flow control
Testing Phase
If the data length sent by the PC does not exceed the DMA BufferSize (set to 216 in this example), a UART interrupt will be triggered after receiving the data, and the interrupt information and received data information will be printed in the Debug Analyzer.
uart handler UART_FLAG_RX_IDLE value is 0x.. ...
If the data length sent by the PC exceeds the DMA BufferSize (set to 216 in this example), a DMA RX interrupt will be triggered after every 216 data transfers, and the DMA RX interrupt information will be printed in the Debug Analyzer.
GDMA0_Channel2_Handler
The SoC sends the received data back to the PC via DMA. Observe the data information on the PC, and the DMA TX interrupt information will be printed in the Debug Analyzer.
UART_TX_GDMA_Handler
Code Overview
This chapter will be introduced according to the following several parts:
Peripheral initialization will be introduced in chapter Initialization.
Functional implementation after initialization will be introduced in chapter Function Implementation.
Source Code Directory
Project directory:
sdk\board\evb\io_sample\UART\DMA_UnfixedLen
Source code directory:
sdk\src\sample\io_sample\UART\DMA_UnfixedLen
Source files are currently categorized into several groups as below.
└── 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
Initialization
When the EVB is reset, execute the main
function, which includes PAD/PINMUX settings, UART peripheral initialization, and other processes.
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
is for PAD/PINMUX settings, including the following process:
Configure PAD: set pins, PINMUX mode, PowerOn, internal pull-up, and disable output.
Configure PINMUX: configure pins for UART2_TX and UART2_RX functions.
driver_uart_init
is for UART peripheral initialization, including the following process:
Enable the RCC clock.
Configure the UART baud rate to 115200.
Enable UART TX/RX DMA transfer, and configure the corresponding waterLevel.
Configure UART receive idle interrupt
UART_INT_RX_IDLE
and line receive status interruptUART_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
is the initialization of the DMA RX peripheral, which includes the following steps:
Set the data transfer direction from peripheral to memory.
Set the total data transfer size
GDMA_BLOCK_SIZE
to 216.Set the source address fixed and the destination address auto-incremented.
Set the data width to 8 bits, transferring one data per burst.
Set the source address to
(&(UART2->RB_THR))
and the destination address toGDMA_Rx_Buf
.Configure the DMA transfer complete interrupt
GDMA_INT_Transfer
.Enable the DMA RX channel.
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
is the initialization for DMA TX peripheral, which includes the following process:
Set the data transfer direction from memory to peripheral.
Set the source address to auto-increment, and the destination address to be fixed.
Set the data width to 8 bits, with each burst transferring one data.
Set the source address to
GDMA_Tx_Buf
, and the destination address to(&(UART2->RB_THR))
.Configure the DMA transfer complete interrupt
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); }
Functional Implementation
After the initialization phase is completed, the PC sends data information to the SoC. The SoC receives the data from the PC via DMA.
If the length of the data sent by the PC exceeds the set BufferSize (in the example,
GDMA_BLOCK_SIZE
is set to 216), then for every 216 pieces of data transferred, a DMA RX channel total transfer completion interrupt is triggered, entering the interrupt handler functionGDMA0_Channel2_Handler
.Disable DMA transfer.
Record the number of received data
receive_offset
and the total number of DMA transferscount
.Save the data received by the DMA
GDMA_Rx_Buf
into the array to be sentGDMA_Tx_Buf
.Clear the interrupt flag, reset the destination address, and re-enable DMA RX channel transfer.
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); }
If the data length sent from the PC end is less than the configured BufferSize (in the example,
GDMA_BLOCK_SIZE
is set to 216), or the length of the remaining unreceived data is less than BufferSize, it will trigger the UART receive idle interrupt and enter the interrupt handling functionUART2_Handler
.Pause DMA RX channel transmission.
Obtain the length of data transmitted by the DMA RX channel.
If there is data, save the data to the array
GDMA_Tx_Buf
to be sent. Disable the DMA RX channel transmission, clear the DMA RX channel pending, reinitialize the DMA RX channel, and setreceiveflg
totrue
.If there is no data, clear the DMA RX channel pending and set
receiveflg
totrue
.
Clear the UART RX FIFO and re-enable the
UART_INT_RX_IDLE
interrupt.
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); ... }
Loop to check the
receive_flag
bit. When this flag bit is detected to be set totrue
, it indicates that data reception on the SoC side is complete. Set the TX BufferSize according to the length of the received data, enable the DMA TX channel, and send the data back to the 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; } }
When the DMA TX data transfer is completed, it triggers the
GDMA_INT_Transfer
interrupt, enters the interrupt handler functionGDMA0_Channel3_Handler
, disables the DMA TX channel transmission, and clears all interrupts for the DMA TX channel.void GDMA0_Channel3_Handler(void) { DBG_DIRECT("UART_TX_GDMA_Handler"); GDMA_Cmd(DMA_TX_CHANNEL_NUM, DISABLE); GDMA_ClearAllTypeINT(DMA_TX_CHANNEL_NUM); }