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