Ajout de la mémoire Flash QSPI à la STM32F4 : pilote W25Q128 et code d'exemple

Un guide complet pour interfaçage de la mémoire Flash NOR QSPI 16 Mo W25Q128JV avec la STM32F4. Couvre le câblage, la configuration CubeMX, le pilote HAL SPI, les opérations de lecture/écriture/effacement, et un exemple de journalisation de données — le tout testé sur la carte Flash NVX-F4.

<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>