一.Quad-SPI简介
在第十章和第十一章中,我们介绍了标准的SPI总线,SPI由四根线控制,NSS为片选,SCK为时钟信号线。MISO,MOSI为数据线,一根作为输入,一根作为输出。
Quad-SPI,即四线SPI,由此可知其数据线比标准的SPI接口要多,最多支持四条数据线同时传输。 连接单、双或四(条数据线) SPI Flash 存储介质。Quad-SPI总共有6根控制线:CS为片选,CLK为时钟信号线。IO0~IO3为数据线,可以发送数据也可以接收数据。
二.Quad-SPI 命令序列
QUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五个阶段 任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。 nCS 在每条指令开始前下降,在每条指令完成后再次上升。指令阶段 这一阶段将发送一条8位指令到flash,指定待执行的类型。指令可以单线,双线或四线传输。
地址阶段 在地址阶段,将1-4字节发送到flash,指示操作地址,地址阶段可一次发送 1 位(在单线 SPI 模式中通过 IO0)、2 位(在双线 SPI 模式中通过 IO0/IO1 )或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。
交替字节阶段 在交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。可以通过可以单线,双线或四线传输。空指令周期阶段 在空指令周期阶段,给定的 1-31个周期内不发送或接收任何数据,目的是当采用更高的时钟频率时,给 Flash 留出准备数据阶段的时间。 数据阶段 在数据阶段,可从 Flash 接收或向其发送任意数量的字节。数据阶段如果发送数据IO口为输出,如果是接收数据则IO切换为输入,一次可发送 1 位(在单线 SPI 模式中通过 IO0)、2 位(在双线 SPI 模式中通过 IO0/IO1 )或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。
三.新建工程
复制串口printf的工程,修改文件夹名。击STM32F746I.ioc打开STM32cubeMX的工程文件重新配置,QuadSPI模式选择Bank1四线SPI。
此时QUADSPI对应的IO口会被选中。
QUADSPI配置如下。时钟分频设置为2,故QSPI时钟 = 216M / (2+ 1) = 72MHz
FIFO深度设置为4个字节,配置QSPI 在Flash 驱动信号后过半个CLK 周期才对Flash 驱动的数据采样。 Flash Size设置外部存储器大小,本实验以W25Q128FV芯片为例,大小为16MB(使用24位寻址),则设置为23。 四.应用程序
生成报告以及代码,编译程序。在quadspi.c文件中可以看到初始化函数。在stm32f7xx_hal_qspic.h头文件中可以看到QSPI的操作函数。分别对应轮询,中断和DMA三种控制方式。 下面为W25QXX的驱动文件。下载并添加进工程中。(操作方式参照第十一章)
在main.c文件中添加头文件
3 | # include "stm32746g_qspi.h" |
声明变量: s_command为QSPI命令结构体,配置命令;pData存储读取ID值,rData,wData作为读写数据缓存
03 | QSPI_CommandTypeDef s_command; |
在main函数中添加应用程序,第一部分初始化W25Q128FV芯片,第二部分分别用单线,双线和四线三种模式读设备ID。第三部分则是读写擦除芯片操作实验。
002 | printf( "W25Q128FV QuadSPI Test ....\r\n\r\n" ); |
009 | s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; |
010 | s_command.Instruction = READ_ID_CMD; |
011 | s_command.AddressMode = QSPI_ADDRESS_1_LINE; |
012 | s_command.AddressSize = QSPI_ADDRESS_24_BITS; |
013 | s_command.Address = 0x000000; |
014 | s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; |
015 | s_command.DataMode = QSPI_DATA_1_LINE; |
016 | s_command.DummyCycles = 0; |
017 | s_command.NbData = 2; |
018 | s_command.DdrMode = QSPI_DDR_MODE_DISABLE; |
019 | s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; |
020 | s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; |
022 | if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
026 | if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
030 | printf( "SPI I/0 Read Device ID : 0x%2X 0x%2X\r\n" ,pData[0],pData[1]); |
034 | s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; |
035 | s_command.Instruction = DUAL_READ_ID_CMD; |
036 | s_command.AddressMode = QSPI_ADDRESS_2_LINES; |
037 | s_command.AddressSize = QSPI_ADDRESS_24_BITS; |
038 | s_command.Address = 0x000000; |
039 | s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_2_LINES; |
040 | s_command.AlternateBytesSize= QSPI_ALTERNATE_BYTES_8_BITS; |
041 | s_command.AlternateBytes = 0; |
042 | s_command.DataMode = QSPI_DATA_2_LINES; |
043 | s_command.DummyCycles = 0; |
044 | s_command.NbData = 4; |
045 | s_command.DdrMode = QSPI_DDR_MODE_DISABLE; |
046 | s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; |
047 | s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; |
049 | if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
053 | if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
057 | printf( "Dual I/O Read Device ID : 0x%2X 0x%2X\r\n" ,pData[0],pData[1]); |
060 | s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; |
061 | s_command.Instruction = QUAD_READ_ID_CMD; |
062 | s_command.AddressMode = QSPI_ADDRESS_4_LINES; |
063 | s_command.AddressSize = QSPI_ADDRESS_24_BITS; |
064 | s_command.Address = 0x000000; |
065 | s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_4_LINES; |
066 | s_command.AlternateBytesSize= QSPI_ALTERNATE_BYTES_8_BITS; |
067 | s_command.AlternateBytes = 0x00; |
068 | s_command.DataMode = QSPI_DATA_4_LINES; |
069 | s_command.DummyCycles = 4; |
070 | s_command.NbData = 2; |
071 | s_command.DdrMode = QSPI_DDR_MODE_DISABLE; |
072 | s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; |
073 | s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; |
075 | if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
079 | if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
083 | printf( "Quad I/O Read Device ID : 0x%2X 0x%2X\r\n" ,pData[0],pData[1]); |
086 | s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; |
087 | s_command.Instruction = READ_JEDEC_ID_CMD; |
088 | s_command.AddressMode = QSPI_ADDRESS_NONE; |
089 | s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; |
090 | s_command.DataMode = QSPI_DATA_1_LINE; |
091 | s_command.DummyCycles = 0; |
092 | s_command.NbData = 3; |
093 | s_command.DdrMode = QSPI_DDR_MODE_DISABLE; |
094 | s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; |
095 | s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; |
097 | if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
101 | if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
105 | printf( "Read JEDEC ID : 0x%2X 0x%2X 0x%2X\r\n\r\n" ,pData[0],pData[1],pData[2]); |
110 | for (i =0;i<0x100;i ++) |
116 | if (BSP_QSPI_Erase_Block(0) == QSPI_OK) |
117 | printf( " QSPI Erase Block ok\r\n" ); |
121 | if (BSP_QSPI_Write(wData,0x00,0x100)== QSPI_OK) |
122 | printf( " QSPI Write ok\r\n" ); |
126 | if (BSP_QSPI_Read(rData,0x00,0x100)== QSPI_OK) |
127 | printf( " QSPI Read ok\r\n\r\n" ); |
131 | printf( "QSPI Read Data : \r\n" ); |
132 | for (i =0;i<0x100;i++) |
133 | printf( "0x%02X " ,rData[i]); |
136 | for (i =0;i<0x100;i++) |
137 | if (rData[i] != wData[i])printf( "0x%02X 0x%02X " ,wData[i],rData[i]); |
140 | if (memcmp(wData,rData,0x100) == 0 ) |
141 | printf( " W25Q128FV QuadSPI Test OK\r\n" ); |
143 | printf( " W25Q128FV QuadSPI Test False\r\n" ); |
将W25QXX DataFlash Board模块插入到Open746I开发板I2C1中,编译程序并下载到开发板。打开串口调试助手。设置波特率为115200。串口助手上会显示如下信息。
五.程序讲解
现在以四线读设备ID为例讲解QSPI如何进行一次读写操作。如下为四线读制造商/设备ID命令(94H)
程序中先根据上面时序配置s_command命令结构体, - 命令为1线,命令为QUAD_READ_ID_CMD,在w25q128fv.h头文件中宏定义为0x94。
- 地址为4线,地址长度为24位,地址为0x000000。
- 交替字节阶段设置为4线,长度为8位,备用字节为0x00。
- 空指令阶段为4个时钟周期
- 数据为4线,字节为两个字节。
程序中先通过HAL_QSPI_Command()将命令发送出去,然后通过HAL_QSPI_Receive()命令接收数据。即完成一次读操作,如果是写操作,同样是先配置命令结构体,然后发送命令,最后通过HAL_QSPI_Transmit()命令发送数据。
02 | s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; |
03 | s_command.Instruction = QUAD_READ_ID_CMD; |
04 | s_command.AddressMode = QSPI_ADDRESS_4_LINES; |
05 | s_command.AddressSize = QSPI_ADDRESS_24_BITS; |
06 | s_command.Address = 0x000000; |
07 | s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_4_LINES; |
08 | s_command.AlternateBytesSize= QSPI_ALTERNATE_BYTES_8_BITS; |
09 | s_command.AlternateBytes = 0x00; |
10 | s_command.DataMode = QSPI_DATA_4_LINES; |
11 | s_command.DummyCycles = 4; |
13 | s_command.DdrMode = QSPI_DDR_MODE_DISABLE; |
14 | s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; |
15 | s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; |
17 | if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
21 | if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) |
25 | printf( "Quad I/O Read Device ID : 0x%2X 0x%2X\r\n" ,pData[0],pData[1]); |
关于W25Q128fv的读写擦除等驱动函数可以查看stm32746g-qspi.c文件,这里不再详细讲解。
|