STM32学习笔记【31.SPI总线——W25Q128实验】

文章目录


前言

使用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();
}
  1. 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. 实验结果