W25Q128是一款大容量SPI FLASH 产品,容量为128Mb。擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,支持高达104MHz的串行时钟通讯。

我买的是W25Q128模块,HOLD引脚和WP引脚都接入了3.3V。

原理图:

ESP8266模块的引脚图:

使用D5\D6\D7\D8四个引脚连接模块

为了验证去读取的操作是否正常,预先使用其它编程器改写了部分单元的内容(从地址0xFFFFF0开始的额6个单元的数据:00、01、02、03、04、05、06、07、08、09、10、11、12、13、14、15)。
W25Q128的资料在其它帖子中简单做了说明(W25Q128的简单说明资料)。
程序代码如下:
/**
本例使用软件模拟方式读写操作W25Q128
*/
#include <SPI.h>
#define NORFLASH_CS_PIN 15 // D8
#define NORFLASH_CLK_PIN 14 // D5
#define NORFLASH_MOSI_PIN 13 // D7
#define NORFLASH_MISO_PIN 12 // D6
#define NORFLASH_HOLD_PIN -1 // hold引脚接3.3V
#define NORFLASH_WP_PIN -1 // wp引脚接3.3V
// 操作W25Q128的指令
#define ManufactDeviceID_CMD 0x90 // 制造商设备ID
#define READ_JEDEC_ID_CMD 0x9F // JEDEC ID:标识制造商和设备类型的重要代码
#define WRITE_STATUS 0x01 // 写状态寄存器
#define READ_STATU_REGISTER_1 0x05 // 读状态寄存器1
#define READ_STATU_REGISTER_2 0x35 // 读状态寄存器2
#define READ_DATA_CMD 0x03 // 读数据
#define WRITE_ENABLE_CMD 0x06 // 写使能
#define WRITE_DISABLE_CMD 0x04 // 写失能
#define SECTOR_ERASE_CMD 0x20 // 扇区擦除
#define CHIP_ERASE_CMD 0xC7 // 全片擦除
#define PAGE_PROGRAM_CMD 0x02 // 页编程
#define ONE_PAGE_SIZE 256 // 页数据字节数量
#define SPI_FREQUENCY 40 * 1000000 // SPI通讯速度,SCK频率
/* 初始化SPI */
void norflash_spi_init() {
// gpio init
pinMode(NORFLASH_HOLD_PIN, OUTPUT);
pinMode(NORFLASH_WP_PIN, OUTPUT);
digitalWrite(NORFLASH_HOLD_PIN, HIGH);
digitalWrite(NORFLASH_WP_PIN, HIGH);
pinMode(NORFLASH_CS_PIN, OUTPUT);
digitalWrite(NORFLASH_CS_PIN, HIGH);
// 软件模拟方式
pinMode(NORFLASH_CLK_PIN, OUTPUT);
pinMode(NORFLASH_MOSI_PIN, OUTPUT);
pinMode(NORFLASH_MISO_PIN, INPUT);
digitalWrite(NORFLASH_CLK_PIN, LOW);
delay(1);
uint8_t data = 0;
write_enable(); // 开放写
data = read_status(); // 读状态,确认是否可写
Serial.printf("norflash write enable status:");
Serial.println(data, BIN);
// 读取设备ID
uint16_t device_id = 0;
device_id = read_norflash_id();
Serial.printf("norflash device id: 0x%04X", device_id);
}
/* 写字节数据 */
void write_byte(uint8_t data) {
for(uint8_t i = 0; i < 8; i++) {
uint8_t status;
status = data & (0x80 >> i);
digitalWrite(NORFLASH_MOSI_PIN, status);
digitalWrite(NORFLASH_CLK_PIN, LOW);
digitalWrite(NORFLASH_CLK_PIN, HIGH);
}
}
/* 读一字节 */
uint8_t read_byte(uint8_t tx_data) {
// 软件模拟方式
uint8_t i = 0, data = 0;
for(i = 0; i < 8; i++) {
digitalWrite(NORFLASH_CLK_PIN, HIGH);
digitalWrite(NORFLASH_CLK_PIN, LOW);
if(digitalRead(NORFLASH_MISO_PIN)) {
data |= 0x80 >> i;
}
}
return data;
}
/* 允许写 */
void write_enable() {
digitalWrite(NORFLASH_CS_PIN, LOW);
write_byte(WRITE_ENABLE_CMD);
digitalWrite(NORFLASH_CS_PIN, HIGH);
}
/* 禁止写*/
void write_disable() {
digitalWrite(NORFLASH_CS_PIN, LOW);
write_byte(WRITE_DISABLE_CMD);
digitalWrite(NORFLASH_CS_PIN, HIGH);
}
/* 读状态 */
uint8_t read_status() {
uint8_t status = 0;
digitalWrite(NORFLASH_CS_PIN, LOW);
write_byte(READ_STATU_REGISTER_1);
status = read_byte(0);
digitalWrite(NORFLASH_CS_PIN, HIGH);
return status;
}
/* 检查是否忙 */
void check_busy(char *str) {
while(read_status() & 0x01) {
#ifdef NORFLASH_DEBUG_ENABLE
Serial.printf("status = 0, flash is busy of %s\n", str);
#endif
}
}
/* 写入少于一个块(256字节)的数据 */
void write_one_block_data(uint32_t addr, uint8_t * pbuf, uint16_t len) {
uint16_t i;
check_busy("write_one_block_data");
write_enable();
digitalWrite(NORFLASH_CS_PIN, LOW);
write_byte(PAGE_PROGRAM_CMD);
write_byte((uint8_t)(addr >> 16));
write_byte((uint8_t)(addr >> 8));
write_byte((uint8_t)addr);
for(i = 0; i < len; i++) {
write_byte(*pbuf++);
}
digitalWrite(NORFLASH_CS_PIN, HIGH);
check_busy("write_one_block_data");
}
/* 写入少于一个扇区(4096字节)长度的数据 */
void write_one_sector_data(uint32_t addr, uint8_t * pbuf, uint16_t len) {
uint16_t free_space, head, page, remain;
free_space = ONE_PAGE_SIZE - addr % ONE_PAGE_SIZE;
if(len <= free_space) {
head = len;
page = 0;
remain = 0;
}
if(len > free_space) {
head = free_space;
page = (len - free_space) / ONE_PAGE_SIZE;
remain = (len - free_space) % ONE_PAGE_SIZE;
}
if(head != 0) {
Serial.print("head:");
Serial.println(head);
write_one_block_data(addr, pbuf, head);
pbuf = pbuf + head;
addr = addr + head;
}
if(page != 0) {
Serial.print("page:");
Serial.println(page);
for(uint16_t i = 0; i < page; i++) {
write_one_block_data(addr, pbuf, ONE_PAGE_SIZE);
pbuf = pbuf + ONE_PAGE_SIZE;
addr = addr + ONE_PAGE_SIZE;
}
}
if(remain != 0) {
Serial.print("remain:");
Serial.println(remain);
write_one_block_data(addr, pbuf, remain);
}
}
/* Write arbitrary length data */
void write_arbitrary_data(uint32_t addr, uint8_t* pbuf, uint32_t len) {
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *write_buf = pbuf;
uint8_t save_buffer[4096]; // 保存扇区原始数据并添加新数据
secpos = addr / 4096; // 扇区编号
secoff = addr % 4096; // 扇区地址偏移量
secremain = 4096 - secoff; // 扇区剩余空间
if(len <= secremain) {
secremain = len; // 扇区剩余空间小于4096
}
while(1) {
read_data(secpos * 4096, save_buffer, 4096); // read sector data
for(i = 0; i < secremain; i++) {
// 检查数据,如果所有数据都是0xFF,则不需要擦除扇区
if(save_buffer[secoff + i] != 0XFF) {
// 不需要擦除扇区
break;
}
}
if(i < secremain) {
// 擦除扇区并写入数据
sector_erase(secpos);
for(i = 0; i < secremain; i++) {
save_buffer[i + secoff] = write_buf[i]; // add new data
}
write_one_sector_data(secpos * 4096, save_buffer, 4096); // write sector
} else {
// 不需要擦除扇区
write_one_sector_data(addr, write_buf, secremain);
}
if(len == secremain) {
// 完成写入
break;
} else {
// 继续写入
secpos ++; // 扇区编号 + 1
secoff = 0; // 扇区地址偏移量 = 0
write_buf += secremain; // 写缓冲偏移
addr += secremain; // 地址
len -= secremain; // 数据长度
if(len > 4096) {
// 剩余数据超过一个扇区(4096字节)
secremain = 4096;
} else {
// 剩余数据少于一个扇区(4096字节)
secremain = len;
}
}
}
}
/* 读取任意长度的数据 */
void read_data(uint32_t addr24, uint8_t *pbuf, uint32_t len) {
check_busy("read_data");
digitalWrite(NORFLASH_CS_PIN, LOW);
write_byte(READ_DATA_CMD);
write_byte((uint8_t)(addr24 >> 16));
write_byte((uint8_t)(addr24 >> 8));
write_byte((uint8_t)addr24);
for(uint32_t i = 0; i < len; i++) {
*pbuf = read_byte(0xFF);
pbuf ++;
}
digitalWrite(NORFLASH_CS_PIN, HIGH);
}
/* 擦除扇区 */
void sector_erase(uint32_t addr24) {
addr24 *= 4096;
check_busy("sector_erase");
write_enable(); // WP=0,允许写操作
digitalWrite(NORFLASH_CS_PIN, LOW); // CS = 0,允许操作
write_byte(SECTOR_ERASE_CMD); // 发送清除扇区指令:
write_byte((uint8_t)(addr24 >> 16)); // 发送地址
write_byte((uint8_t)(addr24 >> 8));
write_byte((uint8_t)addr24);
digitalWrite(NORFLASH_CS_PIN, HIGH); // CS = 1,禁止操作
check_busy("sector_erase");
}
/* Read norflash id */
uint16_t read_norflash_id() {
uint8_t data = 0;
uint16_t device_id = 0;
digitalWrite(NORFLASH_CS_PIN, LOW);
write_byte(ManufactDeviceID_CMD); // 发送读取ID的指令
write_byte(0x00);
write_byte(0x00);
write_byte(0x00);
data = read_byte(0);
device_id |= data; // low byte
data = read_byte(0);
device_id |= (data << 8); // high byte
digitalWrite(NORFLASH_CS_PIN, HIGH);
return device_id;
}
void setup() {
uint16_t id=0;
Serial.begin(115200);
norflash_spi_init();
int g = 0;
uint8_t str[1280];
memset(str, 0, sizeof(str));
unsigned int j = 0;
for(int k=0; k < 5; k++) {
for(int i = 0; i < 256; i++) {
str[j] = i;
j++;
}
}
Serial.println("");
Serial.println("-----write data-------");
sector_erase(0x00);
write_one_sector_data(0x00, str, 512);
memset(str, 0, sizeof(str));
id = read_norflash_id();
Serial.printf("IC ID = %04X \n", id);
read_data(0x00, str, 512);
Serial.println("data:");
for(int k = 0; k < 512; k++) {
if(g == 16) {
Serial.println("|");
if(k % 256 == 0) {
Serial.println("---------------");
}
g = 1;
} else {
g++;
}
Serial.printf("%02X ", str[k]);
}
//为了验证读操作,预先用其它编程器改写了 0x00FFFFF0 所在单元的数据,计16字节
read_data(0x00fffff0, str, 16); // 读取地址=0x00FFFFF0所在单元的16个字节
Serial.println("\n 0xFFFFF0:");
g=0;
for(int k = 0; k < 16; k++) {
if(g == 16) {
Serial.println("|");
if(k % 256 == 0) {
Serial.println("---------------");
}
g = 1;
} else {
g++;
}
Serial.printf("%02X ", str[k]);
}
}
void loop() {
}测试时串口输出的信息:

我要赚赏金
