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() { }
测试时串口输出的信息: