STM32CubeMX-H7-10-SPI对W25Q64的读写

前言

w25q64是经常使用的存储芯片,在项目比赛的时候,存储参数集聚非常有用

这样就不担心自己设计的菜单系统因为重新上电而把原来调好的参数又重置,所以读写功能就很重要。

模块的简介

下面是豆包给的模块资料,可以看出这个模块存储的容量非常大,但是删除就删除掉一个扇区

引脚配置

模块上的DI接了PA0,CLK接了PA1,DO接了PA2,CS接了PA3

根据自己的接线调整!

模块的封装

所以我打算把一个数据用一个扇区来存储

那么我就可以存储2024个变量,于是经过豆包生成的spi代码以及我自己的封装,于是我生成了一个简单的函数,就可以实现变量的读写,只需要一个函数

int32_t save_digital(uint8_t flag, uint16_t address, int32_t num)

后面只需要调用这个函数

这个函数是这个样子

int32_t save_digital(uint8_t flag, uint16_t address, int32_t num)
{
    // 定义一个错误返回值,这里用 -1 作为错误标识
    const int32_t ERROR_RETURN = -1;

    if (address > 2047) {
        return ERROR_RETURN; // 地址超出范围,返回错误值
    }
    uint32_t sectorAddr = address * SECTOR_SIZE;
    uint8_t buffer[4]; // 用于存储32位数据的字节数组
    int32_t result = 0;

    switch (flag) {
        case 0: // 写入
            buffer[0] = (num >> 24) & 0xFF;
            buffer[1] = (num >> 16) & 0xFF;
            buffer[2] = (num >> 8) & 0xFF;
            buffer[3] = num & 0xFF;

            W25Q64_SectorErase(sectorAddr); // 先擦除扇区
            W25Q64_PageProgram(sectorAddr, buffer, 4); // 写入数据
            break;
        case 1: // 读取
            W25Q64_BufferRead(sectorAddr, buffer, 4);
            result = ((int32_t)buffer[0] << 24) | ((int32_t)buffer[1] << 16) | ((int32_t)buffer[2] << 8) | buffer[3];
            break;
        case 2: // 清除
            W25Q64_SectorErase(sectorAddr);
            break;
        default:
            result = ERROR_RETURN; // 无效的标志位,返回错误值
            break;
    }
    return result;
}

其中我设定flag为0的时候是写入,1的时候是读取,2的时候是清除

当flag为0的时候num无效

然后地址可以从0到2047,存储2048个变量

文件的.c和.h文件

把.c和.h文件放上去后,就可以直接调用这个函数使用了。

w25q64_soft_spi.c

#include "w25q64_soft_spi.h"
#include <string.h>

// 每个扇区大小为4KB
#define SECTOR_SIZE 4096

// 软件SPI初始化
void W25Q64_SoftSPI_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOA_CLK_ENABLE();

    // 配置CS引脚
    GPIO_InitStruct.Pin = W25Q64_CS_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(W25Q64_CS_PORT, &GPIO_InitStruct);

    // 配置CLK引脚
    GPIO_InitStruct.Pin = W25Q64_CLK_PIN;
    HAL_GPIO_Init(W25Q64_CLK_PORT, &GPIO_InitStruct);

    // 配置MOSI引脚
    GPIO_InitStruct.Pin = W25Q64_MOSI_PIN;
    HAL_GPIO_Init(W25Q64_MOSI_PORT, &GPIO_InitStruct);

    // 配置MISO引脚
    GPIO_InitStruct.Pin = W25Q64_MISO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    HAL_GPIO_Init(W25Q64_MISO_PORT, &GPIO_InitStruct);

    // 片选信号初始化为高电平
    W25Q64_CS_HIGH();
}

// 软件SPI读写一个字节
uint8_t W25Q64_SoftSPI_ReadWriteByte(uint8_t byte)
{
    uint8_t receivedByte = 0;
    for (int i = 7; i >= 0; i--)
    {
        if (byte & (1 << i))
        {
            W25Q64_MOSI_HIGH();
        }
        else
        {
            W25Q64_MOSI_LOW();
        }

        W25Q64_CLK_LOW();
        HAL_Delay(1); // 适当延时,确保时钟信号稳定
        receivedByte <<= 1;
        if (W25Q64_READ_MISO())
        {
            receivedByte |= 0x01;
        }
        W25Q64_CLK_HIGH();
        HAL_Delay(1);
    }
    return receivedByte;
}

// 读取设备ID
void W25Q64_ReadID(uint8_t * ManufacturerID, uint16_t * DeviceID)
{
    W25Q64_CS_LOW();
    uint8_t temp = W25Q64_SoftSPI_ReadWriteByte(0x90);  // 发送读ID指令
    temp = W25Q64_SoftSPI_ReadWriteByte(0x00);
    temp = W25Q64_SoftSPI_ReadWriteByte(0x00);
    temp = W25Q64_SoftSPI_ReadWriteByte(0x00);
    *ManufacturerID = W25Q64_SoftSPI_ReadWriteByte(0xFF);
    *DeviceID = W25Q64_SoftSPI_ReadWriteByte(0xFF) << 8;
    *DeviceID |= W25Q64_SoftSPI_ReadWriteByte(0xFF);
    W25Q64_CS_HIGH();
}

// 写使能
void W25Q64_WriteEnable(void)
{
    W25Q64_CS_LOW();
    W25Q64_SoftSPI_ReadWriteByte(0x06);  // 发送写使能指令
    W25Q64_CS_HIGH();
}

// 等待写操作结束
void W25Q64_WaitForWriteEnd(void)
{
    uint8_t status;
    W25Q64_CS_LOW();
    W25Q64_SoftSPI_ReadWriteByte(0x05);  // 发送读状态寄存器指令
    do
    {
        status = W25Q64_SoftSPI_ReadWriteByte(0xFF);
    } while (status & 0x01);
    W25Q64_CS_HIGH();
}

// 扇区擦除
void W25Q64_SectorErase(uint32_t SectorAddr)
{
    W25Q64_WriteEnable();
    W25Q64_CS_LOW();
    W25Q64_SoftSPI_ReadWriteByte(0x20);  // 发送扇区擦除指令
    W25Q64_SoftSPI_ReadWriteByte((SectorAddr >> 16) & 0xFF);
    W25Q64_SoftSPI_ReadWriteByte((SectorAddr >> 8) & 0xFF);
    W25Q64_SoftSPI_ReadWriteByte(SectorAddr & 0xFF);
    W25Q64_CS_HIGH();
    W25Q64_WaitForWriteEnd();
}

// 页编程
void W25Q64_PageProgram(uint32_t Address, uint8_t * pBuffer, uint16_t NumByteToWrite)
{
    W25Q64_WriteEnable();
    W25Q64_CS_LOW();
    W25Q64_SoftSPI_ReadWriteByte(0x02);  // 发送页编程指令
    W25Q64_SoftSPI_ReadWriteByte((Address >> 16) & 0xFF);
    W25Q64_SoftSPI_ReadWriteByte((Address >> 8) & 0xFF);
    W25Q64_SoftSPI_ReadWriteByte(Address & 0xFF);
    for (uint16_t i = 0; i < NumByteToWrite; i++)
    {
        W25Q64_SoftSPI_ReadWriteByte(pBuffer[i]);
    }
    W25Q64_CS_HIGH();
    W25Q64_WaitForWriteEnd();
}

// 缓冲区读取
void W25Q64_BufferRead(uint32_t Address, uint8_t * pBuffer, uint16_t NumByteToRead)
{
    W25Q64_CS_LOW();
    W25Q64_SoftSPI_ReadWriteByte(0x03);  // 发送读数据指令
    W25Q64_SoftSPI_ReadWriteByte((Address >> 16) & 0xFF);
    W25Q64_SoftSPI_ReadWriteByte((Address >> 8) & 0xFF);
    W25Q64_SoftSPI_ReadWriteByte(Address & 0xFF);
    for (uint16_t i = 0; i < NumByteToRead; i++)
    {
        pBuffer[i] = W25Q64_SoftSPI_ReadWriteByte(0xFF);
    }
    W25Q64_CS_HIGH();
}

#include "w25q64_soft_spi.h"
// 假设SECTOR_SIZE在头文件或者其他地方已经定义
#define SECTOR_SIZE 4096

int32_t save_digital(uint8_t flag, uint16_t address, int32_t num)
{
    // 定义一个错误返回值,这里用 -1 作为错误标识
    const int32_t ERROR_RETURN = -1;

    if (address > 2047) {
        return ERROR_RETURN; // 地址超出范围,返回错误值
    }
    uint32_t sectorAddr = address * SECTOR_SIZE;
    uint8_t buffer[4]; // 用于存储32位数据的字节数组
    int32_t result = 0;

    switch (flag) {
        case 0: // 写入
            buffer[0] = (num >> 24) & 0xFF;
            buffer[1] = (num >> 16) & 0xFF;
            buffer[2] = (num >> 8) & 0xFF;
            buffer[3] = num & 0xFF;

            W25Q64_SectorErase(sectorAddr); // 先擦除扇区
            W25Q64_PageProgram(sectorAddr, buffer, 4); // 写入数据
            break;
        case 1: // 读取
            W25Q64_BufferRead(sectorAddr, buffer, 4);
            result = ((int32_t)buffer[0] << 24) | ((int32_t)buffer[1] << 16) | ((int32_t)buffer[2] << 8) | buffer[3];
            break;
        case 2: // 清除
            W25Q64_SectorErase(sectorAddr);
            break;
        default:
            result = ERROR_RETURN; // 无效的标志位,返回错误值
            break;
    }
    return result;
}

w25q64_soft_spi.h

#ifndef __W25Q64_SOFT_SPI_H
#define __W25Q64_SOFT_SPI_H

#include "gpio.h"

// 定义软件SPI引脚
#define W25Q64_CS_PIN    GPIO_PIN_3
#define W25Q64_CS_PORT   GPIOA
#define W25Q64_CLK_PIN   GPIO_PIN_1
#define W25Q64_CLK_PORT  GPIOA
#define W25Q64_MISO_PIN  GPIO_PIN_2
#define W25Q64_MISO_PORT GPIOA
#define W25Q64_MOSI_PIN  GPIO_PIN_0
#define W25Q64_MOSI_PORT GPIOA

// 片选操作宏定义
#define W25Q64_CS_LOW()  HAL_GPIO_WritePin(W25Q64_CS_PORT, W25Q64_CS_PIN, GPIO_PIN_RESET)
#define W25Q64_CS_HIGH() HAL_GPIO_WritePin(W25Q64_CS_PORT, W25Q64_CS_PIN, GPIO_PIN_SET)

// 时钟操作宏定义
#define W25Q64_CLK_LOW() HAL_GPIO_WritePin(W25Q64_CLK_PORT, W25Q64_CLK_PIN, GPIO_PIN_RESET)
#define W25Q64_CLK_HIGH() HAL_GPIO_WritePin(W25Q64_CLK_PORT, W25Q64_CLK_PIN, GPIO_PIN_SET)

// MOSI操作宏定义
#define W25Q64_MOSI_LOW() HAL_GPIO_WritePin(W25Q64_MOSI_PORT, W25Q64_MOSI_PIN, GPIO_PIN_RESET)
#define W25Q64_MOSI_HIGH() HAL_GPIO_WritePin(W25Q64_MOSI_PORT, W25Q64_MOSI_PIN, GPIO_PIN_SET)

// 读取MISO引脚电平
#define W25Q64_READ_MISO() HAL_GPIO_ReadPin(W25Q64_MISO_PORT, W25Q64_MISO_PIN)

// 函数声明
void W25Q64_SoftSPI_Init(void);
uint8_t W25Q64_SoftSPI_ReadWriteByte(uint8_t byte);
void W25Q64_ReadID(uint8_t * ManufacturerID, uint16_t * DeviceID);
void W25Q64_WriteEnable(void);
void W25Q64_WaitForWriteEnd(void);
void W25Q64_SectorErase(uint32_t SectorAddr);
void W25Q64_PageProgram(uint32_t Address, uint8_t * pBuffer, uint16_t NumByteToWrite);
void W25Q64_BufferRead(uint32_t Address, uint8_t * pBuffer, uint16_t NumByteToRead);
int32_t save_digital(uint8_t flag, uint16_t address, int32_t num);

#endif

功能测试

我对几个简单的读写功能进行测试

因为是32位,所以可以读写的大小是-2^31到 2^31

获取ID号

     W25Q64_SoftSPI_Init();

    uint8_t ManufacturerID;
    uint16_t DeviceID;
    W25Q64_ReadID(&ManufacturerID, &DeviceID);
    printf("Manufacturer ID: %d, Device ID: %d\r\n", ManufacturerID, DeviceID);

写入读取,然后清楚后再读取

   int32_t testData[] = {-12345,21321};
    uint16_t testAddress[] = {0,1};

    // 写入数据
 save_digital(0, testAddress[0], testData[0]);
 save_digital(0, testAddress[1], testData[1]);
   
    // 读取数据
    int32_t readData = save_digital(1, testAddress[0], 0);
    printf("Read data0: %d\r\n", readData);
    
    readData = save_digital(1, testAddress[1], 0);
    printf("Read data1: %d\r\n", readData);
    // 清除数据
    
    save_digital(2, testAddress[0], 0);
    readData = save_digital(1, testAddress[0], 0);
    printf("dele data0: %d\r\n", readData);
       
    save_digital(2, testAddress[1], 0);
    readData = save_digital(1, testAddress[1], 0);
    printf("dele data1: %d\r\n", readData);

可以看出写入后确实成功读取,但是删除后确实清零了

读写测试

首先只写

写入成功

只读

读取成功