型号介绍
W25Q128是华邦公司推出的一款容量为 128M-bit(1byte == 8bit,相当于 16M-byte)的 SPI 接口的 NOR Flash 芯片(NOR Flash:一种非易失性存储器,它可以在断电或掉电后仍然保持存储的数据,被广泛应用于长期数据存储。有容量大,可重复擦写、按“扇区/块”擦除的特性。Flash的物理特性:只能写0,不能写 1,写1靠擦除。这一特性对后续编程,实现控制非常重要)
相应的还有其他型号
型号 | 容量 |
W25Q256 | 256M bit |
W25Q64 | 64M bit |
W25Q32 | 32M bit |
W25Q16 | 16M bit |
W25Q80 | 8M bit |
内部存储架构
以标准SPI总线方式接线的话,主要就以下pin脚
/CS片选
使能或者失能SPI设备的操作。当CS为高电平是设备未选中,串行数据线处于高阻抗状态。低电平选中芯片。
DO(IO1)数据输出(数据输入输出1)标准SPI模式下的MISO
GND 接地
DI(IO0)数据输入(数据输入输出0)标准SPI模式下的MOSI
CLK 串行时钟输入
VCC 电源供应
W25Q128将16M的容量分为256个块(block),每块 64K 字节;
每块分为16个扇区(sector),一扇区4K字节;每扇区分为16个页(page),一页 256 字节。
W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。需要给W25Q128开辟一个至少4K的缓存区。
地址范围为 000000h ~ FFFFFFh
相关寄存器及指令
状态寄存器1
状态寄存器2
状态寄存器3
其中常用的是状态寄存器1
BUSY:指示当前的状态,0 表示空闲;1 表示忙碌。
WEL:写使能锁定,为 1 时,可以操作页/扇区/块;为 0 时,写禁止。
指令表
其中常用的指令有
指令(HEX) | 名称 | 功能 |
0x06 | 写使能 | 写入/擦除数据前,必须发送指令使能 |
0x05 | 读SR1 | 判断flash是否处于空闲状态,擦除用 |
0x03 | 读数据 | 读取数据 |
0x02 | 页写 | 写入数据,最多写256字节 |
0x20 | 扇区擦除 | 扇区(最小擦除单位)擦除指令 |
注意事项:
执行页写,扇区擦除,块擦除,片擦除,写状态寄存器等指令前,需要写使能
写入数据前,检查内存空间是否全部都是 0xFF ,不满足需擦除
拉低 CS 片选 → 发送 06H → 拉高 CS 片选
拉低 CS 片选 → 发送 05H → 返回SR1的值 → 拉高 CS 片选
拉低 CS 片选 → 发送 03H → 发送24位地址 → 读取数据(1~n)→ 拉高 CS 片选
拉低 CS 片选 → 发送 02H → 发送24位地址 → 发送数据(1~n)→ 拉高 CS 片选
拉低 CS 片选 → 发送 20H→ 发送24位地址 → 拉高 CS 片选
主从机通信
以32位单片机为例,STM32在SPI 通信中,主机和从机都有一个串行移位寄存器。主机通过向自己的 SPI 串行寄存器写入一个字节来发起传输。
1. 首先,拉低相应的 SS 信号线,表示与特定的从机进行通信。
2. 主机通过发送 SCLK 时钟信号告诉从机进行数据的读写操作。
(注意:SCLK 时钟信号可以是低电平有效或高电平有效,因为SPI有不同的模式)。
3. 主机将要发送的数据写入发送数据缓冲区,然后通过移位寄存器逐位地将数据传输给从机的串行移位寄存器,使用 MOSI 信号线进行传输。同时,从机的 MISO 接口接收到的数据也经过移位寄存器一位一位地移到接收缓冲区。
4. 从机也通过 MISO 信号线将自己串行移位寄存器中的内容返回给主机。同时,从机通过 MOSI 信号线接收主机发送的数据。这样,两个移位寄存器中的内容就被交换。
5.SPI通信只有主模式和从模式,没有明确的读和写操作之分。实际上,外设的写操作和读操作是同步完成的。在SPI通信中,发送一个数据必然会收到一个数据;如果要接收一个数据,就必须先发送一个数据。
简而言之:
如果只进行写操作,主机可以忽略从设备传输过来的字节,因为主机不需要接收数据。
如果主机要读取从设备的一个字节,那么主机必须发送一个空字节来引发从设备的传输。
SPI通讯协议就不进行详细介绍了(不重复造轮子节约大家时间),不懂的小伙伴可以看我往期的博客-通信协议,或自行百度哈
读写实例
在代码中,使用HAL库进行配置,SPI的工作模式配置为 0,即 CPOL = 0,CPHA = 0
选择SPI1控制器
w25q128.c文件
#include "w25q128.h"
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);
}
/*
__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi) 为可重写的弱函数
在 HAL_SPI_Init 中被调用
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI1)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__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);
}
}
/*
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t
*pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)
hspi: 指向SPI外设的句柄(handle)。
pTxData:要发送的数据缓冲区指针。
pRxData:接收数据的缓冲区指针。
Size: 要发送/接收的数据字节数。
Timeout:超时时间,以毫秒为单位。
*/
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;
}
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 get_w25q128_id(void)
{
uint16_t flash_type;
w25q128_spi_swap_byte(0xFF); /* 清除DR的作用 */
W25Q128_CS(1); /* 拉高片选 */
flash_type = w25q128_read_id(); /* 读取FLASH ID. */
if (flash_type == 0XEF17) /* FLASH芯片号0XEF17 */
printf("型号为W25Q128芯片 0XEF17\r\n");
}
void w25q128_init(void)
{
w25q128_spi_init(); //再封装,不暴露接口
}
void w25q128_writ_enable(void)
{
W25Q128_CS(0);
w25q128_spi_swap_byte(FLASH_WriteEnable);
W25Q128_CS(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位为0 */
void w25q128_wait_busy(void)
{
while((w25q128_read_sr1() & 0x01) == 0x01);
}
void w25q128_send_address(uint32_t address)
{
w25q128_spi_swap_byte(address >> 16); /* 发送 bit23 ~ bit16 地址 */
w25q128_spi_swap_byte(address >> 8); /* 发送 bit15 ~ bit8 地址 */
w25q128_spi_swap_byte(address); /* 发送 bit7 ~ bit0 地址 */
}
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 0xFF
void w25q128_init(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);
void get_w25q128_id(void);
#endif
main.c文件
#include "sys.h"
#include "delay.h"
#include "uart1.h"
#include "w25q128.h"
uint8_t data_write[4] = {0x12, 0x34, 0x56, 0x78};
uint8_t data_read[4] = {0};
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
uart1_init(115200);
w25q128_init();
printf("hello world!\r\n");
get_w25q128_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) { }
}
最后需要注意:
写内存时需要注意Flash的一个要点,Flash编程只能将1写为0,而不能将0写成1,写1靠擦除。所以需要在写内存的时候将内存擦除,使用内存擦除指令擦除内存,内存变为0xFF,然后再写内存。(任何一款芯片都会有状态寄存器,在操作芯片的时候需要先了解芯片的状态再去操作)
完结撒花!!!!!!