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
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下复位按键,开始运行。
测试验证
初始化完毕后,DMA开始搬运TX数据。搬运完毕后,进入GDMA TX中断,打印LOG。
GDMA_TX_Channel_Handler: TX completed!
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
UART将接收到的数据通过FT232发送至PC端的串口助手,在串口助手上可以显示SPI收到的数据。
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
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_init
、board_uart_init
、driver_uart_init
和 driver_spi_gdma_init
。
board_spi_init
中包含了对SPI的PAD与PINMUX设置。
配置PAD:设置引脚、PINMUX模式、PowerOn、内部上拉。
配置PINMUX:分配引脚分别为SPI0_CLK_MASTER、SPI0_MO_MASTER、SPI0_MI_MASTER、SPI0_CSN_0_MASTER功能。
board_uart_init
中包含了对UART的PAD与PINMUX设置。
配置PAD:设置引脚、PINMUX模式、PowerOn、内部上拉。
配置PINMUX:分配引脚分别为UART0_TX、UART0_RX功能。
driver_uart_init
包含了对UART外设的初始化。
使能PCC时钟。
设置无奇偶校验。
停止位设置为1。
数据长度为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外设的初始化:
初始化SPI外设:
使能PCC时钟。
设置通信模式为EEPROM模式。
其他基础设置参考 SPI Polling 初始化流程。
设置NDF为GDMA_READ_SIZE_MAX。
使能SPI的DMA发送与接收功能。
设置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。
初始化GDMA TX:
使能PCC时钟。
使用GDMA通道3。
传输方向为 内存 到 外设 传输。
设置源端地址自增,目的端地址固定。
设置源端和目的端的MSize为8。
源端地址为
GDMA_WriteCmdBuffer
,目的端地址为FLASH_SPI->SPI_DR
。配置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);
...
初始化GDMA RX:
使能PCC时钟。
使用GDMA通道0。
传输方向为 外设 到 内存 传输。
设置源端地址固定,目的端地址自增。
设置源端和目的端的MSize为8。
源端地址为
FLASH_SPI->SPI_DR
,目的端地址为GDMA_Recv_Buffer
。配置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);
设置EEPORM传输模式下的数据接收长度为3(JEDEC_ID长度为3),使能SPI。
设置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);
功能实现
使能GDMA TX通道后,触发GDMA TX传输,传输完成后触发
GDMA_INT_Transfer
中断,进入中断处理函数GDMA_TX_Channel_Handler
。失能GDMA TX通道中断,打印进入中断信息,清除GDMA中断悬挂位,使能
GDMA_INT_Transfer
中断。
Flash接收到GDMA TX搬运的消息后,将内部信息通过GDMA RX通道搬运回来,搬运完成后触发
GDMA_INT_Transfer
中断,进入中断处理函数GDMA_RX_Channel_Handler
。失能GDMA TX通道中断,打印GDMA_Recv_Buffer信息。
执行
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的传输效率。