<p>The STM32F4CEU6 has 512KB of internal flash — enough for firmware, but not much else. The moment you need to store sensor logs, configuration data, audio samples, or display assets, you need external flash. The W25Q128JV is a 16MB QSPI NOR Flash chip from Winbond that is widely available, inexpensive, and well-supported. The NVX-F4 Flash board includes one pre-wired and ready to use.</p>
<p>This guide covers the full driver from scratch: wiring, CubeMX configuration, initialisation, and read, write, and erase operations.</p>
<h2>W25Q128JV Key Specifications</h2>
<ul>
<li>Capacity: 128Mbit (16MB)</li>
<li>Interface: SPI / Dual SPI / Quad SPI</li>
<li>Max clock: 133MHz (standard SPI)</li>
<li>Page size: 256 bytes</li>
<li>Sector size: 4KB (4096 bytes)</li>
<li>Block size: 64KB</li>
<li>Supply voltage: 2.7V – 3.6V</li>
<li>JEDEC ID: 0xEF, 0x40, 0x18</li>
</ul>
<h2>Wiring to STM32F4</h2>
<p>On the NVX-F4 Flash board the W25Q128JV is pre-connected to SPI1. If wiring your own:</p>
<table>
<thead><tr><th>W25Q128 Pin</th><th>STM32F4 Pin</th><th>Function</th></tr></thead>
<tbody>
<tr><td>CS (pin 1)</td><td>PA4</td><td>Chip Select (GPIO out, active low)</td></tr>
<tr><td>DO (pin 2)</td><td>PA6</td><td>SPI1 MISO</td></tr>
<tr><td>WP (pin 3)</td><td>3.3V</td><td>Write protect — tie high to disable</td></tr>
<tr><td>GND (pin 4)</td><td>GND</td><td>Ground</td></tr>
<tr><td>DI (pin 5)</td><td>PA7</td><td>SPI1 MOSI</td></tr>
<tr><td>CLK (pin 6)</td><td>PA5</td><td>SPI1 SCK</td></tr>
<tr><td>HOLD (pin 7)</td><td>3.3V</td><td>Hold — tie high to disable</td></tr>
<tr><td>VCC (pin 8)</td><td>3.3V</td><td>Power</td></tr>
</tbody>
</table>
<h2>CubeMX Configuration</h2>
<ol>
<li>Enable SPI1 in Full-Duplex Master mode</li>
<li>Set prescaler to divide clock to approximately 10MHz to start (you can increase later)</li>
<li>Data size: 8 bits</li>
<li>CPOL: Low, CPHA: 1 Edge (Mode 0)</li>
<li>Set PA4 as GPIO_Output, label it FLASH_CS</li>
<li>Generate code</li>
</ol>
<h2>Driver Code</h2>
<p>Create w25q128.h and w25q128.c in your project:</p>
<pre><code>/* w25q128.h */
#ifndef W25Q128_H
#define W25Q128_H
#include “stm32f4xx_hal.h”
#define W25Q128_CMD_WRITE_ENABLE 0x06
#define W25Q128_CMD_READ_STATUS 0x05
#define W25Q128_CMD_PAGE_PROGRAM 0x02
#define W25Q128_CMD_READ_DATA 0x03
#define W25Q128_CMD_SECTOR_ERASE 0x20
#define W25Q128_CMD_CHIP_ERASE 0xC7
#define W25Q128_CMD_JEDEC_ID 0x9F
#define FLASH_CS_LOW() HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET)
#define FLASH_CS_HIGH() HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET)
void W25Q128_Init(SPI_HandleTypeDef *hspi);
uint32_t W25Q128_ReadID(void);
void W25Q128_Read(uint32_t addr, uint8_t *buf, uint16_t len);
void W25Q128_WritePage(uint32_t addr, uint8_t *buf, uint16_t len);
void W25Q128_EraseSector(uint32_t addr);
void W25Q128_WaitBusy(void);
#endif
</code></pre>
<pre><code>/* w25q128.c */
#include “w25q128.h”
static SPI_HandleTypeDef *_hspi;
static uint8_t SPI_Transfer(uint8_t data) {
uint8_t rx;
HAL_SPI_TransmitReceive(_hspi, &data, &rx, 1, HAL_MAX_DELAY);
return rx;
}
void W25Q128_Init(SPI_HandleTypeDef *hspi) {
_hspi = hspi;
FLASH_CS_HIGH();
}
uint32_t W25Q128_ReadID(void) {
uint32_t id = 0;
FLASH_CS_LOW();
SPI_Transfer(W25Q128_CMD_JEDEC_ID);
id = (uint32_t)SPI_Transfer(0xFF) << 16;
id |= (uint32_t)SPI_Transfer(0xFF) << 8;
id |= SPI_Transfer(0xFF);
FLASH_CS_HIGH();
return id; /* Should return 0xEF4018 */
}
void W25Q128_WaitBusy(void) {
uint8_t status;
FLASH_CS_LOW();
SPI_Transfer(W25Q128_CMD_READ_STATUS);
do { status = SPI_Transfer(0xFF); } while (status & 0x01);
FLASH_CS_HIGH();
}
void W25Q128_Read(uint32_t addr, uint8_t *buf, uint16_t len) {
FLASH_CS_LOW();
SPI_Transfer(W25Q128_CMD_READ_DATA);
SPI_Transfer((addr >> 16) & 0xFF);
SPI_Transfer((addr >> 8) & 0xFF);
SPI_Transfer( addr & 0xFF);
for (uint16_t i = 0; i < len; i++) buf[i] = SPI_Transfer(0xFF);
FLASH_CS_HIGH();
}
void W25Q128_WritePage(uint32_t addr, uint8_t *buf, uint16_t len) {
/* Write enable */
FLASH_CS_LOW();
SPI_Transfer(W25Q128_CMD_WRITE_ENABLE);
FLASH_CS_HIGH();
/* Page program — max 256 bytes per call */
FLASH_CS_LOW();
SPI_Transfer(W25Q128_CMD_PAGE_PROGRAM);
SPI_Transfer((addr >> 16) & 0xFF);
SPI_Transfer((addr >> 8) & 0xFF);
SPI_Transfer( addr & 0xFF);
for (uint16_t i = 0; i < len; i++) SPI_Transfer(buf[i]);
FLASH_CS_HIGH();
W25Q128_WaitBusy();
}
void W25Q128_EraseSector(uint32_t addr) {
FLASH_CS_LOW();
SPI_Transfer(W25Q128_CMD_WRITE_ENABLE);
FLASH_CS_HIGH();
FLASH_CS_LOW();
SPI_Transfer(W25Q128_CMD_SECTOR_ERASE);
SPI_Transfer((addr >> 16) & 0xFF);
SPI_Transfer((addr >> 8) & 0xFF);
SPI_Transfer( addr & 0xFF);
FLASH_CS_HIGH();
W25Q128_WaitBusy(); /* Sector erase takes ~400ms */
}
</code></pre>
<h2>Usage Example — Write and Read Back</h2>
<pre><code>W25Q128_Init(&hspi1);
/* Verify chip is present */
uint32_t id = W25Q128_ReadID();
/* id should be 0xEF4018 — if not, check wiring */
/* Erase sector 0 before writing */
W25Q128_EraseSector(0x000000);
/* Write 16 bytes to address 0 */
uint8_t write_buf[16] = “NVX-F4 TEST DATA”;
W25Q128_WritePage(0x000000, write_buf, 16);
/* Read back and verify */
uint8_t read_buf[16] = {0};
W25Q128_Read(0x000000, read_buf, 16);
/* read_buf should now contain “NVX-F4 TEST DATA” */
</code></pre>
<h2>Important Notes</h2>
<ul>
<li><strong>Erase before write:</strong> NOR flash can only program bits from 1 to 0. To write new data to a previously written area, you must erase it first (sets all bits back to 1). Erase granularity is 4KB (one sector).</li>
<li><strong>Page boundary:</strong> WritePage() must not cross a 256-byte page boundary. If you need to write more than 256 bytes, split across multiple WritePage() calls aligned to page boundaries.</li>
<li><strong>Wear levelling:</strong> the W25Q128 supports 100,000 erase cycles per sector. For heavy write applications, implement basic wear levelling by rotating write addresses.</li>
</ul>
<h2>Get the NVX-F4 Flash Board</h2>
<p>The <a href=”/products”>NVX-F4 Flash</a> comes with the W25Q128JV pre-wired to SPI1 with CS on PA4 — exactly matching this driver. No extra wiring required. The full driver source, example project, and a data logging demo are available at github.com/nvixeon.</p>
