H753_CPP_W25Q_QSPI_Flash_Read_Write_01 1.0
STM32H753 W25Q QSPI flash (Adafruit 5632/5633/5634) read/write tutorial
Loading...
Searching...
No Matches
W25QFlashQSPI.cpp
1/*
2 * W25QFlashQSPI.cpp
3 *
4 * Created on: Sep 3, 2026
5 * Author: johng
6 */
7
8#include "W25QFlashQSPI.hpp"
9
10extern QSPI_HandleTypeDef hqspi;
11
12namespace {
13
21QSPI_CommandTypeDef makeBaseCommand() {
22 QSPI_CommandTypeDef cmd = {0};
23 cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
24 cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
25 cmd.DummyCycles = 0;
26 cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
27 cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
28 cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
29 return cmd;
30}
31
32} // namespace
33
34W25QFlashQSPI::W25QFlashQSPI(uint32_t qspiTimeoutMs) : _hqspi(&hqspi), _qspiTimeoutMs(qspiTimeoutMs) {
35}
36
37HAL_StatusTypeDef W25QFlashQSPI::runCommand(uint8_t instruction) {
38 QSPI_CommandTypeDef cmd = makeBaseCommand();
39 cmd.Instruction = instruction;
40 cmd.AddressMode = QSPI_ADDRESS_NONE;
41 cmd.DataMode = QSPI_DATA_NONE;
42
43 HAL_StatusTypeDef status = HAL_QSPI_Command(_hqspi, &cmd, _qspiTimeoutMs);
44 return status;
45}
46
47HAL_StatusTypeDef W25QFlashQSPI::readCommand(uint8_t instruction, uint8_t *resp, uint32_t len) {
48 QSPI_CommandTypeDef cmd = makeBaseCommand();
49 cmd.Instruction = instruction;
50 cmd.AddressMode = QSPI_ADDRESS_NONE;
51 cmd.DataMode = (len > 0) ? QSPI_DATA_1_LINE : QSPI_DATA_NONE;
52 cmd.NbData = len;
53
54 HAL_StatusTypeDef status = HAL_QSPI_Command(_hqspi, &cmd, _qspiTimeoutMs);
55 if (status == HAL_OK && len > 0) {
56 status = HAL_QSPI_Receive(_hqspi, resp, _qspiTimeoutMs);
57 }
58 return status;
59}
60
61HAL_StatusTypeDef W25QFlashQSPI::writeCommand(uint8_t instruction, const uint8_t *data, uint32_t len) {
62 QSPI_CommandTypeDef cmd = makeBaseCommand();
63 cmd.Instruction = instruction;
64 cmd.AddressMode = QSPI_ADDRESS_NONE;
65 cmd.DataMode = (len > 0) ? QSPI_DATA_1_LINE : QSPI_DATA_NONE;
66 cmd.NbData = len;
67
68 HAL_StatusTypeDef status = HAL_QSPI_Command(_hqspi, &cmd, _qspiTimeoutMs);
69 if (status == HAL_OK && len > 0) {
70 status = HAL_QSPI_Transmit(_hqspi, const_cast<uint8_t *>(data), _qspiTimeoutMs);
71 }
72 return status;
73}
74
75HAL_StatusTypeDef W25QFlashQSPI::eraseCommand(uint8_t instruction, uint32_t addr) {
76 HAL_StatusTypeDef status = writeEnable();
77 if (status != HAL_OK) {
78 return status;
79 }
80
81 QSPI_CommandTypeDef cmd = makeBaseCommand();
82 cmd.Instruction = instruction;
83 cmd.AddressMode = QSPI_ADDRESS_1_LINE;
84 cmd.AddressSize = QSPI_ADDRESS_24_BITS;
85 cmd.Address = addr;
86 cmd.DataMode = QSPI_DATA_NONE;
87
88 status = HAL_QSPI_Command(_hqspi, &cmd, _qspiTimeoutMs);
89 return status;
90}
91
92HAL_StatusTypeDef W25QFlashQSPI::writeEnable() {
93 HAL_StatusTypeDef status = runCommand(W25Q_CMD_WRITE_ENABLE);
94 return status;
95}
96
98 // Fail-hard: see the class-level Doxygen note on Init() for why a HAL
99 // failure here calls Error_Handler() while every other method below
100 // propagates instead.
101 HAL_StatusTypeDef status = runCommand(W25Q_CMD_ENABLE_RESET);
102 if (status != HAL_OK) {
103 Error_Handler();
104 }
105
106 status = runCommand(W25Q_CMD_RESET);
107 if (status != HAL_OK) {
108 Error_Handler();
109 }
110
111 HAL_Delay(1); // tRST: chip reset recovery time
112
113 uint32_t id = 0;
114 if (!readJEDECID(id)) {
115 Error_Handler(); // QUADSPI transfer itself failed -- distinct from a merely wrong id below
116 }
117
118 bool detected = ((id >> 16) & 0xFF) == W25Q_JEDEC_MANUFACTURER_ID;
119 return detected; // a *wrong* ID is a legitimate non-fatal outcome, not a HAL fault
120}
121
122bool W25QFlashQSPI::readJEDECID(uint32_t &id) {
123 // Not escalated to Error_Handler() here -- this is a caller-facing
124 // method, not Init() itself (which calls this and escalates on our
125 // behalf). The bool return lets the caller tell "QUADSPI transfer
126 // failed" apart from "transfer succeeded, id just isn't a valid W25Q ID".
127 uint8_t resp[3] = { 0, 0, 0 };
128 HAL_StatusTypeDef status = readCommand(W25Q_CMD_READ_JEDEC_ID, resp, sizeof(resp));
129 if (status != HAL_OK) {
130 return false;
131 }
132
133 id = ((uint32_t)resp[0] << 16) | ((uint32_t)resp[1] << 8) | resp[2];
134 return true;
135}
136
137bool W25QFlashQSPI::readStatus(uint8_t &status1, uint8_t &status2) {
138 HAL_StatusTypeDef status = readCommand(W25Q_CMD_READ_STATUS1, &status1, 1);
139 if (status != HAL_OK) {
140 return false;
141 }
142
143 status = readCommand(W25Q_CMD_READ_STATUS2, &status2, 1);
144 if (status != HAL_OK) {
145 return false;
146 }
147
148 return true;
149}
150
151bool W25QFlashQSPI::waitUntilReady(uint32_t timeout_ms) {
152 uint32_t start = HAL_GetTick();
153 uint8_t chipStatus = 0;
154
155 do {
156 HAL_StatusTypeDef status = readCommand(W25Q_CMD_READ_STATUS1, &chipStatus, 1);
157 if (status != HAL_OK) {
158 return false; // QUADSPI transfer failure -- propagate, don't escalate (see Init())
159 }
160
161 if ((chipStatus & 0x01) == 0) { // BUSY bit clear
162 return true;
163 }
164 } while ((HAL_GetTick() - start) < timeout_ms);
165
166 return false; // BUSY-bit timeout -- an expected outcome on a long erase,
167 // not a HAL communication failure either way.
168}
169
170bool W25QFlashQSPI::eraseSector(uint32_t addr) {
171 HAL_StatusTypeDef status = eraseCommand(W25Q_CMD_ERASE_SECTOR, addr);
172 if (status != HAL_OK) {
173 return false;
174 }
175
176 bool ready = waitUntilReady(500); // datasheet typical/max sector erase time varies by capacity
177 return ready;
178}
179
180bool W25QFlashQSPI::eraseRange(uint32_t addr, uint32_t len) {
181 if (len == 0) {
182 return true;
183 }
184
185 uint32_t firstSector = addr / W25Q_SECTOR_SIZE;
186 uint32_t lastSector = (addr + len - 1) / W25Q_SECTOR_SIZE;
187
188 for (uint32_t sector = firstSector; sector <= lastSector; sector++) {
189 if (!eraseSector(sector * W25Q_SECTOR_SIZE)) {
190 return false;
191 }
192 }
193
194 return true;
195}
196
197bool W25QFlashQSPI::eraseBlock(uint32_t addr) {
198 HAL_StatusTypeDef status = eraseCommand(W25Q_CMD_ERASE_BLOCK, addr);
199 if (status != HAL_OK) {
200 return false;
201 }
202
203 bool ready = waitUntilReady(3000); // datasheet typical/max block erase time varies by capacity
204 return ready;
205}
206
208 HAL_StatusTypeDef status = writeEnable();
209 if (status != HAL_OK) {
210 return false;
211 }
212
213 status = runCommand(W25Q_CMD_ERASE_CHIP);
214 if (status != HAL_OK) {
215 return false;
216 }
217
218 // Chip erase time scales heavily with capacity (16/64/128 Mbit) -- verify
219 // this timeout against the specific chip's datasheet before relying on it.
220 bool ready = waitUntilReady(100000);
221 return ready;
222}
223
224bool W25QFlashQSPI::readBuffer(uint32_t addr, uint8_t *buf, uint32_t len) {
225 QSPI_CommandTypeDef cmd = makeBaseCommand();
226 cmd.Instruction = W25Q_CMD_READ;
227 cmd.AddressMode = QSPI_ADDRESS_1_LINE;
228 cmd.AddressSize = QSPI_ADDRESS_24_BITS;
229 cmd.Address = addr;
230 cmd.DataMode = QSPI_DATA_1_LINE;
231 cmd.NbData = len;
232
233 HAL_StatusTypeDef status = HAL_QSPI_Command(_hqspi, &cmd, _qspiTimeoutMs);
234 if (status == HAL_OK) {
235 status = HAL_QSPI_Receive(_hqspi, buf, _qspiTimeoutMs);
236 }
237
238 return status == HAL_OK;
239}
240
241bool W25QFlashQSPI::writeBuffer(uint32_t addr, const uint8_t *data, uint32_t len) {
242 uint32_t written = 0;
243
244 while (written < len) {
245 uint32_t pageOffset = (addr + written) % W25Q_PAGE_SIZE;
246 uint32_t chunk = W25Q_PAGE_SIZE - pageOffset;
247 if (chunk > (len - written)) {
248 chunk = len - written;
249 }
250
251 HAL_StatusTypeDef status = writeEnable();
252 if (status != HAL_OK) {
253 return false;
254 }
255
256 QSPI_CommandTypeDef cmd = makeBaseCommand();
257 cmd.Instruction = W25Q_CMD_PAGE_PROGRAM;
258 cmd.AddressMode = QSPI_ADDRESS_1_LINE;
259 cmd.AddressSize = QSPI_ADDRESS_24_BITS;
260 cmd.Address = addr + written;
261 cmd.DataMode = QSPI_DATA_1_LINE;
262 cmd.NbData = chunk;
263
264 status = HAL_QSPI_Command(_hqspi, &cmd, _qspiTimeoutMs);
265 if (status == HAL_OK) {
266 status = HAL_QSPI_Transmit(_hqspi, const_cast<uint8_t *>(data + written), _qspiTimeoutMs);
267 }
268
269 if (status != HAL_OK) {
270 return false;
271 }
272 if (!waitUntilReady(50)) { // page program typical max is a few ms; generous margin
273 return false;
274 }
275
276 written += chunk;
277 }
278
279 return true;
280}
bool readStatus(uint8_t &status1, uint8_t &status2)
Reads both status registers.
bool waitUntilReady(uint32_t timeout_ms=100)
Polls Status Register 1's BUSY bit until clear or timeout.
bool eraseBlock(uint32_t addr)
Erases the single 64KB block containing the given address.
W25QFlashQSPI(uint32_t qspiTimeoutMs=100)
Constructs the driver.
bool writeBuffer(uint32_t addr, const uint8_t *data, uint32_t len)
Writes len bytes starting at addr, paging internally at 256-byte boundaries as the chip's Page Progra...
bool readJEDECID(uint32_t &id)
Reads the 3-byte JEDEC ID (manufacturer, memory type, capacity).
bool eraseChip()
Erases the entire chip. Can take tens of seconds on larger capacities – verify the timeout against th...
bool readBuffer(uint32_t addr, uint8_t *buf, uint32_t len)
Reads len bytes starting at addr into buf.
bool eraseRange(uint32_t addr, uint32_t len)
Erases every sector spanned by [addr, addr + len), computing the sector boundaries automatically – th...
bool Init()
Resets the chip and verifies communication via the JEDEC ID.
bool eraseSector(uint32_t addr)
Erases the single 4KB sector containing the given address.