Memory To Memory - Multi Block
This example uses the Multi Block feature of DMA for memory-to-memory data transfer.
When the total size of the transferred data exceeds 65535, the Multi Block feature can be used.
The Multi Block feature uses a block link list for the transfer, which allows larger data to be divided into multiple link list items for the transfer, ensuring that the data size of each link list item does not exceed 65535.
Requirements
The sample supports the following development kits:
Hardware Platforms |
Board Name |
---|---|
RTL8752H HDK |
RTL8752H EVB |
For more requirements, please refer to Quick Start.
Configurations
The macros that can be configured in this example are as follows:
GDMA_INTERRUPT_MODE
: Configure this macro to select the interrupt mode of DMA. The selectable values are as follows:
INT_TRANSFER
: Configure this macro to choose whether to enable theGDMA_INT_Transfer
interrupt.INT_BLOCK
: Configure this macro to choose whether to enable theGDMA_INT_Block
interrupt.
Building and Downloading
This sample can be found in the SDK folder:
Project file: board\evb\io_sample\GDMA\Mem2Mem_multi_block\mdk
Project file: board\evb\io_sample\GDMA\Mem2Mem_multi_block\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
After the EVB resets, the DMA begins to transfer data. Once the data transfer is complete, a transfer completion message will be displayed in the Debug Analyzer.
If
INT_TRANSFER
is enabled, the log will be printed as follows:[io_gdma] io_handle_gdma_msg: GDMA transfer data completion!
If
INT_BLOCK
is enabled, the following log will be printed each time a block transfer is completed, with the total number of prints equal to the value ofGDMA_MULTIBLOCK_SIZE
.[io_gdma] io_handle_gdma_msg: GDMA block0 transfer data completion! [io_gdma] io_handle_gdma_msg: GDMA block1 transfer data completion! ... [io_gdma] io_handle_gdma_msg: GDMA blockx transfer data completion!
Note
If an error is detected in the transferred data, observe the error information in the Debug Analyzer.
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\GDMA\Mem2Mem_multi_block
Source code directory:
sdk\src\sample\io_sample\GDMA\Mem2Mem_multi_block
Source files are currently categorized into several groups as below.
└── Project: adc_continuous_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_nvic.c
└── rtl876x_gdma.c
├── profile
└── app includes the ble_peripheral user application implementation
├── main.c
├── ancs.c
├── app.c
├── app_task.c
└── io_gdma.c
Initialization
After the EVB resets, the main()
function is called, and the following process will be executed:
int main(void)
{
extern uint32_t random_seed_value;
srand(random_seed_value);
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;
}
Note
le_gap_init()
, gap_lib_init()
, app_le_gap_init
, and app_le_profile_init
are related to the initialization of the privacy management module. Refer to the initialization process description in LE Peripheral Privacy.
The specific initialization process related to peripherals is as follows:
After executing
os_sched_start()
to start task scheduling, in theapp_main_task
main task, executedriver_init
to initialize and configure the peripheral drivers.when
driver_gdma_init
is called indriver_init
function to initialize the DMA peripheral, the typical process includes:
For basic DMA initialization, refer to Initialization in Memory to Memory - Single Block.
Configure the source and destination address that are automatically loaded from LLI structure after each block transmission.
Enable multi-block transmission.
Configure head address of LLI structure.
Configure source and destination address, link list pointer and controller register in LLI structure after each block transmission.
If the
INT_TRANSFER
configuration is enabled, enable theGDMA_INT_Transfer
interrupt; If theINT_BLOCK
configuration is enabled, enable theGDMA_INT_Block
interrupt.void driver_gdma_init(void) { uint32_t i, j = 0; /*--------------Initialize test buffer---------------------*/ for (i = 0; i < GDMA_TRANSFER_SIZE; i++) { for (j = 0; j < GDMA_MULTIBLOCK_SIZE; j++) { GDMA_Send_Buffer[j][i] = (i + j) & 0xff; GDMA_Recv_Buffer[j][i] = 0; } } RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE); GDMA_InitTypeDef GDMA_InitStruct; GDMA_StructInit(&GDMA_InitStruct); GDMA_InitStruct.GDMA_ChannelNum = GDMA_CHANNEL_NUM; GDMA_InitStruct.GDMA_DIR = GDMA_DIR_MemoryToMemory; GDMA_InitStruct.GDMA_BufferSize = GDMA_TRANSFER_SIZE;//determine total transfer size GDMA_InitStruct.GDMA_SourceInc = DMA_SourceInc_Inc; 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)GDMA_Send_Buffer; GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)GDMA_Recv_Buffer; GDMA_InitStruct.GDMA_Multi_Block_Mode = GDMA_MULTIBLOCK_MODE;//LLI_TRANSFER; GDMA_InitStruct.GDMA_Multi_Block_En = 1; GDMA_InitStruct.GDMA_Multi_Block_Struct = (uint32_t)GDMA_LLIStruct; for (uint32_t i = 0; i < GDMA_MULTIBLOCK_SIZE; i++) { if (i == GDMA_MULTIBLOCK_SIZE - 1) { //GDMA_LLIStruct[i].LLP=0; GDMA_LLIStruct[i].SAR = (uint32_t)GDMA_Send_Buffer[i]; GDMA_LLIStruct[i].DAR = (uint32_t)GDMA_Recv_Buffer[i]; GDMA_LLIStruct[i].LLP = 0; /* Configure low 32 bit of CTL register */ GDMA_LLIStruct[i].CTL_LOW = BIT(0) | (GDMA_InitStruct.GDMA_DestinationDataSize << 1) | (GDMA_InitStruct.GDMA_SourceDataSize << 4) | (GDMA_InitStruct.GDMA_DestinationInc << 7) | (GDMA_InitStruct.GDMA_SourceInc << 9) | (GDMA_InitStruct.GDMA_DestinationMsize << 11) | (GDMA_InitStruct.GDMA_SourceMsize << 14) | (GDMA_InitStruct.GDMA_DIR << 20); /* Configure high 32 bit of CTL register */ GDMA_LLIStruct[i].CTL_HIGH = GDMA_InitStruct.GDMA_BufferSize; } else { GDMA_LLIStruct[i].SAR = (uint32_t)GDMA_Send_Buffer[i]; GDMA_LLIStruct[i].DAR = (uint32_t)GDMA_Recv_Buffer[i]; GDMA_LLIStruct[i].LLP = (uint32_t)&GDMA_LLIStruct[i + 1]; /* Configure low 32 bit of CTL register */ GDMA_LLIStruct[i].CTL_LOW = BIT(0) | (GDMA_InitStruct.GDMA_DestinationDataSize << 1) | (GDMA_InitStruct.GDMA_SourceDataSize << 4) | (GDMA_InitStruct.GDMA_DestinationInc << 7) | (GDMA_InitStruct.GDMA_SourceInc << 9) | (GDMA_InitStruct.GDMA_DestinationMsize << 11) | (GDMA_InitStruct.GDMA_SourceMsize << 14) | (GDMA_InitStruct.GDMA_DIR << 20) | (GDMA_InitStruct.GDMA_Multi_Block_Mode & LLP_SELECTED_BIT); /* Configure high 32 bit of CTL register */ GDMA_LLIStruct[i].CTL_HIGH = GDMA_InitStruct.GDMA_BufferSize; } } GDMA_Init(GDMA_Channel, &GDMA_InitStruct); /* GDMA irq config */ NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = GDMA_Channel_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = (FunctionalState)ENABLE; NVIC_InitStruct.NVIC_IRQChannelPriority = 3; NVIC_Init(&NVIC_InitStruct); /** Either single block transmission completion interruption or transmission completion interruption can be choose. * Synchronized modifications are also required in GDMA_Channel_Handler if a single block transmission interrupt is used. */ #if (GDMA_INTERRUPT_MODE == INT_TRANSFER) GDMA_INTConfig(GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE); #elif (GDMA_INTERRUPT_MODE == INT_BLOCK) GDMA_INTConfig(GDMA_CHANNEL_NUM, GDMA_INT_Block, ENABLE); #endif }
Functional Implementation
Execute
os_sched_start()
to start task scheduling. When the stack is ready, executeGDMA_Cmd()
to start GDMA transmission inapp_handle_dev_state_evt
function.void app_handle_dev_state_evt(T_GAP_DEV_STATE new_state, uint16_t cause) { ... if (gap_dev_state.gap_init_state != new_state.gap_init_state) { if (new_state.gap_init_state == GAP_INIT_STATE_STACK_READY) { APP_PRINT_INFO0("GAP stack ready"); /*stack ready*/ GDMA_Cmd(GDMA_CHANNEL_NUM, ENABLE); } } ... }
If
INT_TRANSFER
is enabled, when the transfer is completed, it triggers theGDMA_INT_Transfer
interrupt and enters the interrupt handling function.Disable the
GDMA_INT_Transfer
interrupt.Define the message type
IO_MSG_TYPE_GDMA
and send a message to the task. In the main task, process the sent message data.Clear the
GDMA_INT_Transfer
interrupt flag.Execute
io_handle_gdma_msg
, print the data transfer completion information, and check if GDMA_Recv_Buf and GDMA_Send_Buf are the same. If they are not, print the error data.
void GDMA_Channel_Handler(void) { GDMA_INTConfig(GDMA_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE); T_IO_MSG int_gdma_msg; int_gdma_msg.type = IO_MSG_TYPE_GDMA; int_gdma_msg.subtype = 0; if (false == app_send_msg_to_apptask(&int_gdma_msg)) { APP_PRINT_ERROR0("[io_gdma] GDMA_Channel_Handler: Send int_gdma_msg failed!"); //Add user code here! GDMA_ClearINTPendingBit(GDMA_CHANNEL_NUM, GDMA_INT_Transfer); return; } GDMA_ClearINTPendingBit(GDMA_CHANNEL_NUM, GDMA_INT_Transfer); } void io_handle_gdma_msg(T_IO_MSG *io_gdma_msg) { APP_PRINT_INFO0("[io_gdma] io_handle_gdma_msg: GDMA transfer data completion!"); for (uint32_t i = 0; i < GDMA_MULTIBLOCK_SIZE; i++) { for (uint32_t j = 0; j < GDMA_TRANSFER_SIZE; j++) { if (GDMA_Send_Buffer[i][j] != GDMA_Recv_Buffer[i][j]) { APP_PRINT_INFO2("[io_gdma]io_handle_gdma_msg: Data transmission error! GDMA_Send_Buffer = %d, GDMA_Recv_Buffer = %d", GDMA_Send_Buffer[i][j], GDMA_Recv_Buffer[i][j]); } GDMA_Recv_Buffer[i][j] = 0; } } }
If
INT_BLOCK
is enabled, when each Block transfer is completed, it will trigger theGDMA_INT_Block
interrupt and enter the interrupt handling function.Disable the DMA
GDMA_INT_Block
interrupt.Define the message type
IO_MSG_TYPE_GDMA
and send a message to the task. In the main task, process the data of the sent message.Execute
io_handle_gdma_msg
, print the data transfer completion information, and check whetherGDMA_Recv_Buf
andGDMA_Send_Buf
are the same. If they are not, print the error data.Record the current number of transferred Blocks
GDMA_INT_Block_Counter
. If the current Block count is less thanGDMA_MULTIBLOCK_SIZE
, enable the DMAGDMA_INT_Block
interrupt. Otherwise, clear the current Block count, indicating that the transfer is complete.
void GDMA_Channel_Handler(void) { GDMA_INTConfig(GDMA_CHANNEL_NUM, GDMA_INT_Block, DISABLE); T_IO_MSG int_gdma_msg; int_gdma_msg.type = IO_MSG_TYPE_GDMA; int_gdma_msg.subtype = 0; int_gdma_msg.u.buf = (void *)&GDMA_INT_Block_Counter; if (false == app_send_msg_to_apptask(&int_gdma_msg)) { APP_PRINT_ERROR0("[io_gdma]GDMA_Channel_Handler: Send int_gdma_msg failed!"); //Add user code here! GDMA_ClearINTPendingBit(GDMA_CHANNEL_NUM, GDMA_INT_Block); return; } } void io_handle_gdma_msg(T_IO_MSG *io_gdma_msg) { uint8_t *p_buf = io_gdma_msg->u.buf; APP_PRINT_INFO1("[io_gdma] io_handle_gdma_msg: GDMA block%d transfer data completion!", *p_buf); for (uint32_t j = 0; j < GDMA_TRANSFER_SIZE; j++) { if (GDMA_Send_Buffer[*p_buf][j] != GDMA_Recv_Buffer[*p_buf][j]) { APP_PRINT_INFO2("[io_gdma]io_handle_gdma_msg: Data transmission error! GDMA_Send_Buffer = %d, GDMA_Recv_Buffer = %d", GDMA_Send_Buffer[*p_buf][j], GDMA_Recv_Buffer[*p_buf][j]); } GDMA_Recv_Buffer[*p_buf][j] = 0; } GDMA_INT_Block_Counter++; if (GDMA_INT_Block_Counter < GDMA_MULTIBLOCK_SIZE) { GDMA_INTConfig(GDMA_CHANNEL_NUM, GDMA_INT_Block, ENABLE); } else { GDMA_INT_Block_Counter = 0; } }