一、关于FlashFlash存储器,也称为Flash Memory,是一种非易失性存储设备,它结合了ROM和RAM的特点,不仅具有电子可擦除可编程(EEPROM)的性能,还能在断电后保持数据不丢失,类似于NVRAM的优势。 Flash存储器的类型 Flash存储器主要分为NOR Flash和NAND Flash两大类。NOR Flash特点是具有较快的读速度和片上执行功能,但写入和擦除速度较慢,容量低且价格高,因此多用于代码存储。NAND Flash以其快速的写入和擦除操作,大容量和低成本的优势,主要用于数据存储,如数码相机、MP3播放器和笔记本电脑。
常用
|
| 模式 | 单周期传输bit数 | 理论最高吞吐率 | 典型适用场景 |
|---|---|---|---|
| 标准 SPI | 1bit | 133 Mbps | 小容量参数读写、简单固件升级 |
| Dual SPI(双线) | 2bit | 266 Mbps | 中等数据量读取、字库调用 |
| Quad SPI(四线) | 4bit | 532 Mbps | 大固件 XIP 执行、UI资源加载、高速数据存储 |
| 引脚 | 标准 SPI 模式功能 |
|---|---|
| CS# | 片选信号 |
| CLK | 时钟信号 |
| SI/MOSI | 主机输出、从机输入(单向发数据) |
| SO/MISO | 主机输入、从机输出(单向收数据) |
| WP# | 写保护引脚 |
| HOLD# | 总线保持引脚 |

1、安装STM32芯片支持库

2、安装WS25QxxFlash库
该库提供Flash读取、写入等相关功能;

该驱动库默认使用的是标准SPI模式(高速读指令);
没有启用双线SPI(Dual SPI)或四线SPI(Quad SPI)模式;
可在该库基础上,修改相关指令以启用其他模式;
详见
W25QxxFlash.hbool W25QxxFlash::readData(uint address, byte* data, uint length)
bool W25QxxFlash::writeData(uint address, const byte* data, uint length)
主要使用天空星STM32F407VET6开发板,以及焊接的外部Flash(GD25Q128),通过WS25QxxFlash库,读取Flash的相关信息,以及当按下板载按键KEY时,将Flash中的点亮LED程序,加载到RAM中执行;
STM32F407VET6


SPI接口

GD25Q128ESIGR
GD25Q128ESIGR是兆易创推出的128Mbit(16MB)串行 NOR Flash
存储结构:256 字节 / 页,4KB / 扇区,支持 32KB、64KB 块擦除及全片擦除;
全SPI扩展模式支持:标准SPI(单线)、Dual SPI(双线)、Quad SPI(四线)模式;



Flash信息读取部分
// Flash 对应连接的引脚
#define SPI_SCK PA5
#define SPI_MISO PA6
#define SPI_MOSI PA7
#define FLASH_CS PA4
// 初始化SPI
SPI.setMISO(SPI_MISO);
SPI.setMOSI(SPI_MOSI);
SPI.setSCLK(SPI_SCK);
SPI.begin();
// 初始化Flash芯片 128Mbit (16MB)
flash.begin(&SPI, FLASH_CS, 128);
// 打印Flash信息
uint manufacturer, memType, capId;
if (flash.readChipInfo(&manufacturer, &memType, &capId)) {
uint32_t totalBytes = 1UL << capId;
Serial1.println("======= Flash 芯片信息 =======");
Serial1.print("厂商ID: 0x");
Serial1.println(manufacturer, HEX);
Serial1.print("内存类型: 0x");
Serial1.println(memType, HEX);
Serial1.print("容量编码: 0x");
Serial1.println(capId, HEX);
Serial1.print("总容量: ");
Serial1.print(totalBytes);
Serial1.print(" 字节 = ");
Serial1.print(totalBytes / 1024);
Serial1.print(" KB = ");
Serial1.print(totalBytes / (1024UL * 1024UL));
Serial1.print(" MB = ");
Serial1.print((totalBytes * 8UL) / (1024UL * 1024UL));
Serial1.println(" Mbit");
} else {
Serial1.println("读取Flash信息失败");
}
uint64_t uniqueId;
if (flash.readUniqueId(&uniqueId)) {
Serial1.print("64位唯一ID: 0x");
Serial1.print((uint32_t)(uniqueId >> 32), HEX);
Serial1.println((uint32_t)uniqueId, HEX);
} else {
Serial1.println("读取芯片唯一ID失败");
}
Serial1.println("");
示例代码:
#include <SPI.h>
#include "MumanchuDebug.h"
#include "W25QxxFlash.h"
#ifdef DEBUG
void LogError(const char* msg, const char* filePath, uint line) {
char buf[256];
const char* fname = strrchr(filePath, '\\');
fname = fname ? fname + 1 : filePath;
sprintf(buf, "ERROR: %s : %s(%u)", msg, fname, line);
Serial1.println(buf);
Serial1.flush();
}
#endif
// SPI引脚定义 (SPI1)
#define SPI_SCK PA5
#define SPI_MISO PA6
#define SPI_MOSI PA7
#define FLASH_CS PA4
// 硬件引脚(LED/KEY)
#define LED_BUILTIN PB2
#define KEY_BUTTON PA0
// 外部Flash中代码的存储地址
#define FIRMWARE_FLASH_ADDR 0x00000000
// RAM执行缓冲区,强制4字节对齐
uint8_t ram_code_buf[64] __attribute__((aligned(4)));
// 按键消抖配置
#define KEY_DEBOUNCE_MS 20
unsigned long last_key_tick = 0;
bool key_pressed_flag = false;
/*
功能:将 PB2 置高电平点亮LED,执行完毕后返回主程序
对应汇编:
ldr r0, [pc, #4] ; 加载 GPIOB_BSRR 寄存器地址
movs r1, #4 ; 1<<2,对应 PB2 置位
str r1, [r0] ; 写入寄存器,点亮LED
bx lr ; 返回调用者
DCD 0x40020418 ; GPIOB_BSRR 硬件地址
*/
const uint8_t led_on_binary[] = {
0x01, 0x48, // ldr r0, [pc, #4]
0x04, 0x21, // movs r1, #4
0x01, 0x60, // str r1, [r0]
0x70, 0x47, // bx lr
// GPIOB_BSRR 地址 0x40020418,小端序存储
0x18, 0x04, 0x02, 0x40
};
HardwareSerial Serial1(PA10, PA9);
W25QxxFlash flash;
void setup() {
Serial1.begin(115200);
delay(1000);
Serial1.flush();
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); // 初始熄灭
pinMode(KEY_BUTTON, INPUT_PULLDOWN);
// 初始化SPI总线
SPI.setMISO(SPI_MISO);
SPI.setMOSI(SPI_MOSI);
SPI.setSCLK(SPI_SCK);
SPI.begin();
// 初始化Flash芯片 128Mbit (16MB)
flash.begin(&SPI, FLASH_CS, 128);
// 打印 Flash 信息
uint manufacturer, memType, capId;
if (flash.readChipInfo(&manufacturer, &memType, &capId)) {
uint32_t totalBytes = 1UL << capId;
Serial1.println("======= Flash 芯片信息 =======");
Serial1.print("厂商ID: 0x");
Serial1.println(manufacturer, HEX);
Serial1.print("内存类型: 0x");
Serial1.println(memType, HEX);
Serial1.print("容量编码: 0x");
Serial1.println(capId, HEX);
Serial1.print("总容量: ");
Serial1.print(totalBytes);
Serial1.print(" 字节 = ");
Serial1.print(totalBytes / 1024);
Serial1.print(" KB = ");
Serial1.print(totalBytes / (1024UL * 1024UL));
Serial1.print(" MB = ");
Serial1.print((totalBytes * 8UL) / (1024UL * 1024UL));
Serial1.println(" Mbit");
} else {
Serial1.println("读取Flash信息失败");
}
uint64_t uniqueId;
if (flash.readUniqueId(&uniqueId)) {
Serial1.print("64位唯一ID: 0x");
Serial1.print((uint32_t)(uniqueId >> 32), HEX);
Serial1.println((uint32_t)uniqueId, HEX);
} else {
Serial1.println("读取芯片唯一ID失败");
}
Serial1.println("");
// 写入代码到外部Flash
uint8_t check_byte;
flash.readData(FIRMWARE_FLASH_ADDR, &check_byte, 1);
if (check_byte != led_on_binary[0]) {
Serial1.println("首次运行,正在写入LED代码到外部Flash...");
flash.eraseSector(0); // 擦除第0个4KB扇区
flash.waitWhileBusy(1000);
flash.writeData(FIRMWARE_FLASH_ADDR, led_on_binary, sizeof(led_on_binary));
flash.waitWhileBusy(1000);
Serial1.println("代码写入到外部Flash完成");
} else {
Serial1.println("外部Flash中已写入代码");
}
Serial1.println("按下Key 触发Flash代码加载执行");
Serial1.println("");
Serial1.flush();
}
void loop() {
// 按键消抖检测
if (digitalRead(KEY_BUTTON) == HIGH) {
if (millis() - last_key_tick > KEY_DEBOUNCE_MS) {
if (!key_pressed_flag) {
key_pressed_flag = true;
Serial1.println("按键触发:开始从Flash加载代码到SRAM...");
// 把Flash中的二进制机器码读到RAM缓冲区
flash.readData(FIRMWARE_FLASH_ADDR, ram_code_buf, sizeof(led_on_binary));
// 转换为函数指针,地址最低位置1 (Cortex-M Thumb状态位要求)
void (*exec_led_func)(void) = (void (*)(void))((uint32_t)ram_code_buf | 0x01);
// 跳转执行RAM中的代码,执行完毕自动返回
exec_led_func();
Serial1.println("LED已点亮");
}
}
} else {
key_pressed_flag = false;
last_key_tick = millis();
}
}


【经验分享】STM32 HAL库移植FreeModbus详细步骤
STM32F4中文用户手册
STM32F400、STM32F402 Cortex-M4超值单片机
SPI 高温读错最后一位?STM32F42xx 官方根治方案
STM32CubeIDE for Visual Studio Code 开发流水帐
实战经验 | Keil工程使用NEAI库的异常问题
STM32的RTC电流消耗异常问题分析
STM32与51单片机差异一文速览
STM32大神笔记,超详细单片机学习汇总资料
经验分享 | FDCAN数据段波特率增加后发送失败的问题分析
微信公众号
手机版