W25Q128

W25Q128 是一种常见的串行闪存器件,它采用 SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。W25Q128 芯片容量为 128 Mbit(16 MB),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中。

W25Q128 闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为 4KB,每个块包含 16 个扇区,即一个块的大小为 64KB。

存储结构图

ccjgt

引脚说明

yjsm

引脚编号 引脚名称 描述
1 /CS 芯片选择引脚(Chip Select)
2 DO (IO₁) 数据输出引脚(Data Out,I/O1)
3 /WP (IO₂) 写保护引脚(Write Protect,I/O2)
4 GND 地引脚(Ground)
5 DI (IO₀) 数据输入引脚(Data In,I/O0)
6 CLK 时钟信号引脚(Clock)
7 /HOLD or /RESET (IO₃) 保持或复位引脚(Hold/Reset,I/O3)
8 VCC 电源引脚(Power Supply)

/ 表示引脚是低电平有效

HOLD:当它有效时允许设备暂停,低电平:DO 引脚高阻态,DI CLK 引脚的信号被忽略。高电平:设备重新开始,当多个设备共享相同的 SPI 信 号的时候该功能可能会被用到。

示例电路

sjdl

SPI

根据 W25Q128 的数据手册,得到以下配置:

  • SPI 配置为了全双工模式,可以同时发送与接收数据;
  • STM32F407 配置为主机模式,由 STM32F407 产生时钟,与从机 W25Q128 进行通信;
  • 数据的传输以 8 位进行传输。
  • 片选方式选择软件控制片选。在硬件 SPI 中,一个 SPI 只有一个片选线,这会导致硬件 SPI 如果选择硬件控制片选信号,只能控制一个从机。我们是希望能够一个 SPI 可以控制多个从机,因此选择软件方式片选,片选线可以随意设定。
  • 时钟分频选择 4 分频,根据 W25Q128 的数据手册说明,W25Q128 的 SPI 时钟可以达到 30MHz,而我们的 SPI0 的时钟来源为 PCLK2=84MHz,SPI 的配置中必须要求进行分频,分频之后的频率是 21MH,低于 30Mhz,W25Q1282 完全可以兼容。
  • 字节顺序选择高位在前。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SPI_InitTypeDef  SPI_InitStructure;

/* FLASH_SPI 模式配置 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 传输模式全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 配置为主机
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8位数据
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 极性相位
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件cs
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // SPI时钟预调因数为2
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(BSP_SPI, &SPI_InitStructure);

/* 使能 FLASH_SPI */
SPI_Cmd(BSP_SPI, ENABLE);

示例代码

https://github.com/TooUpper/SensorDrive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

#include "board.h"
#include "bsp_uart.h"
#include <stdio.h>
#include "spi_flash.h"
#include <string.h>

int main(void){
board_init();
uart1_init(115200U);

/* SPI初始化 */
bsp_spi_init();

// 缓存区数组定义
unsigned char buff[20] = {0};

printf("\r\n=========【开始】========\r\n");

// 擦除Flash扇区0
printf("\r\n【1】擦除Flash扇区0......\r\n");
W25Q128_erase_sector(0);
printf("Flash扇区0擦除完成!!\r\n");
delay_ms(200);

//获取W25Q128的设备ID
printf("\r\n【2】读取设备的id......\r\n");
printf("设备ID = %X\r\n",W25Q128_readID());

//读取0地址长度为10个字节的数据到buff
printf("\r\n【3】读取0地址长度为10个字节的数据到buff......\r\n");
W25Q128_read(buff, 0, 10);
//输出读取到的数据
printf("读取到的数据= %s\r\n",buff);
delay_ms(200);

//往0地址写入10个字节的数据 “立创开发板”
printf("\r\n【4】往0地址写入8个字节的数据 “TooUpper”......\r\n");
W25Q128_write((uint8_t *)"TooUpper", 0, 8);
// 等待写入完成
delay_ms(200);
printf("数据写入成功!\r\n");

//读取0地址长度为10个字节的数据到buff
printf("\r\n【5】读取0地址长度为8个字节的数据到buff......\r\n");
W25Q128_read(buff, 0, 8);
//输出读取到的数据
printf("读取到的数据= %s\r\n",buff);
delay_ms(1000);

// 因为是从0地址写的,所以擦除第0个扇区
printf("\r\n【6】功能测试完毕擦除写入的数据......\r\n");
W25Q128_erase_sector(0);
delay_ms(200);

// 清除缓存区
memset(buff,0,sizeof(buff));

printf("\r\n=========【结束】========\r\n");

while(1) {
delay_ms(100);
}
}