STM32H7使用QSPI连接NOR闪存

STM32H7使用QSPI连接NOR闪存

硬件连接

本文以Bank2为例,实际应用也可以连Bank1。

只需CubeMX中选择对应选项,右边IO分配自动显示,按图连接即可
CubeMX配置QSPI

CubeMX配置

需配置的选项有:

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

代码基础

基础收发

根据手册,QSPI FLASH常用的操作有:

  1. 发送命令
    QSPI指令
  2. 读写状态寄存器
    QSPI指令
  3. 擦除
    QSPI指令
  4. 块编程(写入)
    QSPI指令
    QSPI指令
  5. 读取
    QSPI指令

这些东西一般遵循一个固定格式,在Flash手册的Instruction Set Table(指令集表)可以找到。一般来说是这样的:

  1. 发送一个指令数(一般必要)
  2. 发送地址(可选)
  3. 发送一些无关紧要的数据,但是要给时钟(Dummy clocks)(可选)
  4. 发送或读取数据

所以,HAL库将这些操作组合成一个结构体来表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief QSPI Command structure definition
*/
typedef struct
{
uint32_t Instruction; /* 8bit指令 */
uint32_t Address; /* 可选32bit地址 */
uint32_t AlternateBytes; /* 字节交换,一般不用,这里不讲 */
uint32_t AddressSize; /*地址长度 */
uint32_t AlternateBytesSize; /* 字节交换的长度 */
uint32_t DummyCycles; /* dummy clocks的数量(0到31) */
uint32_t InstructionMode; /* 指令用什么模式发(标准spi,dio,还是qio啥的) */
uint32_t AddressMode; /* 地址用什么模式发(标准spi,dio,还是qio啥的) */
uint32_t AlternateByteMode; /* 字节交换用什么模式发(标准spi,dio,还是qio啥的) */
uint32_t DataMode; /* 数据用什么模式发(标准spi,dio,还是qio啥的) */
uint32_t NbData; /* 数据数量*/
uint32_t DdrMode; /* 使能ddr模式,一般不用*/
uint32_t DdrHoldHalfCycle; /* DDR模式下,设置延迟半个时钟周期再做数据输出 */
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;//指令用1line spi发(标准spi)
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;//无dummy clock
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;//不ddr
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;//ddr模拟延时(无ddr就无意义)
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;//匹配值0x02
s_config.Mask = 0x02;//掩码0x02
s_config.MatchMode = QSPI_MATCH_MODE_AND;//读取的数值和掩码先进行and操作再与匹配值对比
s_config.StatusBytesSize = 1;//状态寄存器长1字节
s_config.Interval = 0x10;//隔0x10个时钟轮询一次
s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;//匹配到了自动停止

//配置读取状态寄存器2(指令0x05)
s_command.Instruction = 0x05;
s_command.DataMode = QSPI_DATA_1_LINE;//指令用1line spi发(标准spi)
s_command.NbData = 1;//数据长度1(因为在读取状态寄存器,这玩意长度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; // 禁止DDR模式
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; // 禁止DDR延时
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; // 在每个事务中发送指令

/* Send the command */
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;

/* 设置四路使能的状态寄存器,使能QE */
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; // 禁止DDR模式
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;// 禁止DDR延时
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;// 禁止DDR模式
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;// 禁止DDR延时
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;// 在每个事务中发送指令
//BUSY == 0
s_config.Match = 0x00;//匹配值
s_config.Mask = 0x01;//掩码
s_config.MatchMode = QSPI_MATCH_MODE_AND;//和运算
s_config.StatusBytesSize = 1;//状态寄存器长度
s_config.Interval = 0x10;//每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;// 禁止DDR模式
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;// 禁止DDR延时
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;// 在每个事务中发送指令
//BUSY == 0
s_config.Match = 0x00;//匹配值
s_config.Mask = 0x01;//掩码
s_config.MatchMode = QSPI_MATCH_MODE_AND;//和运算
s_config.StatusBytesSize = 1;//状态寄存器长度
s_config.Interval = 0x10;//每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;

/* Configure the command for the read instruction */
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;

/* Configure the memory mapped mode */
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 开发指南(水星版)

STM32H7使用QSPI连接NOR闪存

https://wuxiproj.mzy7.cn/posts/4bf43ad2.html

作者

【社区】UM4I

发布于

2024-08-06

更新于

2024-12-07

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×