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 的通信测试。
配置选项
可配置如下宏修改是否使用软件控制 CS 线。
#define CONFIG_SPI_SW_SIM_CS 1 /*< Set this macro to 1 to enable software control CS. */
可配置如下宏修改引脚定义。
#define SPI_SCK_PIN P4_0 #define SPI_MOSI_PIN P4_1 #define SPI_MISO_PIN P4_2 #define SPI_CS_PIN P4_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
编译和下载
该示例的编译和下载流程,可参考 编译和下载。
测试验证
初始化完毕后,SPI 通过 GDMA TX 向外部 flash 发送指令,执行数据读取,写入,擦除等操作。同时通过 GDMA RX 读取外部 flash 回复的数据。
当 GDMA TX 通道数据传输完成时,会打印 log 信息。
gdma tx data finish!
当 GDMA RX 通道数据传输完成时,会打印传输完成说明,收到的数据长度和数据内容。
读取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
使能写操作时,打印如下内容:第一个字节为dummy read。
data_len = 1 dma rx data[0] = 000000ff
执行擦除操作时,打印如下内容:写操作对应的四个字节为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
擦除完成后,读取该地址下的数据,打印如下内容:由于该地址下数据被擦除,因此读到的数据均为FF,前5个字节为dummy read。
data_len = 105 dma rx data[0] = 000000ff dma rx data[1] = 000000ff ... dma rx data[104] = 000000ff
执行数据写入操作,在指定内存下写入{a, b, …}等 100 个数据。打印如下内容:写操作对应的前4个字节为指令的dummy read,后100个字节为数据的dummy read。
data_len = 104 dma rx data[0] = 000000ff ... dma rx data[103] = 000000ff
写入数据之后再次进行数据读取,打印读取的数据内容:前 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 中的 初始化流程 部分。
调用
Pad_Config()
与Pinmux_Config()
,配置对应引脚的 PAD 和 PINMUX。如果配置CONFIG_SPI_SW_SIM_CS
为1
,则需要将 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 }
调用
RCC_PeriphClockCmd()
,开启 SPI 时钟。对 SPI 外设进行初始化:
定义
SPI_InitTypeDef
类型SPI_InitStruct
,调用SPI_StructInit()
将SPI_InitStruct
预填默认值。根据需求修改
SPI_InitStruct
参数,SPI 的初始化参数配置如下表。调用
SPI_Init()
,初始化 SPI 外设。
SPI Hardware Parameters |
Setting in the |
SPI |
---|---|---|
SPI Transfer Mode |
||
SPI Data Size |
||
Clock Mode - CPOL |
||
Clock Mode - CPHA |
||
Clock Prescaler |
20 |
|
Frame Format |
||
TX GDMA Enable |
||
RX GDMA Enable |
||
TX Waterlevel |
31 |
|
RX Waterlevel |
0 |
备注
SPI 使用 GDMA 传输时,推荐配置 TX Waterlevel 为 SPI_TX_FIFO_SIZE - MSize,RX Waterlevel 为 MSize - 1。
调用
RCC_PeriphClockCmd()
,开启 GDMA 时钟。对 GDMA 外设进行初始化:
定义
GDMA_InitTypeDef
类型GDMA_InitStruct
,调用GDMA_StructInit()
将GDMA_InitStruct
预填默认值。根据需求修改
GDMA_InitStruct
参数。GDMA TX 和 RX 通道的初始化参数配置如下表。调用GDMA_Init()
,初始化 GDMA 外设。配置 GDMA 总传输完成中断
GDMA_INT_Transfer
和 NVIC,NVIC 相关配置可参考 中断配置。
GDMA Hardware Parameters |
Setting in the |
GDMA TX Channel |
GDMA RX Channel |
---|---|---|---|
Channel Num |
2 |
4 |
|
Transfer Direction |
|||
Buffer Size |
- |
- |
|
Source Address Increment or Decrement |
|||
Destination Address Increment or Decrement |
|||
Source Data Size |
|||
Destination Data Size |
|||
Source Burst Transaction Length |
|||
Destination Burst Transaction Length |
|||
Source Address |
|
|
|
Destination Address |
|
|
|
Source Handshake |
- |
||
Destination Handshake |
- |
功能实现
根据 W25Q128 的硬件信息和通信协议,在执行不同操作时 SPI 需要发送的数据长度和内容是不同的,因此需要在每次通信开始前对 GDMA 的数据长度和数据内容进行设定。调用
GDMA_SetBufferSize()
设定 GDMA 对应通道的 Block Size。由于 SPI 设定为全双工通信,因此对于 GDMA TX 通道和 GDMA RX 通道,需要设置相同的 Block Size。在设定完成后使能 SPI,GDMA TX 和 GDMA RX 通道。当 GDMA TX 通道和 GDMA RX 通道传输完毕时,会进入相应通道的 GDMA 中断,在 GDMA RX 中断内会打印外部 flash 回复的数据信息,同时在对应中断内将
isDMATxDone
和isDMARxDone
分别置为true
。当发现两个 flag 位均被设置为
true
时,代表本次通信完成,此时可以开始下次通信流程。如果配置
CONFIG_SPI_SW_SIM_CS
为1
,则需要在每次通信开始前和通信结束后调用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); ...
在主程序中,调用
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);