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:

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:

  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

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

  1. 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!
    
  2. 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:

  1. Source Code Directory.

  2. Peripheral initialization will be introduced in chapter Initialization.

  3. 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:

  1. In board_init, execute board_spi_init. This function sets up the PAD/PINMUX for SPI related pins, including the following steps:

    1. Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-up, and output high.

    2. Configure PINMUX: Assign pins for SPI0_CLK_MASTER, SPI0_MO_MASTER, SPI0_MI_MASTER, and SPI0_SS_N_0_MASTER functions.

  2. In board_init, execute board_uart_init. This function sets up the PAD/PINMUX for UART related pins, including the following steps:

    1. Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-up, and output disable.

    2. Configure PINMUX: Assign pins for UART0_TX and UART0_RX functions.

  3. After executing os_sched_start() to start task scheduling, execute driver_init in the main task app_main_task to initialize and configure the peripheral drivers.

  4. In driver_init, execute driver_uart_init, which initializes the UART peripheral, including the following steps:

    1. Enable the RCC clock.

    2. Set the default baud rate to 115200, 8 data bits, and 1 stop bit.

    3. Set the RX FIFO receive threshold to 16.

    4. 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);
    }
    
  5. In the main function, execute os_sched_start() to start task scheduling. When the stack is ready, execute app_handle_dev_state_evt, and in spi_demo, execute driver_spi_gdma_init to initialize the SPI and DMA peripherals.

    1. Initialize the SPI peripheral:

      1. Enable the RCC clock.

      2. Set the SPI transfer mode to EEPROM mode.

      3. Set the data width to 8 bits.

      4. Set the clock division factor to 100.

      5. In EEPROM mode, set NDF to GDMA_READ_SIZE_MAX - 1.

      6. 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);
      
    2. Initialize DMA TX peripheral:

      1. Set DMA transfer direction from memory to peripheral.

      2. Set source address to increment, destination address to be fixed.

      3. Set data width to 8 bits, each burst transfers one byte of data.

      4. Set source address to GDMA_WriteCmdBuffer, destination address to FLASH_SPI->DR.

      5. 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);
      
    3. Initialize the DMA RX peripheral:

      1. Set the DMA transfer direction from peripheral to memory.

      2. Set the source address to be fixed, the destination address to increment.

      3. Set the data width to 8 bits, with each burst transferring one byte of data.

      4. Set the source address to FLASH_SPI->DR, and the destination address to GDMA_Recv_Buffer.

      5. 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);
      
    4. Enable the DMA RX channel and enable SPI.

    5. 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

  1. 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);
    }
    
  2. 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.

    1. Disable the DMA interrupt.

    2. Execute uart_senddata_continuous to send the received data to the PC serial assistant.

    3. 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);
    }