GDMA Single Block

该示例通过使用 SPI 与外部 flash 以 GDMA 搬运数据方式进行数据传输。

示例中采用全双工的模式进行通信,SPI 通过 GDMA 与外部 flash 通信,对外部 flash 进行数据的读取,写入,擦除等操作。

示例采用 W25Q128 作为外部 flash 与 SPI 通信,关于该硬件的介绍详见 硬件介绍

用户可以通过宏配置来选择是否使用软件控制 CS 线通信,修改引脚设置和 GDMA 通道设置等参数,具体宏配置详见 配置选项

备注

使用软件控制 CS 线的目的,是为了保证 SPI 在数据传输过程中不会断流。如果 SPI 在传输过程中出现 Tx underflow 的情况,可能会使得 CS 线被拉高,导致通信出现问题。使用软件控制 CS 线,可以保证在本次通信传输完毕前,CS 线不会拉高,保证通信的完整性。

环境需求

该示例的环境需求,可参考 环境需求

硬件连线

EVB 外接 W25Q128 模块,使用杜邦线连接 P4_0(SCK)和 CLK,P4_1(MISO)和 DI,P4_2(MOSI)和 DO,P4_3(CS)和 CS#。

硬件介绍

W25Q128 是一款 SPI 接口的 NOR Flash 芯片,支持标准串行外设接口(SPI)、双线/四线 SPI 以及双时钟指令周期四线外设接口(QPI)。

此外,该器件还支持 JEDEC 标准的制造商和设备 ID 以及 SFDP 寄存器,一个 64 位唯一序列号和三个 256 字节安全寄存器

其他细节说明可查阅该器件的应用指南。在示例中使用该器件作为从设备进行 SPI 的通信测试。

配置选项

  1. 可配置如下宏修改是否使用软件控制 CS 线。

    #define CONFIG_SPI_SW_SIM_CS                   1            /*< Set this macro to 1 to enable software control CS. */
    
  2. 可配置如下宏修改引脚定义。

    #define SPI_SCK_PIN                     P4_0
    #define SPI_MOSI_PIN                    P4_1
    #define SPI_MISO_PIN                    P4_2
    #define SPI_CS_PIN                      P4_3
    
  3. 可配置如下宏修改 GDMA TX 和 GDMA RX 通道配置。

    // Set the following macros to modify the SPI TX GDMA Channel configurations.
    #define GDMA_TX_CHANNEL_NUM                 2
    #define GDMA_TX_CHANNEL                     GDMA_Channel2
    #define GDMA_TX_CHANNEL_IRQN                GDMA0_Channel2_IRQn
    #define GDMA_TX_CHANNEL_HANDLER             GDMA0_Channel2_Handler
    
    // Set the following macros to modify the SPI RX GDMA Channel configurations.
    #define GDMA_RX_CHANNEL_NUM                 4
    #define GDMA_RX_CHANNEL                     GDMA_Channel4
    #define GDMA_RX_CHANNEL_IRQN                GDMA0_Channel4_IRQn
    #define GDMA_RX_CHANNEL_HANDLER             GDMA0_Channel4_Handler
    

编译和下载

该示例的编译和下载流程,可参考 编译和下载

测试验证

  1. 初始化完毕后,SPI 通过 GDMA TX 向外部 flash 发送指令,执行数据读取,写入,擦除等操作。同时通过 GDMA RX 读取外部 flash 回复的数据。

  2. 当 GDMA TX 通道数据传输完成时,会打印 log 信息。

    gdma tx data finish!
    
  3. 当 GDMA RX 通道数据传输完成时,会打印传输完成说明,收到的数据长度和数据内容。

    1. 读取flash id时,打印如下内容:第一个字节为dummy read,后三个字节为 JEDEC_ID 内容。

      data_len = 4
      dma rx data[0] = 000000ff
      dma rx data[1] = 000000ef
      dma rx data[2] = 00000040
      dma rx data[3] = 00000018
      
    2. 使能写操作时,打印如下内容:第一个字节为dummy read。

      data_len = 1
      dma rx data[0] = 000000ff
      
    3. 执行擦除操作时,打印如下内容:写操作对应的四个字节为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. 擦除完成后,读取该地址下的数据,打印如下内容:由于该地址下数据被擦除,因此读到的数据均为FF,前5个字节为dummy read。

      data_len = 105
      dma rx data[0] = 000000ff
      dma rx data[1] = 000000ff
      ...
      dma rx data[104] = 000000ff
      
    5. 执行数据写入操作,在指定内存下写入{a, b, …}等 100 个数据。打印如下内容:写操作对应的前4个字节为指令的dummy read,后100个字节为数据的dummy read。

      data_len = 104
      dma rx data[0] = 000000ff
      ...
      dma rx data[103] = 000000ff
      
    6. 写入数据之后再次进行数据读取,打印读取的数据内容:前 5 个字节为 dummy read,后 100 个字节为读取到的数据。

      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
      

代码介绍

该章节主要介绍示例中的初始化和相应功能实现的代码和流程说明。

源码路径

工程文件和源码路径如下:

  • 工程路径: sdk\samples\peripheral\spi\gdma_singleblock\proj

  • 源码路径: sdk\samples\peripheral\spi\gdma_singleblock\src

初始化

外设的初始化流程可参考 General Introduction 中的 初始化流程 部分。

  1. 调用 Pad_Config()Pinmux_Config(),配置对应引脚的 PAD 和 PINMUX。如果配置 CONFIG_SPI_SW_SIM_CS1 ,则需要将 CS 引脚设置为 SW 模式。

    void board_spi_init(void)
    {
        Pad_Config(SPI_SCK_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH);
        Pad_Config(SPI_MOSI_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH);
        Pad_Config(SPI_MISO_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH);
    
        Pinmux_Deinit(P4_0);
        Pinmux_Deinit(P4_1);
        Pinmux_Deinit(P4_2);
        Pinmux_Deinit(P4_3);
    
        Pinmux_Config(SPI_SCK_PIN, SPI0_CLK_MASTER);
        Pinmux_Config(SPI_MOSI_PIN, SPI0_MO_MASTER);
        Pinmux_Config(SPI_MISO_PIN, SPI0_MI_MASTER);
    
    #if (CONFIG_SPI_SW_SIM_CS == 1)
        Pad_Config(SPI_CS_PIN, PAD_SW_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH);
    #else
        Pad_Config(SPI_CS_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH);
        Pinmux_Config(SPI_CS_PIN, SPI0_CSN_0_MASTER);
    #endif
    }
    
  2. 调用 RCC_PeriphClockCmd() ,开启 SPI 时钟。

  3. 对 SPI 外设进行初始化:

    1. 定义 SPI_InitTypeDef 类型 SPI_InitStruct ,调用 SPI_StructInit()SPI_InitStruct 预填默认值。

    2. 根据需求修改 SPI_InitStruct 参数,SPI 的初始化参数配置如下表。

    3. 调用 SPI_Init(),初始化 SPI 外设。

SPI 初始化参数

SPI Hardware Parameters

Setting in the SPI_InitStruct

SPI

SPI Transfer Mode

SPI_InitTypeDef::SPI_Direction

SPI_Direction_FullDuplex

SPI Data Size

SPI_InitTypeDef::SPI_DataSize

SPI_DataSize_8b

Clock Mode - CPOL

SPI_InitTypeDef::SPI_CPOL

SPI_CPOL_High

Clock Mode - CPHA

SPI_InitTypeDef::SPI_CPHA

SPI_CPHA_2Edge

Clock Prescaler

SPI_InitTypeDef::SPI_BaudRatePrescaler

20

Frame Format

SPI_InitTypeDef::SPI_FrameFormat

SPI_Frame_Motorola

TX GDMA Enable

SPI_InitTypeDef::SPI_TxDmaEn

ENABLE

RX GDMA Enable

SPI_InitTypeDef::SPI_RxDmaEn

ENABLE

TX Waterlevel

SPI_InitTypeDef::SPI_TxWaterlevel

31

RX Waterlevel

SPI_InitTypeDef::SPI_RxWaterlevel

0

备注

SPI 使用 GDMA 传输时,推荐配置 TX Waterlevel 为 SPI_TX_FIFO_SIZE - MSize,RX Waterlevel 为 MSize - 1。

  1. 调用 RCC_PeriphClockCmd() ,开启 GDMA 时钟。

  2. 对 GDMA 外设进行初始化:

    1. 定义 GDMA_InitTypeDef 类型 GDMA_InitStruct ,调用 GDMA_StructInit()GDMA_InitStruct 预填默认值。

    2. 根据需求修改 GDMA_InitStruct 参数。GDMA TX 和 RX 通道的初始化参数配置如下表。调用 GDMA_Init(),初始化 GDMA 外设。

    3. 配置 GDMA 总传输完成中断 GDMA_INT_Transfer 和 NVIC,NVIC 相关配置可参考 中断配置

GDMA 初始化参数

GDMA Hardware Parameters

Setting in the GDMA_InitStruct

GDMA TX Channel

GDMA RX Channel

Channel Num

GDMA_InitTypeDef::GDMA_ChannelNum

2

4

Transfer Direction

GDMA_InitTypeDef::GDMA_DIR

GDMA_DIR_MemoryToPeripheral

GDMA_DIR_PeripheralToMemory

Buffer Size

GDMA_InitTypeDef::GDMA_BufferSize

-

-

Source Address Increment or Decrement

GDMA_InitTypeDef::GDMA_SourceInc

DMA_SourceInc_Inc

DMA_SourceInc_Fix

Destination Address Increment or Decrement

GDMA_InitTypeDef::GDMA_DestinationInc

DMA_DestinationInc_Fix

DMA_DestinationInc_Inc

Source Data Size

GDMA_InitTypeDef::GDMA_SourceDataSize

GDMA_DataSize_Byte

GDMA_DataSize_Byte

Destination Data Size

GDMA_InitTypeDef::GDMA_DestinationDataSize

GDMA_DataSize_Byte

GDMA_DataSize_Byte

Source Burst Transaction Length

GDMA_InitTypeDef::GDMA_SourceMsize

GDMA_Msize_1

GDMA_Msize_1

Destination Burst Transaction Length

GDMA_InitTypeDef::GDMA_DestinationMsize

GDMA_Msize_1

GDMA_Msize_1

Source Address

GDMA_InitTypeDef::GDMA_SourceAddr

GDMA_Send_Buffer

SPI0->SPI_DR

Destination Address

GDMA_InitTypeDef::GDMA_DestinationAddr

SPI0->SPI_DR

GDMA_Recv_Buffer

Source Handshake

GDMA_InitTypeDef::GDMA_SourceHandshake

-

GDMA_Handshake_SPI0_RX

Destination Handshake

GDMA_InitTypeDef::GDMA_DestHandshake

GDMA_Handshake_SPI0_TX

-

功能实现

  1. 根据 W25Q128 的硬件信息和通信协议,在执行不同操作时 SPI 需要发送的数据长度和内容是不同的,因此需要在每次通信开始前对 GDMA 的数据长度和数据内容进行设定。调用 GDMA_SetBufferSize() 设定 GDMA 对应通道的 Block Size。由于 SPI 设定为全双工通信,因此对于 GDMA TX 通道和 GDMA RX 通道,需要设置相同的 Block Size。

  2. 在设定完成后使能 SPI,GDMA TX 和 GDMA RX 通道。当 GDMA TX 通道和 GDMA RX 通道传输完毕时,会进入相应通道的 GDMA 中断,在 GDMA RX 中断内会打印外部 flash 回复的数据信息,同时在对应中断内将 isDMATxDoneisDMARxDone 分别置为 true

  3. 当发现两个 flag 位均被设置为 true 时,代表本次通信完成,此时可以开始下次通信流程。

  4. 如果配置 CONFIG_SPI_SW_SIM_CS1,则需要在每次通信开始前和通信结束后调用 pull_cs_down,分别拉低和拉高 CS 线。

    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);
    ...
    
  5. 在主程序中,调用 spi_flash_read_id 读取外部 flash 的 ID 信息。调用 spi_flash_sector_erase 对指定地址下的数据进行擦除,擦除后调用 spi_flash_read 读取对应地址下的数据,检查是否擦除成功。调用 spi_flash_page_write 对指定地址下进行数据写入,写入后再次读取该地址下的数据检查是否写入成功。

    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);