DMA Single Block

This sample demonstrates how to use SPI to transfer data via DMA.

SPI communicates with external flash through DMA, and reads, writes and erases data from external flash.

In the example, full-duplex mode is used for communication, and the macro CONFIG_SPI_SW_SIM_CS can be configured to choose whether to use software to simulate the CS line communication or not.

Requirements

The sample supports the following development kits:

Development Kits

Hardware Platforms

Board Name

RTL87x2G HDK

RTL87x2G EVB

For more requirements, please refer to Quick Start.

Wiring

EVB is connected to W25Q128 module: connect P4_0(SCK) to CLK, P4_1(MISO) to DO, P4_2(MOSI) to DI, P4_3(CS) to CS#.

Hardware Introduction

The W25Q128 is a NOR Flash chip with SPI interface, which supports the standard Serial Peripheral Interface (SPI), Dual/Quad I/O SPI as well as 2-clocks instruction cycle Quad Peripheral Interface (QPI).

Additionally, the device supports JEDEC standard manufacturer and device ID and SFDP Register, a 64-bit Unique Serial Number and three 256-byte Security Registers.

Additional details can be found in the application guide for this device. In this example the device is used as a slave for SPI communication test.

Configurations

The example configurable macros are as follows:

  1. CONFIG_SPI_SW_SIM_CS: Configure this macro to select whether or not to use software emulation of the CS line for communication. Configuring 1 means to use software to simulate CS line communication and 0 means not to use software for simulation.

Building and Downloading

This sample can be found in the SDK folder:

Project file: samples\peripheral\spi\gdma_singleblock\proj\rtl87x2g\mdk

Project file: samples\peripheral\spi\gdma_singleblock\proj\rtl87x2g\gcc

To build and run the sample, follow the steps listed below:

  1. Open sample project file.

  2. To build the target, follow the steps listed on the Generating App Image in Quick Start.

  3. After a successful compilation, the app bin app_MP_xxx.bin will be generated in the directory mdk\bin or gcc\bin.

  4. To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.

  5. Press reset button on EVB board and it will start running.

Experimental Verification

  1. When the DMA TX channel data transfer is complete, a log is printed as follows

    gdma tx data finish!
    
  2. When the DMA RX channel data transfer is completed, it will print the transfer completion statement, the length of the received data and the data content.

    1. When flash id is read, the following will be printed: the first byte is dummy read, and the last three bytes are JEDEC_ID contents.

      data_len = 4
      dma rx data[0] = 000000ff
      dma rx data[1] = 000000ef
      dma rx data[2] = 00000040
      dma rx data[3] = 00000018
      
    2. When the write operation is enabled, the following is printed: the first byte is dummy read.

      data_len = 1
      dma rx data[0] = 000000ff
      
    3. When the erase operation is performed, the following is printed: the four bytes corresponding to the write operation are dummy read.

      data_len = 4
      dma rx data[0] = 000000ff
      dma rx data[1] = 000000ff
      dma rx data[2] = 000000ff
      dma rx data[3] = 000000ff
      
    4. After the erase is completed, read the data under this address and print the following: Since the data under this address is erased, all the data read is FF and the first 5 bytes are dummy read.

      data_len = 105
      dma rx data[0] = 000000ff
      dma rx data[1] = 000000ff
      ...
      dma rx data[104] = 000000ff
      
    5. Execute the data write operation and print the following: the first 4 bytes corresponding to the write operation are the dummy read of the instruction, and the last 100 bytes are the dummy read of the data.

      data_len = 104
      dma rx data[0] = 000000ff
      ...
      dma rx data[103] = 000000ff
      
    6. After writing the data, the data is read again, and the content of the read data is printed: the first 5 bytes are dummy read, and the last 100 bytes are the read data.

      data_len = 105
      dma rx data[0] = 000000ff
      dma rx data[1] = 000000ff
      dma rx data[2] = 000000ff
      dma rx data[3] = 000000ff
      dma rx data[4] = 000000ff
      dma rx data[5] = 0000000a
      dma rx data[6] = 0000000b
      dma rx data[7] = 0000000c
      ...
      dma rx data[104] = 0000006d
      

Code Overview

This chapter will be introduced according to the following several parts:

  1. Source Code Directory.

  2. Peripheral initialization will be introduced in chapter Initialization.

  3. Functional implementation after initialization will be introduced in chapter Functional Implementation .

Source Code Directory

  • Project Directory: sdk\samples\peripheral\spi\gdma_singleblock\proj

  • Source Code Directory: sdk\samples\peripheral\spi\gdma_singleblock\src

Source files are currently categorized into several groups as below.

└── Project: gdma_singleblock
    └── 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_rcc.c
            ├── rtl_pinmux.c
            ├── rtl_nvic.c
            ├── rtl_gdma.c
            └── rtl_spi.c
        └── APP                      includes the ble_peripheral user application implementation
            ├── main_ns.c
            └── io_spi.c

Initialization

The initialization process includes board_spi_init, driver_spi_init, driver_tx_gdma_init and driver_rx_gdma_init.


board_spi_init contains the PAD and PINMUX settings of SPI.

  1. Config PAD: Set pins as PINMUX mode, PowerOn, internal Pull-Up.

  2. Config PINMUX: Assign pins for SPI0_CLK_MASTER, SPI0_MO_MASTER, SPI0_MI_MASTER, SPI0_CSN_0_MASTER functions respectively.

  3. If the macro CONFIG_SPI_SW_SIM_CS is ENABLED, the corresponding CS line pin needs to be set to SW mode without configuring PINMUX.


driver_spi_init contains the initialization of the SPI peripheral.

  1. Enable PCC clock.

  2. Set the communication mode to FullDuplex mode.

  3. Refer to SPI Polling initialization flow for other basic settings.

  4. Enable DMA TX and RX for SPI.

  5. Set the TxWaterlevel to SPI_TX_FIFO_SIZE - 1, set the RxWaterlevel to 1 - 1.

RCC_PeriphClockCmd(APBPeriph_SPI0, APBPeriph_SPI0_CLOCK, ENABLE);
...
SPI_InitStructure.SPI_Direction   = SPI_Direction_FullDuplex;
...
SPI_InitStructure.SPI_RxDmaEn     = ENABLE;
SPI_InitStructure.SPI_TxDmaEn     = ENABLE;
SPI_InitStructure.SPI_RxWaterlevel = 1 - 1;
SPI_InitStructure.SPI_TxWaterlevel = SPI_TX_FIFO_SIZE - 1;

Note

When SPI uses DMA transfer, it is recommended to configure SPI_TxWaterlevel as SPI_TX_FIFO_SIZE - MSize and SPI_RxWaterlevel as MSize - 1.


driver_tx_gdma_init contains the initialization of the DMA TX peripheral.

  1. Enable PCC clock.

  2. Use GDMA channel 2.

  3. Transfer direction is memory to peripheral transfer.

  4. Set the source address to increment and the destination address to be fixed.

  5. Set the source and destination MSize to GDMA_Msize_1.

  6. The source address is GDMA_Send_Buffer, and the destination address is SPI0->SPI_DR.

  7. Configure the GDMA TX total transfer completion interrupt GDMA_INT_Transfer.

RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE);
...
GDMA_InitStruct.GDMA_ChannelNum          = GDMA_TX_CHANNEL_NUM;
GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_MemoryToPeripheral;
GDMA_InitStruct.GDMA_SourceInc           = DMA_SourceInc_Inc;
GDMA_InitStruct.GDMA_DestinationInc      = DMA_DestinationInc_Fix;
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)SPI0->SPI_DR;
...
GDMA_INTConfig(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
...

driver_rx_gdma_init contains the initialization of the DMA RX peripheral.

  1. Enable PCC clock.

  2. Use GDMA channel 4.

  3. Transfer direction is peripheral to memory transfer.

  4. Set the source address to be fixed and the destination address to be incremented.

  5. Set the source and destination MSize to GDMA_Msize_1.

  6. The source address is SPI0->SPI_DR and the destination address is GDMA_Recv_Buffer.

  7. Configure the GDMA RX total transfer completion interrupt GDMA_INT_Transfer.

RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE);
...
GDMA_InitStruct.GDMA_ChannelNum          = GDMA_RX_CHANNEL_NUM;
GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_PeripheralToMemory;
GDMA_InitStruct.GDMA_SourceInc           = DMA_SourceInc_Fix;
GDMA_InitStruct.GDMA_DestinationInc      = DMA_DestinationInc_Inc;
GDMA_InitStruct.GDMA_SourceMsize         = GDMA_Msize_1;
GDMA_InitStruct.GDMA_DestinationMsize    = GDMA_Msize_1;
...
GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)SPI0->SPI_DR;
GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)GDMA_Recv_Buffer;
...
GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);

Functional Implementation

  1. Execute spi_flash_read_id() to read the JEDEC ID information of the external flash.

    1. Initialize information about the contents of the DMA receive and transmit arrays.

    2. Set the instruction to be sent and the send length. According to the hardware information of W25Q128, to read the JEDEC ID, the total length of communication is 4 bytes. Therefore, set the BufferSize of DMA to 4.

    3. Enable SPI, enable DMA receive channel, enable DMA transmit channel. If software emulation of CS communication is used, the CS line needs to be pulled low before communication starts.

    4. After waiting for the DMA transmit and receive channels to complete, the CS line can be pulled high and the subsequent steps can continue.

DBG_DIRECT("===================read_flash_id==================");
memset(GDMA_Send_Buffer, 0, sizeof(GDMA_Send_Buffer) / sizeof(GDMA_Send_Buffer[0]));
memset(GDMA_Recv_Buffer, 0, sizeof(GDMA_Recv_Buffer) / sizeof(GDMA_Recv_Buffer[0]));

GDMA_Send_Buffer[0] = SPI_FLASH_JEDEC_ID;
GDMA_SetBufferSize(GDMA_TX_CHANNEL, 4);
GDMA_SetBufferSize(GDMA_RX_CHANNEL, 4);

GDMA_INTConfig(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);

pull_cs_down(true);

SPI_Cmd(SPI0, ENABLE);
GDMA_Cmd(GDMA_RX_CHANNEL_NUM, ENABLE);
GDMA_Cmd(GDMA_TX_CHANNEL_NUM, ENABLE);

DBG_DIRECT("after dma cmd enable");

//wait for read id finish!
while ((isDMATxDone != true) || (isDMARxDone != true))
{
    DBG_DIRECT("isDMATxDone = %d", isDMATxDone);
    DBG_DIRECT("isDMARxDone = %d", isDMARxDone);
    platform_delay_ms(1000);
}

isDMATxDone = false;
isDMARxDone = false;

pull_cs_down(false);
...
  1. Execute the spi_flash_sector_erase() function to erase the data at the specified address.

    1. Before the write operation, execute spi_flash_write_enable() to enable the write operation. Send the write enable command to the external flash via DMA and wait for the DMA transfer to complete.

    2. Set the data content and data length to be sent, enable the DMA receive and transmit channels, and wait for the DMA transfer to complete.

...
GDMA_Send_Buffer[0] = SPI_FLASH_SECTOR_ERASE;
GDMA_Send_Buffer[1] = (vAddress >> 16) & 0xff;
GDMA_Send_Buffer[2] = (vAddress >> 8) & 0xff;
GDMA_Send_Buffer[3] = vAddress & 0xff;

GDMA_SetBufferSize(GDMA_TX_CHANNEL, 4);
GDMA_SetBufferSize(GDMA_RX_CHANNEL, 4);
...
  1. Execute the spi_flash_read() function to read the data at the specified address. 4.

  2. Execute the spi_flash_page_write() function to write data of the specified length to the specified address. 5.

  3. Execute the spi_flash_read() function again to read the data at the specified address.

void spi_dma_demo(void)
{
    uint8_t write_data[100];
    uint8_t read_data[105] = {0};
    for (uint16_t i = 0; i < 100; i++)
    {
        write_data[i] = (i + 10) & 0xFF;
    }

    //read flash JEDEC id
    spi_flash_read_id();

    //sector erase
    spi_flash_sector_erase(0x001000);

    spi_flash_read(SPI_FLASH_FAST_READ, 0x001000, read_data, 100);

    spi_flash_page_write(0x001000, write_data, 100);

    spi_flash_read(SPI_FLASH_FAST_READ, 0x001000, read_data, 100);

}
  1. Trigger the GDMA_INT_Transfer interrupt when the DMA TX channel has finished handling data, and enter the interrupt handler function GDMA_TX_CHANNEL_HANDLER :

    1. Disable the GDMA TX channel interrupt, print the entry interrupt message, and clear the GDMA interrupt suspend bit.

    2. Set the DMA TX completion flag bit to true.

void GDMA_TX_CHANNEL_HANDLER(void)
{
    GDMA_INTConfig(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE);
    GDMA_ClearINTPendingBit(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer);

    DBG_DIRECT("gdma tx data finish!");
    isDMATxDone = true;

    GDMA_Cmd(GDMA_TX_CHANNEL_NUM, DISABLE);
}
  1. Trigger the GDMA_INT_Transfer interrupt when the DMA RX channel has finished handling data and enter the interrupt handler function GDMA_RX_CHANNEL_HANDLER:

    1. Disable the GDMA RX channel interrupt and clear the GDMA interrupt suspend bit.

    2. Print the length of data received by the DMA and the full data contents.

    3. Set the DMA RX completion flag bit to true.

void GDMA_RX_CHANNEL_HANDLER(void)
{
    GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE);
    GDMA_ClearINTPendingBit(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer);

    DBG_DIRECT("gdma rx data finish!");
    uint16_t data_len = GDMA_GetTransferLen(GDMA_RX_CHANNEL);
    DBG_DIRECT("data_len = %d", data_len);
    for (uint16_t i = 0; i < data_len; i++)
    {
        DBG_DIRECT("dma rx data[%d] = %x", i, GDMA_Recv_Buffer[i]);
    }
    isDMARxDone = true;

    GDMA_Cmd(GDMA_RX_CHANNEL_NUM, DISABLE);
}

Troubleshooting

When the SPI frequency to be used is high (greater than 10MHz), it is recommended to use DMA to transfer data. If it is found that the CS line is pulled up during the transfer of data, the MSize of the GDMA can be increased to improve the transfer efficiency of the DMA or the CS communication can be simulated using software.