1. W25Q128簡介
W25Q128 是Winbond推出的128M-bit(16MB)SPI接口Flash存儲器,支持標準SPI、Dual-SPI和Quad-SPI模式。關鍵特性:
- 工作電壓:2.7V~3.6V
- 分頁結構:256頁/塊,每塊16KB,共1024塊
- 支持頁編程(256字節/頁)
- 擦除操作支持:扇區擦除(4KB)、塊擦除(32/64KB)、全片擦除
- 最高104MHz時鐘頻率
- 超過10萬次擦寫周期
1.1 W25Q128存儲架構
W25Q128存儲器采用分級存儲架構,總容量為16MB(128Mbit)。其物理存儲結構按以下層級劃分:
- 頂層劃分:256個存儲塊(Block),每塊容量64KB
- 塊內結構:每個存儲塊包含16個扇區(Sector),每扇區容量4KB
- 扇區組成:每個扇區細分為16個存儲頁(Page),每頁容量256字節
該存儲器的擦除機制具有以下特性:
- 最小擦除單位為單個扇區(4KB)
- 執行擦除操作時必須完整處理整個扇區
- 系統需預置4KB對齊的緩沖區,以滿足物理擦除粒度要求
1.2 W25Q128常用指令
W25Q128 有非常多的指令,我們介紹幾個常用的指令。
指令 | (HEX) | 名稱 作用 |
---|---|---|
0x06 | 寫使能 | 寫入數據/擦除之前,必須先發送該指令 |
0x05 | 讀 SR1 | 判定 FLASH 是否處于空閑狀態,擦除用 |
0x03 | 讀數據 | 讀取數據 |
0x02 | 頁寫 | 寫入數據,最多寫256字節 |
0x20 | 扇區擦除 | 扇區擦除指令,最小擦除單位 |
1.3 W25Q128 操作時序詳解
- 寫使能(06h)- 指令功能?:執行編程/擦除操作的必要使能信號
- 操作流程:
① CS引腳置低(通信起始)
② 發送1字節指令碼06h(MSB先發)
③ CS引腳置高(通信終止) - 狀態影響?:WEL位(SR1)置1,有效時間約50ms
- 操作流程:
- 狀態寄存器讀取(05h)
- 指令格式?:
┌──────┬─────────────┐
| 指令碼 | 響應數據(持續可讀) |
└──────┴─────────────┘ - 操作時序**?:
① CS置低 → 發送05h → 連續讀取SR1 → CS置高? - 技術要點?:
BUSY位(SR1)監控:0=空閑,1=操作中
支持全雙工SPI模式(MOSI/MISO同步傳輸)
- 指令格式?:
- 數據讀取(03h)
- 指令結構?:
┌──────┬──────────┬─────────┐
| 03h | 24bit地址 | 數據流(≥1字節) |
└──────┴──────────┴─────────┘ - 執行步驟?:
- 起始地址對齊(按頁邊界自動遞增)
- 單次讀取最大長度:256字節(單頁容量)
- 跨頁讀取需重新發送地址指令
- 指令結構?:
- 頁編程(02h)
- 技術限制?:
- 目標區域必須處于擦除狀態(全FFh)
- 單次寫入跨度≤256字節(禁止跨頁寫入)
- 操作序列?:
[CS↓] → 02h → A23-A0 → Data_1~Data_n → [CS↑] - 物理特性?:
實際編程時間約0.7-1.2ms(需BUSY狀態輪詢)
- 技術限制?:
- 扇區擦除(20h)
- 約束條件?:
- 擦除粒度:4KB(地址自動對齊到扇區起始)
- 擦除后狀態:全FFh(電平=1)
- 時序流程?:
[CS↓] → 20h → 目標地址(A23-A0) → [CS↑] - 耗時范圍?:
典型值400ms(溫度25℃),最大600ms
- 約束條件?:
2. 硬件連接
這篇文章,使用的是W25Q128模塊,W25Q128 的模塊各個廠家做的各有不同,只是長得不一樣而已,使用方式、引腳都是一樣的。
通過產品手冊如下:
STM32F103C8T6 與 W25Q128 接線方式:
STM32引腳 | W25Q128引腳 | 功能 |
---|---|---|
PA5 | CLK | SPI時鐘 |
PA6 | MISO | 主機輸入 |
PA7 | MOSI | 主機輸出 |
PA4 | CS | 片選信號 |
3.3V | VCC | 電源 |
GND | GND | 地 |
注意:CS引腳需通過GPIO控制,不可固定接低電平
3. 代碼實現
3.1 SPI及GPIO初始化
在進行初始化時, 我們需要查看參考手冊:
按照手冊和實際情況進行GPIO口的配置
SPI_HandleTypeDef spi_handle = {0};
void w25q128_spi_init(void)
{spi_handle.Instance = SPI1;spi_handle.Init.Mode = SPI_MODE_MASTER;spi_handle.Init.Direction = SPI_DIRECTION_2LINES;spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW; /* CPOL = 0 */spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE; /* CPHA = 0 */spi_handle.Init.NSS = SPI_NSS_SOFT;spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;spi_handle.Init.CRCPolynomial = 7;HAL_SPI_Init(&spi_handle);
}void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{if(hspi->Instance == SPI1){GPIO_InitTypeDef gpio_initstruct;//打開時鐘__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOB時鐘__HAL_RCC_SPI1_CLK_ENABLE();//調用GPIO初始化函數gpio_initstruct.Pin = GPIO_PIN_4; gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; gpio_initstruct.Pull = GPIO_PULLUP; gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &gpio_initstruct);gpio_initstruct.Pin = GPIO_PIN_5 | GPIO_PIN_7; gpio_initstruct.Mode = GPIO_MODE_AF_PP; HAL_GPIO_Init(GPIOA, &gpio_initstruct);gpio_initstruct.Pin = GPIO_PIN_6; gpio_initstruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(GPIOA, &gpio_initstruct);}
}
3.2 W25Q128驅動函數
w251128.c
// 交換一個字節
uint8_t w25q128_spi_swap_byte(uint8_t data)
{uint8_t recv_data = 0;HAL_SPI_TransmitReceive(&spi_handle, &data, &recv_data, 1, 1000);return recv_data;
}// 初始化:
void w25q128_init(void)
{w25q128_spi_init();
}// 讀取iD值
uint16_t w25q128_read_id(void)
{uint16_t device_id = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ManufactDeviceID);w25q128_spi_swap_byte(0x00);w25q128_spi_swap_byte(0x00);w25q128_spi_swap_byte(0x00);device_id = w25q128_spi_swap_byte(FLASH_DummyByte) << 8;device_id |= w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);return device_id;
}// 寫使能void w25q128_writ_enable(void)
{W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_WriteEnable);W25Q128_CS(1);
}// 讀取狀態寄存器1
uint8_t w25q128_read_sr1(void)
{uint8_t recv_data = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ReadStatusReg1);recv_data = w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);return recv_data;
}// 等待BUSY
void w25q128_wait_busy(void)
{while((w25q128_read_sr1() & 0x01) == 0x01);
}
// 發送地址
void w25q128_send_address(uint32_t address)
{w25q128_spi_swap_byte(address >> 16);w25q128_spi_swap_byte(address >> 8);w25q128_spi_swap_byte(address);
}
// 讀取數據
void w25q128_read_data(uint32_t address, uint8_t *data, uint32_t size)
{uint32_t i = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ReadData);w25q128_send_address(address);for(i = 0; i < size; i++)data[i] = w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);
}// 頁寫
void w25q128_write_page(uint32_t address, uint8_t *data, uint16_t size)
{uint16_t i = 0;w25q128_writ_enable();W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_PageProgram);w25q128_send_address(address);for(i = 0; i < size; i++)w25q128_spi_swap_byte(data[i]);W25Q128_CS(1);//等待空閑w25q128_wait_busy();
}// 擦除
void w25q128_erase_sector(uint32_t address)
{//寫使能w25q128_writ_enable();//等待空閑w25q128_wait_busy();//拉低片選W25Q128_CS(0);//發送扇區擦除指令w25q128_spi_swap_byte(FLASH_SectorErase);//發送地址w25q128_send_address(address);//拉高片選W25Q128_CS(1);//等待空閑w25q128_wait_busy();
}
w25q128.h
#ifndef __W25Q128_H__
#define __W25Q128_H__#include "sys.h"#define W25Q128_CS(x) do{ x ? \HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET): \HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); \}while(0)/* 指令表 */
#define FLASH_ManufactDeviceID 0x90
#define FLASH_WriteEnable 0x06
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadData 0x03
#define FLASH_PageProgram 0x02
#define FLASH_SectorErase 0x20
#define FLASH_DummyByte 0xFFvoid w25q128_init(void);
uint16_t w25q128_read_id(void);
void w25q128_read_data(uint32_t address, uint8_t *data, uint32_t size);
void w25q128_write_page(uint32_t address, uint8_t *data, uint16_t size);
void w25q128_erase_sector(uint32_t address);#endif
3.3 使用示例
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "w25q128.h"uint8_t data_write[4] = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t data_read[4] = {0};
int main(void)
{HAL_Init(); /* 初始化HAL庫 */stm32_clock_init(RCC_PLL_MUL9); /* 設置時鐘, 72Mhz */led_init(); /* 初始化LED燈 */uart1_init(115200);w25q128_init();printf("hello world!\r\n");uint16_t device_id = w25q128_read_id();printf("device id: %X\r\n", device_id);// w25q128_erase_sector(0x000000);
// w25q128_write_page(0x000000, data_write, 4);w25q128_read_data(0x000000, data_read, 4);printf("data read: %X, %X, %X, %X\r\n", data_read[0], data_read[1], data_read[2], data_read[3]);while(1){ }
}
4. 注意事項
- 擦除操作必需:Flash寫入前必須擦除對應區域
- 頁邊界限制:單次寫入不可跨頁(每頁256字節)
- 時鐘配置:確保SPI時鐘不超過芯片規格(建議初始使用較低頻率)
- 狀態檢測:重要操作后需檢查BUSY位
- 電源穩定:Flash操作期間需保持3.3V穩定
5. 常見問題排查
- 無法讀取ID:檢查SPI模式(CPOL=0/CPHA=0),確認CS信號時序
- 寫入失敗:確保已執行寫使能命令,檢查電源電壓
- 數據錯誤:注意地址對齊,排除SPI時鐘干擾
完整工程代碼可在Gitee獲取:https://gitee.com/bad-lemon/mcu-development-record.git
實現效果:通過上述代碼可實現W25Q128的讀寫操作,實測寫入速度可達600KB/s,讀取速度1.2MB/s(SPI時鐘18MHz)。該方案適用于需要大容量非易失存儲的物聯網設備、數據采集系統等場景。