DMA Single Block
This example communicates with external flash using SPI and transfers data using DMA.
SPI communicates with external flash via DMA, reads the JEDEC_ID information from the external flash, and sends the read data to the PC terminal assistant via UART.
In the example, SPI communicates using EEPROM mode.
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 the EVB to the W25Q128 module using DuPont wires: P4_0 (SCK) to CLK, P4_1 (MISO) to DO, P4_2 (MOSI) to DI, and P4_3 (CS) to CS#.
Note
For the module introduction of W25Q128, refer to W25Q128 Hardware Introduction.
Building and Downloading
This sample can be found in the SDK folder:
Project file: board\evb\io_sample\SPI\GDMA_SingleBlock\mdk
Project file: board\evb\io_sample\SPI\GDMA_SingleBlock\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 PC terminal software such as PuTTY or UartAssist, connect to the used COM port, and configure the UART settings as follows:
Baud rate: 115200
8 data bits
1 stop bit
No parity
No hardware flow control
Testing Phase
SPI sends the read JEDEC_ID command information through the DMA TX channel. After sending is complete, print the completion information in the DMA interrupt handler function.
[io_gdma]GDMA_TX_Channel_Handler: TX completed!
The JEDEC_ID information can be observed on the PC terminal assistant.
EF 40 18
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\SPI\GDMA_SingleBlock
Source code directory:
sdk\src\sample\io_sample\SPI\GDMA_SingleBlock
Source files are currently categorized into several groups as below.
└── Project: gdma_singleblock
└── 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
└── rtl876x_spi.c
├── profile
└── app includes the ble_peripheral user application implementation
├── main.c
├── ancs.c
├── app.c
├── app_task.c
├── io_spi.c
├── io_uart.c
└── io_gdma.c
Initialization
When the EVB reset is initiated, 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);
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;
}
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:
In
board_init
, executeboard_spi_init
. This function sets up the PAD/PINMUX for SPI related pins, including the following steps:Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-up, and output high.
Configure PINMUX: Assign pins for SPI0_CLK_MASTER, SPI0_MO_MASTER, SPI0_MI_MASTER, and SPI0_SS_N_0_MASTER functions.
In
board_init
, executeboard_uart_init
. This function sets up the PAD/PINMUX for UART related pins, including the following steps:Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-up, and output disable.
Configure PINMUX: Assign pins for UART0_TX and UART0_RX functions.
After executing
os_sched_start()
to start task scheduling, executedriver_init
in the main taskapp_main_task
to initialize and configure the peripheral drivers.In
driver_init
, executedriver_uart_init
, which initializes the UART peripheral, including the following steps:Enable the RCC clock.
Set the default baud rate to 115200, 8 data bits, and 1 stop bit.
Set the RX FIFO receive threshold to 16.
Set the idle time to 2 bytes, which is the time taken to receive 2 bytes at the current baud rate.
void driver_uart_init(void) { RCC_PeriphClockCmd(APBPeriph_UART0, APBPeriph_UART0_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 = 16; //1~29 UART_InitStruct.UART_IdleTime = UART_RX_IDLE_2BYTE; //idle interrupt wait time UART_Init(UART0, &UART_InitStruct); }
In the main function, execute
os_sched_start()
to start task scheduling. When the stack is ready, executeapp_handle_dev_state_evt
, and inspi_demo
, executedriver_spi_gdma_init
to initialize the SPI and DMA peripherals.Initialize the SPI peripheral:
Enable the RCC clock.
Set the SPI transfer mode to EEPROM mode.
Set the data width to 8 bits.
Set the clock division factor to 100.
In EEPROM mode, set NDF to
GDMA_READ_SIZE_MAX - 1
.Enable the DMA TX and RX functions, and set the waterLevel.
RCC_PeriphClockCmd(APBPeriph_SPI0, APBPeriph_SPI0_CLOCK, ENABLE); /*----------------------SPI init---------------------------------*/ SPI_StructInit(&SPI_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_EEPROM;//SPI_Direction_RxOnly; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_BaudRatePrescaler = 100; SPI_InitStructure.SPI_FrameFormat = SPI_Frame_Motorola; SPI_InitStructure.SPI_NDF = GDMA_READ_SIZE_MAX - 1; SPI_InitStructure.SPI_RxDmaEn = ENABLE; SPI_InitStructure.SPI_TxDmaEn = ENABLE; SPI_InitStructure.SPI_RxWaterlevel = 1; SPI_InitStructure.SPI_TxWaterlevel = 1; SPI_Init(FLASH_SPI, &SPI_InitStructure);
Initialize DMA TX peripheral:
Set DMA transfer direction from memory to peripheral.
Set source address to increment, destination address to be fixed.
Set data width to 8 bits, each burst transfers one byte of data.
Set source address to
GDMA_WriteCmdBuffer
, destination address toFLASH_SPI->DR
.Configure DMA transfer complete interrupt
GDMA_INT_Transfer
/*---------------------TX GDMA initial------------------------------*/ GDMA_StructInit(&GDMA_InitStruct); GDMA_InitStruct.GDMA_ChannelNum = GDMA_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_WriteCmdBuffer; GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)FLASH_SPI->DR; GDMA_InitStruct.GDMA_DestHandshake = GDMA_Handshake_SPI0_TX; GDMA_InitStruct.GDMA_ChannelPriority = 1; GDMA_InitStruct.GDMA_Multi_Block_En = 0; GDMA_Init(GDMA_TX_Channel, &GDMA_InitStruct); /*-----------------GDMA IRQ-----------------------------*/ NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = GDMA_TX_Channel_IRQn; NVIC_InitStruct.NVIC_IRQChannelPriority = 3; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); /* Enable transfer interrupt */ GDMA_INTConfig(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
Initialize the DMA RX peripheral:
Set the DMA transfer direction from peripheral to memory.
Set the source address to be fixed, the destination address to increment.
Set the data width to 8 bits, with each burst transferring one byte of data.
Set the source address to
FLASH_SPI->DR
, and the destination address toGDMA_Recv_Buffer
.Configure the DMA transfer complete interrupt:
GDMA_INT_Transfer
.
GDMA_StructInit(&GDMA_InitStruct); GDMA_InitStruct.GDMA_ChannelNum = GDMA_RX_CHANNEL_NUM; GDMA_InitStruct.GDMA_DIR = GDMA_DIR_PeripheralToMemory; GDMA_InitStruct.GDMA_BufferSize = GDMA_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)FLASH_SPI->DR; GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)GDMA_Recv_Buffer; GDMA_InitStruct.GDMA_SourceHandshake = GDMA_Handshake_SPI0_RX; GDMA_InitStruct.GDMA_ChannelPriority = 1; GDMA_InitStruct.GDMA_Multi_Block_En = 0; GDMA_Init(GDMA_RX_Channel, &GDMA_InitStruct); /*-----------------GDMA IRQ-----------------------------*/ NVIC_InitStruct.NVIC_IRQChannel = GDMA_RX_Channel_IRQn; NVIC_InitStruct.NVIC_IRQChannelPriority = 3; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); /* Enable transfer interrupt */ GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
Enable the DMA RX channel and enable SPI.
Set the total transfer data amount of the DMA TX channel to 1, and enable the DMA TX channel.
GDMA_Cmd(GDMA_RX_CHANNEL_NUM, ENABLE); SPI_Cmd(FLASH_SPI, ENABLE); /* Send read data command */ GDMA_SetBufferSize(GDMA_TX_Channel, 1); GDMA_SetSourceAddress(GDMA_TX_Channel, (uint32_t)GDMA_WriteCmdBuffer); GDMA_Cmd(GDMA_TX_CHANNEL_NUM, ENABLE);
Functional Implementation
SPI sends the read JEDEC_ID command to the external flash through the DMA TX channel. After the DMA transmission is completed, an interrupt is triggered, and the interrupt handler function
GDMA_TX_Channel_Handler
is entered, where relevant information is printed and the interrupt flag is cleared.void GDMA_TX_Channel_Handler(void) { GDMA_INTConfig(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE); DBG_DIRECT("[io_gdma]GDMA_TX_Channel_Handler: TX completed!"); GDMA_ClearINTPendingBit(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer); GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE); }
Flash replies JEDEC_ID information to the host, and the host receives the data through the DMA RX channel. When the data transfer is complete, an interrupt is triggered, and the interrupt handler function
GDMA_RX_Channel_Handler
is entered.Disable the DMA interrupt.
Execute
uart_senddata_continuous
to send the received data to the PC serial assistant.Clear the interrupt flag.
void GDMA_RX_Channel_Handler(void) { GDMA_INTConfig(GDMA_RX_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; int_gdma_msg.u.buf = (void *)GDMA_Recv_Buffer; 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_RX_CHANNEL_NUM, GDMA_INT_Transfer); return; } GDMA_ClearINTPendingBit(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer); GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE); } void io_handle_gdma_msg(T_IO_MSG *io_gdma_msg) { uint8_t *p_buf = io_gdma_msg->u.buf; uint16_t data_len = (GDMA_TRANSFER_SIZE); APP_PRINT_INFO1("[io_gdma] io_handle_gdma_msg: read data complete,data_len = %d", data_len); uart_senddata_continuous(UART0, p_buf, data_len); }