W25Q128 spi-flash HAL库驱动详解

型号介绍

W25Q128是华邦公司推出的一款容量为 128M-bit(1byte == 8bit,相当于 16M-byte)的 SPI 接口的 NOR Flash 芯片(NOR Flash:一种非易失性存储器,它可以在断电或掉电后仍然保持存储的数据,被广泛应用于长期数据存储。有容量大,可重复擦写、按“扇区/块”擦除的特性。Flash的物理特性:只能写0,不能写 1,写1靠擦除。这一特性对后续编程,实现控制非常重要)

相应的还有其他型号

型号容量
W25Q256256M bit
W25Q6464M bit
W25Q3232M bit
W25Q1616M bit
W25Q808M 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,然后再写内存。(任何一款芯片都会有状态寄存器,在操作芯片的时候需要先了解芯片的状态再去操作)

完结撒花!!!!!!

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值