文章目录
- 前言
- W25Q128存储器介绍
- [1. 实验目的](#1. 实验目的)
- 2.硬件清单
- [3. 硬件接线](#3. 硬件接线)
- [4. 构思环节](#4. 构思环节)
-
- [4.1 新建w25q128文件夹](#4.1 新建w25q128文件夹)
- [4.2 搭建框架](#4.2 搭建框架)
-
- [1. SPI通信部分代码](#1. SPI通信部分代码)
- [2. W25Q128操作流程](#2. W25Q128操作流程)
- [3. W25Q128封装命令接口](#3. W25Q128封装命令接口)
- [5. 代码展示](#5. 代码展示)
- [6. 实验结果](#6. 实验结果)
前言
使用SPI总线,通过W25Q128存储器,实现数据通信。
W25Q128存储器介绍
W25Q128存储器是什么?
W25Q128是华邦公司推出的一款容量为 128M-bit(相当于 16M-byte)的 SPI 接口的 NOR Flash 芯片。用来保存数据,对应Flash,Flash 是有一个物理特性:只能写 0 ,不能写 1 ,写 1 靠擦除。
W25Q128模块参数及引脚介绍
W25Q128参数:
- 产品容量:128M-bit(16M-byte)
- 时钟频率:<=104MHz
- 工作电压:2.7V ~ 3.6V
- 工作温度:-40℃ ~ +85℃
- 支持 SPI 接口
W25Q128引脚:
| W25Q128 | STM32 | 备注 |
|---|---|---|
| VCC | 3.3 | 电源正极 |
| CS | A4/B12 | 片选信号 |
| DO | A6/B14 | 输出 |
| GND | G | 电源负极 |
| CLK | A5/B13 | 时钟信号 |
| DI | A7/B15 | 输入 |
W25Q128存储架构
W25Q128 将 16M 的容量分为 256 个块 (block),每块 64K 字节;
每块分为 16 个扇区 (sector),一扇区 4K 字节;
每扇区分为 16 个页(page),一页 256 字节。

W25Q128常用指令

| 指令(HEX) | 名称 | 作用 |
|---|---|---|
| 0x06 | 写使能 | 写入数据/擦除之前,必须先发送该指令 |
| 0x05 | 读SR1 | 判定FLASH是否处于空闲状态,擦除用 |
| 0x03 | 读数据 | 读取数据 |
| 0x02 | 页写 | 写入数据,最多写256字节 |
| 0x20 | 扇区擦除 | 扇区擦除指令,最小擦除单位 |
具体工作时序如下:
-
写使能 (06H)
执行页写,扇区擦除,块擦除,片擦除,写状态寄存器等指令前,需要写使能。
拉低 CS 片选 → 发送 06H → 拉高 CS 片选
-
读SR1(05H)------状态寄存器1
拉低 CS 片选 → 发送 05H → 返回SR1的值 → 拉高 CS 片选
-
读数据(03H)
拉低 CS 片选 → 发送 03H → 发送24位地址 → 读取数据(1~n)→ 拉高 CS 片选
-
页写 (02H)
页写命令最多可以向FLASH传输256个字节的数据。
拉低 CS 片选 → 发送 02H → 发送24位地址 → 发送数据(1~n)→ 拉高 CS 片选
-
扇区擦除(20H)
写入数据前,检查内存空间是否全部都是 0xFF ,不满足需擦除。
拉低 CS 片选 → 发送 20H→ 发送24位地址 → 拉高 CS 片选
发送扇区起始地址
W25Q128状态寄存器
有3个状态寄存器,常用的是状态寄存器1:

BUSY:指示当前的状态,0 表示空闲;1 表示忙碌。
WEL:写使能锁定,为 1 时,可以操作页/扇区/块;为 0 时,写禁止。
解释:
WEL:写使能,置1可写
BUSY:写的过程为1
1. 实验目的
读写W25Q128
2.硬件清单
- W25Q128
- 上官二号
- ST-Link
- USB转TTL
3. 硬件接线
| STM32 | W25Q128 |
|---|---|
| PA4 | CS |
| PA5 | CLK |
| PA6 | DO |
| PA7 | DI |
| 3V3 | VCC |
| GND | GND |
查看产品手册

4. 构思环节
4.1 新建w25q128文件夹
复制之前实验的串口打印功能实验,建立w25q128文件夹,建立相应.c和.h文件,并加载进来。
4.2 搭建框架
w25q128.c
1. SPI通信部分代码
c
// 封装通信部分
void w25q128_spi_init(void)
{
// 对里面的参数进行配置
对象是SPI
模式是主设备
方向是全双工
数据长度是8bit
时钟极性是LOW
时钟相位是奇数边沿
NSS是自己拿IO口,SOFT
波特率是256,可选择其它
第一位是高位先行
中断模式是失能
CRCCalculation = DISABLE;
CRCPolynomial = 7;
HAL_SPI_Init();
}
c
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
判断是否为SPI1
{
开启GPIO和SPI1时钟
GPIO初始化4个引脚
// NSS初始化配置
1.引脚PA4
2. 推挽输出
3. 上拉电阻
4. 速度可选高速
// CLK和DI初始化配置
1.引脚PA5和PA7
2. 复用推挽输出
// DO初始化配置
1. 引脚PA6
2. 输入
}
}
GPIO口4个引脚初始化,看参考手册

c
// 读写数据
uint8_t w25q128_spi_swap_byte(uint8_t data)
{
//定义一个参数接收数据
HAL_SPI_TransmitReceive(句柄, 要发送的数据内容, 接收的数据内容, 字节1,溢出(超时)1000)
返回接收数据
}
2. W25Q128操作流程
操作流程图:

c
// 1. w25q128初始化
void w25q128_init(void)
{
w25q128_spi_init();
}
// 2. 写使能
void w25q128_write_enable(void)
{
}
// 3. 读SR1
void w25q128_read_sr1(void)
{
}
// 4. 读数据
void w25q128_read_data(uint32_t address, uint8_t *data, uint32_t size)
{
}
// 5. 页写
void w25q128_write_page(uint32_t address, uint8_t *data, uint16_t size)
{
}
// 6. 扇区擦除
void w25q128_eraser_sector(uint32_t address)
{
}
问题:不确定自己写的对不对?芯片相关参数读出来

c
uint16_t w25q128_read_id(void)
{
uint16_t device_id = 0;
// CS引脚拉低
W25Q128_CS(0);
// 读写数据的内容
w25q128_spi_swap_byte(0x90);
w25q128_spi_swap_byte(0x00); // dummy
w25q128_spi_swap_byte(0x00);// dummy
w25q128_spi_swap_byte(0x00);
device_id = w25q128_spi_swap_byte(0xFF) << 8; // 期待数据放到高位,左移8位即可
// 再读一个字节,放到device_id的低位
device_id |= w25q128_spi_swap_byte(0xFF);
// CS引脚拉高
W25Q128_CS(1);
return device_id;
}
w25q128.h
c
#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)
main.c
c
uint8_t device_id = w25q128_read_id();
printf("device_id: %X\r\n", device_id);
观察是否打印"EFI7"
3. W25Q128封装命令接口
封装指令表
c
/* 指令表*/
#define FLASH_ManufactDeviceID 0x90
#define FLASH_WriteEnable 0x06
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadData 0x03
#define FLASH_PageProgram 0x02
#define FLASH_SectorEraser 0x20
#define FLASH_DummyByte 0xFF
封装命令接口
c
// 1. w25q128初始化
void w25q128_init(void)
{
w25q128_spi_init();
}
// 2. 写使能
void w25q128_write_enable(void)
{
W25Q128_CS(0);
w25q128_spi_swap_byte(FLASH_WriteEnable);
W25Q128_CS(1);
}
// 3. 读SR1
uint8_t w25q128_read_sr1(void)
{
uint8_t recv_data = 0;
W25Q128_CS(0);
w25q128_spi_swap_byte(FLASH_ReadStatusReg1);
// 返回SR1的值
recv_data = w25q128_spi_swap_byte(FLASH_DummyByte);
W25Q128_CS(1);
return recv_data;
}
// 封装地址
void w25q128_send_address(uint32_t address)
{
// 右移16位,发送最高位
w25q128_spi_swap_byte(address >> 16);
// 发送中间8位
w25q128_spi_swap_byte(address >> 8);
// 发送低8位
w25q128_spi_swap_byte(address);
}
// 4. 读数据
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);
// 发送24位地址
w25q128_send_address(address);
// 读取数据(1~n)
for(i = 0; i < size; i++)
data[i] = w25q128_spi_swap_byte(FLASH_DummyByte);
W25Q128_CS(1);
}
// 封装忙等待函数
void w25q128_wait_busy(void)
{
// 关注最后一位,如果是等于0x01,处于busy状态,一直在while循环中读
while((w25q128_read_sr1() & 0x01) == 0x01);
}
// 5.页写
void w25q128_write_page(uint32_t address, uint8_t *data,uint16_t size)
{
uint32_t i = 0;
// 写使能
w25q128_write_enable();
W25Q128_CS(0);
w25q128_spi_swap_byte(FLASH_PageProgram);
// 发送24位地址
w25q128_send_address(address);
// 读取数据(1~n)
for(i = 0; i < size; i++)
w25q128_spi_swap_byte(data[i]);
W25Q128_CS(1);
//写完数据后,需要花时间,忙等待
w25q128_wait_busy();
}
// 6. 扇区擦除
void w25q128_eraser_sector(uint32_t address)
{
// 写使能
w25q128_write_enable();
// 等待空闲
w25q128_wait_busy();
// 拉低片选
W25Q128_CS(0);
// 发送扇区擦除指令
w25q128_spi_swap_byte(FLASH_SectorEraser);
// 发送24位地址
w25q128_send_address(address);
// 拉高片选
W25Q128_CS(1);
// 等待空闲
w25q128_wait_busy();
}
- main.c
uint8_t data_write4 = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t data_read4 = {0};
w25q128_eraser_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_read0, data_read1, data_read2, data_read3);
// 注释擦除和写入,再看是否打印出刚才写入的数据
// 断电再试一下,看看是否为刚才写入的数据
验证FLASH是掉电不丢失数据。
5. 代码展示
w25q128.c
c
#include "w25q128.h"
SPI_HandleTypeDef spi_handle = {0};
// SPI初始化
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.CRCPolynomial = 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);
}
// Msp初始化
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI1)
{
GPIO_InitTypeDef gpio_initstruct;
// 开启GPIO和SPI时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
__HAL_RCC_SPI1_CLK_ENABLE();
// NSS初始化
gpio_initstruct.Pin = GPIO_PIN_4; // 引脚A4
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);
// CLK和DI初始化配置
gpio_initstruct.Pin = GPIO_PIN_5 | GPIO_PIN_7; // 引脚A5和A7
gpio_initstruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
// DO初始化配置
gpio_initstruct.Pin = GPIO_PIN_6; // 引脚A6
gpio_initstruct.Mode = GPIO_MODE_INPUT; // 输入
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
}
}
// 读写数据
uint8_t w25q128_spi_swap_byte(uint8_t data)
{
uint8_t receive_data = 0;
HAL_SPI_TransmitReceive(&spi_handle, &data, &receive_data, 1, 1000);
return receive_data;
}
// W25Q128存储器操作流程
// 1. w25q128初始化
void w25q128_init(void)
{
w25q128_spi_init();
}
// 2. 写使能
void w25q128_write_enable(void)
{
W25Q128_CS(0);
w25q128_spi_swap_byte(FLASH_WriteEnable);
W25Q128_CS(1);
}
// 3. 读SR1
uint8_t w25q128_read_sr1(void)
{
uint8_t recv_data = 0;
W25Q128_CS(0);
w25q128_spi_swap_byte(FLASH_ReadStatusReg1);
// 返回SR1的值
recv_data = w25q128_spi_swap_byte(FLASH_DummyByte);
W25Q128_CS(1);
return recv_data;
}
// 封装地址
void w25q128_send_address(uint32_t address)
{
// 右移16位,发送最高位
w25q128_spi_swap_byte(address >> 16);
// 发送中间8位
w25q128_spi_swap_byte(address >> 8);
// 发送低8位
w25q128_spi_swap_byte(address);
}
// 4. 读数据
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);
// 发送24位地址
w25q128_send_address(address);
// 读取数据(1~n)
for(i = 0; i < size; i++)
data[i] = w25q128_spi_swap_byte(FLASH_DummyByte);
W25Q128_CS(1);
}
// 封装忙等待函数
void w25q128_wait_busy(void)
{
// 关注最后一位,如果是等于0x01,处于busy状态,一直在while循环中读
while((w25q128_read_sr1() & 0x01) == 0x01);
}
// 5.页写
void w25q128_write_page(uint32_t address, uint8_t *data,uint16_t size)
{
uint32_t i = 0;
// 写使能
w25q128_write_enable();
W25Q128_CS(0);
w25q128_spi_swap_byte(FLASH_PageProgram);
// 发送24位地址
w25q128_send_address(address);
// 读取数据(1~n)
for(i = 0; i < size; i++)
w25q128_spi_swap_byte(data[i]);
W25Q128_CS(1);
//写完数据后,需要花时间,忙等待
w25q128_wait_busy();
}
// 6. 扇区擦除
void w25q128_eraser_sector(uint32_t address)
{
// 写使能
w25q128_write_enable();
// 等待空闲
w25q128_wait_busy();
// 拉低片选
W25Q128_CS(0);
// 发送扇区擦除指令
w25q128_spi_swap_byte(FLASH_SectorEraser);
// 发送24位地址
w25q128_send_address(address);
// 拉高片选
W25Q128_CS(1);
// 等待空闲
w25q128_wait_busy();
}
// 验证自己写的是否正确?读取芯片ID接口
uint16_t w25q128_read_id(void)
{
uint16_t device_id = 0;
//CS引脚拉低
W25Q128_CS(0);
//读写数据的内容
//w25q128_spi_swap_byte(0xFF);
w25q128_spi_swap_byte(FLASH_ManufactDeviceID);
w25q128_spi_swap_byte(0x00); // dummy
w25q128_spi_swap_byte(0x00); // dummy
w25q128_spi_swap_byte(0x00);
// 期待数据放到高位,左移8位
device_id = w25q128_spi_swap_byte(FLASH_DummyByte) << 8;
// 再读一个字节,放到device_id的低位
device_id |= w25q128_spi_swap_byte(FLASH_DummyByte);
// CS引脚拉高
W25Q128_CS(1);
return device_id;
}
w25q128.h
c
#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_SectorEraser 0x20
#define FLASH_DummyByte 0xFF
void 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_eraser_sector(uint32_t address);
#endif
main.c
c
#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();
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_eraser_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)
{
}
}
6. 实验结果
