STM32外设SPI FLASH应用实例

发布于:2025-02-19 ⋅ 阅读:(15) ⋅ 点赞:(0)

1. 前言

在嵌入式系统中,数据的存储和备份是一个非常重要的功能。SPI FLASH 是一种常见的非易失性存储器,具有容量大、速度快、接口简单等优点。本文将介绍如何在 STM32F103 上使用 SPI 接口操作 QW128 SPI FLASH,并通过乒乓存储的方式实现数据备份。

1.1 硬件准备

  • STM32F103 开发板
  • QW128 SPI FLASH 模块
  • 杜邦线若干

1.2 软件准备

  • Keil MDK 或 STM32CubeIDE
  • STM32 HAL 库

2. 硬件连接

将 QW128 SPI FLASH 模块与 STM32F103 开发板连接,具体连接方式如下:

QW128 引脚 STM32F103 引脚
CS PA4
SCK PA5
MISO PA6
MOSI PA7
GND GND
VCC 3.3V

在这里插入图片描述

3. 软件实现

使用STM32CUBE配置SPI通信
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.1 SPI 初始化

首先,我们需要初始化 SPI 接口。使用 STM32CubeMX 配置 SPI1 外设,并生成初始化代码。

void MX_SPI1_Init(void)
{
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
}

3.2 QW128 SPI FLASH 驱动

接下来,我们编写 QW128 SPI FLASH 的驱动代码,包括读写操作。

#define QW128_CMD_WRITE_ENABLE 0x06
#define QW128_CMD_WRITE_DISABLE 0x04
#define QW128_CMD_READ_STATUS_REG 0x05
#define QW128_CMD_WRITE_STATUS_REG 0x01
#define QW128_CMD_READ_DATA 0x03
#define QW128_CMD_PAGE_PROGRAM 0x02
#define QW128_CMD_SECTOR_ERASE 0x20
#define QW128_CMD_CHIP_ERASE 0xC7

void QW128_WriteEnable(void)
{
    uint8_t cmd = QW128_CMD_WRITE_ENABLE;
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

void QW128_WriteDisable(void)
{
    uint8_t cmd = QW128_CMD_WRITE_DISABLE;
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

uint8_t QW128_ReadStatusReg(void)
{
    uint8_t cmd = QW128_CMD_READ_STATUS_REG;
    uint8_t status;
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
    HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    return status;
}

void QW128_WriteStatusReg(uint8_t status)
{
    uint8_t cmd[2] = {QW128_CMD_WRITE_STATUS_REG, status};
    QW128_WriteEnable();
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi1, cmd, 2, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

void QW128_ReadData(uint32_t addr, uint8_t *data, uint16_t len)
{
    uint8_t cmd[4] = {QW128_CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF};
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
    HAL_SPI_Receive(&hspi1, data, len, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

void QW128_PageProgram(uint32_t addr, uint8_t *data, uint16_t len)
{
    uint8_t cmd[4] = {QW128_CMD_PAGE_PROGRAM, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF};
    QW128_WriteEnable();
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
    HAL_SPI_Transmit(&hspi1, data, len, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

void QW128_SectorErase(uint32_t addr)
{
    uint8_t cmd[4] = {QW128_CMD_SECTOR_ERASE, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF};
    QW128_WriteEnable();
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

void QW128_ChipErase(void)
{
    uint8_t cmd = QW128_CMD_CHIP_ERASE;
    QW128_WriteEnable();
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

3.3 乒乓存储实现

乒乓存储是一种常用的数据备份策略,通过交替使用两个存储区域来确保数据的完整性和可靠性。

#define PAGE_SIZE 256
#define SECTOR_SIZE 4096
#define BUFFER_SIZE 1024

uint8_t buffer[BUFFER_SIZE];
uint32_t current_sector = 0;

void PingPong_Backup(uint8_t *data, uint16_t len)
{
    // 擦除当前扇区
    QW128_SectorErase(current_sector * SECTOR_SIZE);

    // 写入数据
    for (uint16_t i = 0; i < len; i += PAGE_SIZE)
    {
        QW128_PageProgram(current_sector * SECTOR_SIZE + i, data + i, PAGE_SIZE);
    }

    // 切换到下一个扇区
    current_sector = (current_sector + 1) % 2;
}

void PingPong_Restore(uint8_t *data, uint16_t len)
{
    // 读取数据
    QW128_ReadData(current_sector * SECTOR_SIZE, data, len);
}

4. 测试与验证

4.1 数据备份测试

uint8_t test_data[BUFFER_SIZE];
for (uint16_t i = 0; i < BUFFER_SIZE; i++)
{
    test_data[i] = i % 256;
}

PingPong_Backup(test_data, BUFFER_SIZE);

4.2 数据恢复测试

uint8_t restore_data[BUFFER_SIZE];
PingPong_Restore(restore_data, BUFFER_SIZE);

// 验证数据
for (uint16_t i = 0; i < BUFFER_SIZE; i++)
{
    if (restore_data[i] != test_data[i])
    {
        // 数据不一致,处理错误
        Error_Handler();
    }
}

5 实例

5.1 参数结构体定义

以下是参数结构体的定义,基于你提供的代码:

typedef enum
{
    BAUD_9600,
    BAUD_19200,
    BAUD_115200
} BAUD_ENUM;

typedef struct
{
    BAUD_ENUM CommBaud;          // 通信波特率
    uint8_t OnOffCtrl;           // 启停操作方式(0-本地;1-远程485;2-模拟量)
    uint8_t ModeCtrl;            // 模式修改方式(0-本地;1-远程485;2-模拟量)
    uint8_t SetValCtrl;          // 设定修改方式(0-本地;1-远程485;2-模拟量)
    uint8_t MasterSlaver;        // 主副机设置(0-主机;1-副机;2-单机)
    uint8_t TestMode;            // 测试模式
    uint8_t DebugMode;           // 调试模式
    uint8_t DeviceModel;         // 设备型号(0-3KW;2-20KW风冷)
    uint8_t DeviceSer[32];       // 设备序列号
    uint8_t AlarmEnable;         // 告警使能(0-关闭;1-使能)
    uint8_t CommProto;           // 通信协议(0-Modbus;1-Profibus)
    uint16_t UdcLimit;           // Udc调节限定值
    uint16_t IdcLimit;           // Idc调节限定值
    uint16_t PdcLimit;           // Pdc调节限定值
    uint8_t ModeSlect;           // 调节模式选择(0-Udc;1-Idc;2-Pdc)
    uint8_t PWM1Freq;            // PWM1频率(40~80表示40KHz~80KHz)
} DeviceParams;

5.2 存储参数到 SPI FLASH

我们可以将参数结构体存储到 SPI FLASH 的指定地址。以下是存储函数的实现:

#include "stm32f1xx_hal.h"
#include "spi_flash.h"  // 假设这是 QW128 SPI FLASH 的驱动头文件

#define PARAMS_FLASH_ADDR 0x00000000  // 参数存储的起始地址

void SaveParamsToFlash(DeviceParams *params)
{
    // 擦除 SPI FLASH 的指定扇区
    QW128_SectorErase(PARAMS_FLASH_ADDR);

    // 将参数结构体写入 SPI FLASH
    QW128_PageProgram(PARAMS_FLASH_ADDR, (uint8_t *)params, sizeof(DeviceParams));
}

5.3 从 SPI FLASH 读取参数

从 SPI FLASH 中读取参数结构体的实现如下:

void LoadParamsFromFlash(DeviceParams *params)
{
    // 从 SPI FLASH 读取参数结构体
    QW128_ReadData(PARAMS_FLASH_ADDR, (uint8_t *)params, sizeof(DeviceParams));
}

5.4 示例:存储和读取参数

以下是一个完整的示例,展示如何初始化参数、存储到 SPI FLASH 以及从 SPI FLASH 读取参数:

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_SPI1_Init();  // 初始化 SPI
    MX_GPIO_Init();  // 初始化 GPIO

    // 初始化参数结构体
    DeviceParams params = {
        .CommBaud = BAUD_115200,
        .OnOffCtrl = 1,
        .ModeCtrl = 1,
        .SetValCtrl = 1,
        .MasterSlaver = 0,
        .TestMode = 0,
        .DebugMode = 1,
        .DeviceModel = 2,
        .DeviceSer = "1234567890ABCDEF1234567890ABCDEF",
        .AlarmEnable = 1,
        .CommProto = 0,
        .UdcLimit = 1000,
        .IdcLimit = 500,
        .PdcLimit = 2000,
        .ModeSlect = 1,
        .PWM1Freq = 60
    };

    // 存储参数到 SPI FLASH
    SaveParamsToFlash(&params);

    // 从 SPI FLASH 读取参数
    DeviceParams loadedParams;
    LoadParamsFromFlash(&loadedParams);

    // 验证读取的参数是否正确
    if (memcmp(&params, &loadedParams, sizeof(DeviceParams)) == 0)
    {
        printf("Parameters loaded successfully!\n");
    }
    else
    {
        printf("Parameter load failed!\n");
    }

    while (1)
    {
        // 主循环
    }
}

5.6 注意事项

  1. SPI FLASH 的寿命

    • SPI FLASH 的擦写次数有限(通常为 10 万次左右),频繁擦写可能导致损坏。建议在设计中尽量减少擦写操作。
  2. 数据对齐

    • 确保参数结构体的数据对齐与 SPI FLASH 的页大小(通常为 256 字节)匹配,避免跨页写入。
  3. 数据校验

    • 在存储和读取参数时,可以添加 CRC 校验或校验和,确保数据的完整性。
  4. 备份机制

    • 可以使用乒乓存储策略,将参数存储在两个不同的扇区中,确保在一个扇区损坏时可以从另一个扇区恢复数据。

6. 总结

本文介绍了如何在 STM32F103 上使用 SPI 接口操作 QW128 SPI FLASH,并通过乒乓存储的方式实现数据备份。通过这种方式,可以有效地提高数据的可靠性和系统的稳定性。希望本文对大家有所帮助,欢迎在评论区留言讨论。



网站公告

今日签到

点亮在社区的每一天
去签到