硬件连接
本文以Bank2为例,实际应用也可以连Bank1。
只需CubeMX中选择对应选项,右边IO分配自动显示,按图连接即可

CubeMX配置
需配置的选项有:
- 预分频器:和输入时钟共同决定spi频率,按需设置
- FIFO阈值:FIFO的大小,一般设置4就行
- Flash大小:Flash寻址位数-1,按Byte表示。需要参照Flash手册设置,这里使用4字节模式,也就是32Bit,减1等于31
- GPIO驱动能力:决定QSPI最高频率,驱动能力越强,能上的频率越高,但是EMI更严重,按需求设置,这里选择最高


代码基础
基础收发
根据手册,QSPI FLASH常用的操作有:
- 发送命令

- 读写状态寄存器

- 擦除

- 块编程(写入)


- 读取

这些东西一般遵循一个固定格式,在Flash手册的Instruction Set Table(指令集表)可以找到。一般来说是这样的:
- 发送一个指令数(一般必要)
- 发送地址(可选)
- 发送一些无关紧要的数据,但是要给时钟(Dummy clocks)(可选)
- 发送或读取数据
所以,HAL库将这些操作组合成一个结构体来表示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
typedef struct { uint32_t Instruction; uint32_t Address; uint32_t AlternateBytes; uint32_t AddressSize; uint32_t AlternateBytesSize; uint32_t DummyCycles; uint32_t InstructionMode; uint32_t AddressMode; uint32_t AlternateByteMode; uint32_t DataMode; uint32_t NbData; uint32_t DdrMode; uint32_t DdrHoldHalfCycle; uint32_t SIOOMode; }QSPI_CommandTypeDef;
|
我们可以先配置好这个结构体,然后将这个配好的结构体当一个参数,使用函数HAL_QSPI_Command发送指令,调用HAL_QSPI_Transmit发送数据,调用HAL_QSPI_Receive接收数据。(Transmit,Receive和其他HAL库通信函数一样,也提供中断和DMA版本)
自动轮询
部分Flash的操作需要较长时间,如果让CPU一直等待,对性能是很大的损耗,STM32H7的QSPI外设支持硬件自动轮询,可以设置一下命令,让硬件自动读取某些状态寄存器,和掩码进行指定计算后,再与给定的数值进行对比,如果对比成功,则通知CPU。
自动轮询也有个配置用的结构体:
1 2 3 4 5 6 7 8 9
| typedef struct { uint32_t Match; uint32_t Mask; uint32_t Interval; uint32_t StatusBytesSize; uint32_t MatchMode; uint32_t AutomaticStop; }QSPI_AutoPollingTypeDef;
|
然后调用HAL_QSPI_AutoPolling函数,将上面的QSPI_CommandTypeDef作为自动读取的方法,将QSPI_AutoPollingTypeDef作为自动轮询对比的条件,就能开始自动轮询。(自动轮询有中断版本)
示例
单独发送指令(命令,擦除):
W25QXX系列Flash的写使能命令是0x06,我们可以写如下代码来发送:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| QSPI_CommandTypeDef s_command;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = 0x06; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_NONE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; }
s_config.Match = 0x02; s_config.Mask = 0x02; s_config.MatchMode = QSPI_MATCH_MODE_AND; s_config.StatusBytesSize = 1; s_config.Interval = 0x10; s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
s_command.Instruction = 0x05; s_command.DataMode = QSPI_DATA_1_LINE; s_command.NbData = 1; if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; } return QSPI_OK;
|
读取(读Flash状态寄存器,四线模式快速读数据):
W25QXX系列Flash读取状态寄存器1的命令是0x05,我们可以写如下代码来读取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| uint8_t temp = 0; QSPI_CommandTypeDef s_command;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = 0x05; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DummyCycles = 0; s_command.DataMode = QSPI_DATA_1_LINE; s_command.NbData = 1; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return 0xff; }
if (HAL_QSPI_Receive(&hqspi, &temp, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return 0xff; } printf("REG1 = %X\r\n", temp);
|
W25QXX系列Flash的4线快速读取指令是0xEC,我们可以写如下代码来读取:
根据手册:地址用4字节模式,Dummy 6周期。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = 0xec; s_command.AddressMode = QSPI_ADDRESS_4_LINES; s_command.AddressSize = QSPI_ADDRESS_32_BITS; s_command.Address = ReadAddr; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_4_LINES; s_command.DummyCycles = 6; s_command.NbData = <size>; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; }
if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; } return QSPI_OK;
|
写数据(写flash状态寄存器,块编程):
W25QXX系列Flash的写寄存器2的命令是0x31,我们可以写如下代码来写入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| QSPI_CommandTypeDef s_command; uint8_t value = 0x02;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = 0x31; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.DummyCycles = 0; s_command.NbData = 1; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; }
if (HAL_QSPI_Transmit(&hqspi, &value, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; }
QSPI_AutoPollingTypeDef s_config;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = 0x05; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; s_config.Match = 0x00; s_config.Mask = 0x01; s_config.MatchMode = QSPI_MATCH_MODE_AND; s_config.StatusBytesSize = 1; s_config.Interval = 0x10; s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, Timeout) != HAL_OK) { return QSPI_ERROR; } return QSPI_OK;
|
W25QXX系列Flash的块编程的命令是0x31,我们可以写如下代码来写入:
根据手册:地址用4字节模式,Dummy 6周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| QSPI_CommandTypeDef s_command;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = EXT_QUAD_IN_FAST_PROG_CMD; s_command.AddressMode = QSPI_ADDRESS_1_LINE; s_command.AddressSize = W25QxJV_ADDR_SIZE; s_command.Address = WriteAddr; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.NbData = <size>; s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { printf("cmd error\r\n"); return QSPI_ERROR; }
HAL_StatusTypeDef a = HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); if (a != HAL_OK) { printf("tx err %x %x\r\n",a,hqspi.ErrorCode); return QSPI_ERROR; }
QSPI_AutoPollingTypeDef s_config;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = 0x05; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; s_config.Match = 0x00; s_config.Mask = 0x01; s_config.MatchMode = QSPI_MATCH_MODE_AND; s_config.StatusBytesSize = 1; s_config.Interval = 0x10; s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, Timeout) != HAL_OK) { return QSPI_ERROR; } return QSPI_OK;
|
内存映射模式
QSPI的内存映射模式只需要提供一个读取的指令就行了,越快越好,当然也能用慢速读取,不过一般用QSPI快速读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| QSPI_CommandTypeDef s_command; QSPI_MemoryMappedTypeDef s_mem_mapped_cfg;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = 0xec; s_command.AddressMode = QSPI_ADDRESS_4_LINES; s_command.AddressSize = W25QxJV_ADDR_SIZE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_4_LINES; s_command.DummyCycles = 6; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_HALF_CLK_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE; s_mem_mapped_cfg.TimeOutPeriod = 0; return HAL_QSPI_MemoryMapped(QSPIHandle, &s_command, &s_mem_mapped_cfg);
|
参考:
https://www.cnblogs.com/armfly/p/13964640.html
https://blog.csdn.net/qq_49015270/article/details/127757750
https://blog.csdn.net/cp1300/article/details/119947648
https://blog.csdn.net/qq_49015270/article/details/127757750
https://blog.csdn.net/sudaroot/article/details/109097135
正点原子STM32H743 开发指南(水星版)