摘要:本文主要介绍在Arduino IDE开发环境下,如何快速上手使用外部Flash的相关功能(读取外部Flash的相关信息 / 将Flash中的程序,加载到RAM中执行)
一、Flash
Flash存储器,也称为Flash Memory,是一种非易失性存储设备,它结合了ROM和RAM的特点,不仅具有电子可擦除可编程(EEPROM)的性能,还能在断电后保持数据不丢失,类似于NVRAM的优势。
Flash存储器的类型
Flash存储器主要分为NOR Flash和NAND Flash两大类;
NOR Flash特点是具有较快的读速度和片上执行功能,但写入和擦除速度较慢,容量低且价格高,因此多用于代码存储;
NAND Flash以其快速的写入和擦除操作,大容量和低成本的优势,主要用于数据存储,如数码相机、MP3播放器和笔记本电脑;

常用NOR Flash型号
大家可能经常使用都是Winbond(华邦)的W25Qxx系列, 除此之外还有国产的兆易创新(GigaDevice)的GD25Qxx系列等;
同封装型号的GD25Qxx与W25Qxx兼容;
而且目前兆易的同款价格普遍低于华邦,非常适合进行平替;
Flash 特性
串行 NOR(SPI/DSPI/QSPI)

二、开发环境搭建
1、下载Arduino IDE

2、安装相关功能库
1、安装STM32芯片支持库


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

三、硬件设备(功能)
主要使用天空星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();
}
}五、效果演示




我要赚赏金
