DMA Single Block

该示例通过使用 SPI 与外部flash以DMA搬运数据方式进行数据传输。 SPI通过DMA搬运Flash的数据,再通过UART发送数据到PC端。

环境需求

该示例支持以下开发套件:

开发套件

Hardware Platforms

Board Name

RTL87x2G HDK

RTL87x2G EVB

更多信息请参考 快速入门

PC端需要安装串口调试助手软件,以便接收UART发送至PC端的数据。

硬件连线

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

EVB外接FT232,连接P3_0和FT232的RX,P3_1和FT232的TX。

硬件介绍

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

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

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

编译和下载

该示例的工程路径如下:

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

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

请按照以下步骤操作构建并运行该示例:

  1. 打开工程文件。

  2. 按照 快速入门编译APP Image 给出的步骤构建目标文件。

  3. 编译成功后,在路径 mdk\bingcc\bin 下会生成 app bin app_MP_xxx.bin 文件。

  4. 按照 快速入门MPTool 给出的步骤将app bin烧录至EVB内。

  5. 按下复位按键,开始运行。

测试验证

  1. 初始化完毕后,DMA开始搬运TX数据。搬运完毕后,进入GDMA TX中断,打印LOG。

    GDMA_TX_Channel_Handler: TX completed!
    
  2. SPI收到外部flash发回的数据,DMA开始搬运RX数据。搬运完毕后,进入GDMA RX中断,打印获取到的数据长度和数据信息。

    GDMA_RX_Channel_Handler!
    [io_gdma] io_handle_gdma_msg: read data complete,data_len = 3
    GDMA_Recv_Buffer[0] = 0x000000ef
    GDMA_Recv_Buffer[1] = 0x00000040
    GDMA_Recv_Buffer[2] = 0x00000018
    
  3. UART将接收到的数据通过FT232发送至PC端的串口助手,在串口助手上可以显示SPI收到的数据。

这里应该是串口助手收到的信息的图片

串口助手收到的信息

代码介绍

该章节分为以下几个部分:

  1. 源码路径

  2. 初始化函数将在 初始化 章节介绍。

  3. 初始化后的功能实现将在 功能实现 章节介绍。

源码路径

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

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

该工程的工程文件代码结构如下:

└── 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_uart.c
            ├── rtl_gdma.c
            └── rtl_spi.c
        └── APP                      includes the ble_peripheral user application implementation
            ├── main_ns.c
            ├── io_uart.c
            ├── io_gdma.c
            └── io_spi.c

初始化

初始化流程包括了 board_spi_initboard_uart_initdriver_uart_initdriver_spi_gdma_init


board_spi_init 中包含了对SPI的PAD与PINMUX设置。

  1. 配置PAD:设置引脚、PINMUX模式、PowerOn、内部上拉。

  2. 配置PINMUX:分配引脚分别为SPI0_CLK_MASTER、SPI0_MO_MASTER、SPI0_MI_MASTER、SPI0_CSN_0_MASTER功能。


board_uart_init 中包含了对UART的PAD与PINMUX设置。

  1. 配置PAD:设置引脚、PINMUX模式、PowerOn、内部上拉。

  2. 配置PINMUX:分配引脚分别为UART0_TX、UART0_RX功能。


driver_uart_init 包含了对UART外设的初始化。

  1. 使能PCC时钟。

  2. 设置无奇偶校验。

  3. 停止位设置为1。

  4. 数据长度为8bit。

RCC_PeriphClockCmd(APBPeriph_UART0, APBPeriph_UART0_CLOCK, ENABLE);
...
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;
UART_InitStruct.UART_IdleTime       = UART_RX_IDLE_2BYTE;

driver_spi_gdma_init 包含了对SPI和DMA外设的初始化:

  1. 初始化SPI外设:

    1. 使能PCC时钟。

    2. 设置通信模式为EEPROM模式。

    3. 其他基础设置参考 SPI Polling 初始化流程。

    4. 设置NDF为GDMA_READ_SIZE_MAX。

    5. 使能SPI的DMA发送与接收功能。

    6. 设置RxWaterlevel为8-1,设置TxWaterlevel为FIFO_SIZE - 8。

RCC_PeriphClockCmd(APBPERIPH_SPI, APBPERIPH_SPI_CLOCK, ENABLE);
...
SPI_InitStructure.SPI_Direction   = SPI_Direction_EEPROM;
...
SPI_InitStructure.SPI_NDF         = GDMA_READ_SIZE_MAX;
SPI_InitStructure.SPI_RxDmaEn     = ENABLE;
SPI_InitStructure.SPI_TxDmaEn     = ENABLE;
SPI_InitStructure.SPI_RxWaterlevel = 8 - 1;
SPI_InitStructure.SPI_TxWaterlevel = SPI_TX_FIFO_SIZE - 8;

备注

SPI使用DMA传输时,推荐配置SPI_TxWaterlevel为SPI_TX_FIFO_SIZE - MSize,SPI_RxWaterlevel为MSize - 1。

  1. 初始化GDMA TX:

    1. 使能PCC时钟。

    2. 使用GDMA通道3。

    3. 传输方向为 内存外设 传输。

    4. 设置源端地址自增,目的端地址固定。

    5. 设置源端和目的端的MSize为8。

    6. 源端地址为 GDMA_WriteCmdBuffer ,目的端地址为 FLASH_SPI->SPI_DR

    7. 配置GDMA TX总传输完成中断 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_8;
GDMA_InitStruct.GDMA_DestinationMsize    = GDMA_Msize_8;
...
GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)GDMA_WriteCmdBuffer;
GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)FLASH_SPI->SPI_DR;
...
GDMA_INTConfig(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
...
  1. 初始化GDMA RX:

    1. 使能PCC时钟。

    2. 使用GDMA通道0。

    3. 传输方向为 外设内存 传输。

    4. 设置源端地址固定,目的端地址自增。

    5. 设置源端和目的端的MSize为8。

    6. 源端地址为 FLASH_SPI->SPI_DR ,目的端地址为 GDMA_Recv_Buffer

    7. 配置GDMA RX总传输完成中断 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_8;
GDMA_InitStruct.GDMA_DestinationMsize    = GDMA_Msize_8;
...
GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)FLASH_SPI->SPI_DR;
GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)GDMA_Recv_Buffer;
...
GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
  1. 设置EEPORM传输模式下的数据接收长度为3(JEDEC_ID长度为3),使能SPI。

  2. 设置GDMA TX和RX的 Buffer Size,使能RX GDMA,使能TX GDMA。

SPI_SetReadLen(FLASH_SPI, 3);
SPI_Cmd(FLASH_SPI, ENABLE);
GDMA_SetBufferSize(GDMA_RX_Channel, 3);
GDMA_Cmd(GDMA_RX_CHANNEL_NUM, 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);

功能实现

  1. 使能GDMA TX通道后,触发GDMA TX传输,传输完成后触发 GDMA_INT_Transfer 中断,进入中断处理函数 GDMA_TX_Channel_Handler

    1. 失能GDMA TX通道中断,打印进入中断信息,清除GDMA中断悬挂位,使能 GDMA_INT_Transfer 中断。

  2. Flash接收到GDMA TX搬运的消息后,将内部信息通过GDMA RX通道搬运回来,搬运完成后触发 GDMA_INT_Transfer 中断,进入中断处理函数 GDMA_RX_Channel_Handler

    1. 失能GDMA TX通道中断,打印GDMA_Recv_Buffer信息。

    2. 执行 uart_senddata_continuous 将GDMA_Recv_Buffer信息通过UART0发送至PC端。

GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE);

uint16_t data_len = (GDMA_TRANSFER_SIZE);
for (uint16_t i = 0; i < GDMA_TRANSFER_SIZE; i++)
{
    DBG_DIRECT("GDMA_Recv_Buffer[%d] = 0x%x", i, GDMA_Recv_Buffer[i]);
}
uart_senddata_continuous(UART0, GDMA_Recv_Buffer, data_len);

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

常见问题

当需要使用的SPI频率过高(大于10MHz)时,推荐使用DMA传输数据。 如果发现传输数据过程中出现CS线被拉高的情况,可以增大GDMA的MSize以提高DMA的传输效率。