一、使用STM32CubeMX配置QSPI接口

1、这里我们以四线QSPI为例

Image

2、Parameter Settings

Image

(1)Clock Prescaler:预分频系数n-1,Clock输入为100MHz,W25Q256最大支持104MHz,所以这里配为0,0+1=1分频; Image

(2)Fifo Threshold:因为采用的是四线QSPI,每个IO传输1Byte,一次可以传输4Byte; (3)Sample Shifting:这里两种都可以设置,只有在DDR模式下必须设置为QSPI_SAMPLE_SHIFTING_NONE (4)Flash Size:W25Q256总大小为256M-Bit = 32 M-Byte = 2 ^ 25 Byte,所以Flash Size取值24(25-1); (5)Chip Select High Time:该项表示在两次Commands之间CS必须保持高电平为多少个Clock,根据W25Q256 DataSheet可知tCSH >= 50ns,这里我们QSPI的时钟是100MHz,1Clock = 10ns,所以这里为5;

Image

(6)Clock Mode:Low(QSPI_CLOCK_MODE_0)或者 High(QSPI_CLOCK_MODE_3)都是可以的,在DataSheet中随便找一个时序图就可以发现它同时支持Mode0和Mode3。 Image

3、GPIO Setting

Image

(1)pin脚按照自己使用的板子的原理图连接进行配置; (2)这里需要特别注意的是GPIO的速度等级默认是Low,这样是无法达到我们预设的100MHz的,这里我们将其全部配置为Very High

二、主要的指令以及初始化步骤

1、写使能 0x06

对Flash或者寄存器进行写操作是必须先使能

2、状态寄存器2的QE位置1 0x31

QE置位后,/WP与/HOLD作为IO2和IO3使用

3、进入QPI模式 0x38

进入后,指令、地址、数据都需要使用4线模式

4、进入4字节模式 0xB7

默认3字节,寻址范围2^23 = 16M,W25Q256有32M,所以需要4字节才能访问到全部Flash

5、设置读参数指令 0xC0

Image

三、对HAL库的QSPI发送指令、发送数据和接收数据进行二次封装

 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
/* 配置QSPI发送命令
 * instruction 命令
 * address 目的地址
 * dummy_cycle 空指令周期
 * instruction_mode 指令模式:QSPI_INSTRUCTION_NONE、QSPI_INSTRUCTION_1_LINE、QSPI_INSTRUCTION_2_LINES、QSPI_INSTRUCTION_4_LINES
 * address_mode 地址模式:QSPI_ADDRESS_NONE、QSPI_ADDRESS_1_LINE、QSPI_ADDRESS_2_LINE、QSPI_ADDRESS_4_LINE
 * address_size 地址长度:QSPI_ADDRESS_8_BITS、QSPI_ADDRESS_16_BITS、QSPI_ADDRESS_24_BITS、QSPI_ADDRESS_32_BITS
 * data_mode 数据模式:QSPI_DATA_NONE、QSPI_DATA_1_LINE、QSPI_DATA_2_LINE、QSPI_DATA_4_LINE
 */
void qspi_send_cmd(uint32_t instruction, uint32_t address, uint32_t dummy_cycle, uint32_t instruction_mode, uint32_t address_mode, uint32_t address_size, uint32_t data_mode) {
	QSPI_CommandTypeDef cmd_handler;
	cmd_handler.Instruction = instruction;
	cmd_handler.Address = address;
	cmd_handler.DummyCycles = dummy_cycle;
	cmd_handler.InstructionMode = instruction_mode;
	cmd_handler.AddressMode = address_mode;
	cmd_handler.AddressSize = address_size;
	cmd_handler.DataMode = data_mode;
	cmd_handler.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; // 每次都发送指令
	cmd_handler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 无交替字节
	cmd_handler.DdrMode = QSPI_DDR_MODE_DISABLE; // 关闭DDR模式
	cmd_handler.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	HAL_QSPI_Command(&hqspi, &cmd_handler, 0x10);
}

uint8_t qspi_receive(uint8_t *buf, uint32_t data_len) {
	hqspi.Instance -> DLR = data_len - 1;
	if(HAL_QSPI_Receive(&hqspi, buf, 0x10) == HAL_OK) {
		return 0;
	}
  else {
		return 1;
	}
}

uint8_t qspi_transmit(uint8_t *buf, uint32_t data_len) {
	hqspi.Instance -> DLR = data_len - 1;
	if(HAL_QSPI_Transmit(&hqspi, buf, 0x10) == HAL_OK) {
		return 0;
	}
  else {
		return 1;
	}
}

四、W25Q256操作函数库

这里包含对W25Q256的初始化、写入、读取和擦除等函数,注释详细,直接贴出来了,分为.c和.h两个文件:

w25qxx.c

  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
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#include "quadspi.h"
#include "w25qxx.h"

// 4K bytes为一个Sector, 16个Sector为1个Block

uint16_t W25Q_TYPE = W25Q256;
uint8_t W25Q_QPI_MODE = 0; // QSPI模式标志:0 SPI模式;1 QPI模式

void w25q_qspi_enable(void);
uint8_t w25q_read_status_reg(uint8_t command);
void w25q_write_status_reg(uint8_t reg, uint8_t data);
void w25q_write_enable(void);
void w25q_write_disable(void);
void w25q_wait_busy(void);
uint16_t w25q_read_id(void);

void w25q_write_flash_page(uint8_t *buf, uint32_t address, uint16_t length);
void w25q_erase_chip(void);
void w25q_erase_sector(uint32_t address);

void w25q_init(void) {
	w25q_qspi_enable();
	W25Q_TYPE = w25q_read_id();
	if(W25Q_TYPE == W25Q256) {
		uint8_t status_reg3 = w25q_read_status_reg(3);
		if(status_reg3 & 0x01 == 0x00) { // 不是4字节地址模式,需进入
			w25q_write_enable();
			qspi_send_cmd(W25Q_Enable4ByteAddr, 0, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
		}
		w25q_write_enable();
		qspi_send_cmd(W25Q_SetReadParam, 0, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_4_LINES);
		uint8_t temp = 0x30;
		qspi_transmit(&temp, 1);
	}
}

void w25q_qspi_enable(void) { // 使能QSPI模式
	uint8_t status_reg2 = w25q_read_status_reg(2);
	if((status_reg2 & 0x02) == 0x00) {
		w25q_write_enable();
		status_reg2 |= 0x02;
		w25q_write_status_reg(2, status_reg2);
	}
	qspi_send_cmd(W25Q_EnterQPIMode, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
	W25Q_QPI_MODE = 1;
}

/* 状态寄存器说明
 * 状态寄存器1(默认0x00):
 * 7   6  5  4   3   2   1   0
 * SPR RV TB BP2 BP1 BP0 WEL BUSY
 * SPR 默认0,状态寄存器保护位,配合WP使用
 * TB、BP2、BP1、BP0 FLASH区域写保护设置
 * WEL 写使能锁定
 * BUSY 忙标记位(1忙,0空闲)
 * 状态寄存器2:
 * 7   6   5   4   3   2   1  0
 * SUS CMP LB3 LB2 LB1 (R) QE SPR1
 * 状态寄存器3:
 * 7        6    5    4   3   2   1   0
 * HOLD/RST BRV1 DRV0 (R) (R) WPS ADP ADS
 */
uint8_t w25q_read_status_reg(uint8_t reg) { // 读状态寄存器
	uint8_t byte, command;
	switch(reg) {
		case 1:
			command = W25Q_ReadStatusReg1;
			break;
		case 2:
			command = W25Q_ReadStatusReg2;
			break;
		case 3:
			command = W25Q_ReadStatusReg3;
			break;
		default:
			command = W25Q_ReadStatusReg1;
			break;
	}
	if(W25Q_QPI_MODE) qspi_send_cmd(command, 0, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_4_LINES);
	else qspi_send_cmd(command, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_1_LINE);
	qspi_receive(&byte, 1);
	return byte;
}

void w25q_write_status_reg(uint8_t reg, uint8_t data) { // 写状态寄存器
	uint8_t command;
	switch(reg) {
		case 1:
			command = W25Q_WriteStatusReg1;
			break;
		case 2:
			command = W25Q_WriteStatusReg2;
			break;
		case 3:
			command = W25Q_WriteStatusReg3;
			break;
		default:
			command = W25Q_WriteStatusReg1;
			break;
	}
	if(W25Q_QPI_MODE) qspi_send_cmd(command, 0, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_4_LINES);
	else qspi_send_cmd(command, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_1_LINE);
	qspi_transmit(&data, 1);
}

void w25q_write_enable(void) { // W25Q写使能:将S1寄存器的WEL置位
	if(W25Q_QPI_MODE) qspi_send_cmd(W25Q_WriteEnable, 0, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
	else qspi_send_cmd(W25Q_WriteEnable, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
}

void w25q_write_disable(void) { // W25Q写禁止:将S1寄存器的WEL清零
	if(W25Q_QPI_MODE) qspi_send_cmd(W25Q_WriteDisable, 0, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
	else qspi_send_cmd(W25Q_WriteDisable, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
}

void w25q_wait_busy(void) { // 等待空闲
	while((w25q_read_status_reg(1) & 0x01) == 0x01);
}

uint16_t w25q_read_id(void) {
	uint8_t temp[2];
	if(W25Q_QPI_MODE) qspi_send_cmd(W25Q_ManufactDeviceID, 0, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, QSPI_ADDRESS_24_BITS, QSPI_DATA_4_LINES);
	else qspi_send_cmd(W25Q_ManufactDeviceID, 0, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);
	qspi_receive(temp, 2);
	return (temp[0] << 8) | temp[1];
}

/* 读取FLASH,仅支持QPI模式
 * buf 数据存储区
 * address 开始读取的地址,最大32bit
 * length 读取的字节长度,最大65535
 */
void w25q_read_flash(uint8_t *buf, uint32_t address, uint16_t length) {
	qspi_send_cmd(W25Q_FastReadData, address, 8, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, QSPI_ADDRESS_32_BITS, QSPI_DATA_4_LINES);
	qspi_receive(buf, length);
}

/* 在一页(0~65535)内写入少于256个字节的数据,在指定地址开始写入最大256个字节的数据
 * buf 数据存储区
 * address 开始读取的地址,最大32bit
 * length 读取的字节长度,最大256,不应该超过该页剩余的字节数
 */
void w25q_write_flash_page(uint8_t *buf, uint32_t address, uint16_t length) {
	w25q_write_enable();
	qspi_send_cmd(W25Q_PageProgram, address, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, QSPI_ADDRESS_32_BITS, QSPI_DATA_4_LINES);
	qspi_transmit(buf, length);
	w25q_wait_busy();
}

/* 在指定地址开始写入指定长度的数据,但要确保地址不越界
 * 无检验写FLASH,必须确保所写地址范围内的数据全部为0xFF,否则在0xFF处写入的数据将失败,具有自动换页功能
 */
void w25q_write_flash_no_check(uint8_t *buf, uint32_t address, uint16_t length) {
	uint16_t pageremain = 256 - address % 256; // 单页剩余字节数
	if(length <= pageremain) { // 不大于256个字节
		pageremain = length;
	}
	while(1) {
		w25q_write_flash_page(buf, address, length);
		if(length == pageremain) {
			break;
		}
		else {
			buf += pageremain;
			address += pageremain;
			length -= pageremain; // 减去已经写入了的字节数
			if(length > 256) { // 一次可以写入256个字节
				pageremain = 256;
			}
			else { // 不够256个字节
				pageremain = length;
			}
		}
	}
}

/* 在指定地址开始写入指定长度的数据,但要确保地址不越界
 * 该函数带有自动擦除功能
 */
uint8_t W25Q_BUFFER[4096];
void w25q_write_flash(uint8_t *buf, uint32_t address, uint16_t length) {
	uint32_t secpos;
	uint16_t secoff, secremain, i;
	uint8_t *w25q_buf;
	w25q_buf = W25Q_BUFFER;
	secpos = address / 4096; // 扇区地址
	secoff = address % 4096; // 在扇区内的偏移
	secremain = 4096 - secoff; // 扇区剩余空间大小
	if(length <= secremain) { // 不大于4096个字节
		secremain = length;
	}
	while(1) {
		w25q_read_flash(w25q_buf, secpos * 4096, 4096); // 读出整个扇区的数据
		for(i = 0; i < secremain; i++) { // 校验数据
			if(w25q_buf[secoff + i] != 0xFF) break;
		}
		
		if(i < secremain) { // 需要擦除
			w25q_erase_sector(secpos); // 擦除这个扇区
			for(i = 0; i < secremain; i++) {
				w25q_buf[i + secoff] = buf[i];
			}
			w25q_write_flash_no_check(w25q_buf, secpos * 4096, 4096); // 写入整个扇区
		}
		else {
			w25q_write_flash_no_check(buf, address, secremain);
		}
		
		if(length == secremain) { // 写入完成
			break;
		}
		else {
			secpos++;
			secoff = 0;
			buf += secremain; // 指针偏移
			address += secremain; // 写地址偏移
			length -= secremain;
			if(length > 4096) secremain = 4096;
			else secremain = length;
		}
	}
}

void w25q_erase_chip(void) { // 擦除整个芯片,等待时间超长
	w25q_write_enable();
	w25q_wait_busy();
	qspi_send_cmd(W25Q_ChipErase, 0, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
	w25q_wait_busy();
}

void w25q_erase_sector(uint32_t address) { // 擦除一个扇区,至少150ms
	address *= 4096;
	w25q_write_enable();
	w25q_wait_busy();
	qspi_send_cmd(W25Q_SectorErase, address, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, QSPI_ADDRESS_32_BITS, QSPI_DATA_NONE);
	w25q_wait_busy();
}

w25qxx.h

 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
#ifndef __W25QXX_H
#define __W25QXX_H

#define W25Q80 	0XEF13
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17
#define W25Q256 0XEF18

extern uint16_t W25Q_TYPE;

// 指令表
#define W25Q_WriteEnable      0x06
#define W25Q_WriteDisable     0x04
#define W25Q_ReadStatusReg1   0x05
#define W25Q_ReadStatusReg2   0x35
#define W25Q_ReadStatusReg3   0x15
#define W25Q_WriteStatusReg1  0x01
#define W25Q_WriteStatusReg2  0x31
#define W25Q_WriteStatusReg3  0x11
#define W25Q_ReadData         0x03
#define W25Q_FastReadData     0x0B
#define W25Q_FastReadDual     0x3B
#define W25Q_PageProgram      0x02
#define W25Q_BlockErase       0xD8
#define W25Q_SectorErase      0x20
#define W25Q_ChipErase        0xC7
#define W25Q_PowerDown        0xB9
#define W25Q_ReleasePowerDown 0xAB
#define W25Q_DeviceID         0xAB
#define W25Q_ManufactDeviceID 0x90
#define W25Q_JedecDeviceID    0x9F
#define W25Q_Enable4ByteAddr  0xB7
#define W25Q_Exit4ByteAddr    0xE9
#define W25Q_SetReadParam     0xC0
#define W25Q_EnterQPIMode     0x38
#define W25Q_ExitQPIMode      0xFF

void w25q_init(void);
void w25q_read_flash(uint8_t *buf, uint32_t address, uint16_t length);
void w25q_write_flash(uint8_t *buf, uint32_t address, uint16_t length);
void w25q_write_flash_no_check(uint8_t *buf, uint32_t address, uint16_t length);

#endif