STM32外设SPI FLASH应用实例

news2025/5/18 23:13:05

STM32外设SPI FLASH应用实例

  • 1. 前言
    • 1.1 硬件准备
    • 1.2 软件准备
  • 2. 硬件连接
  • 3. 软件实现
    • 3.1 SPI 初始化
    • 3.2 QW128 SPI FLASH 驱动
    • 3.3 乒乓存储实现
  • 4. 测试与验证
    • 4.1 数据备份测试
    • 4.2 数据恢复测试
  • 5 实例
    • 5.1 参数结构体定义
    • 5.2 存储参数到 SPI FLASH
    • 5.3 从 SPI FLASH 读取参数
    • 5.4 示例:存储和读取参数
    • 5.6 注意事项
  • 6. 总结

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 引脚
CSPA4
SCKPA5
MISOPA6
MOSIPA7
GNDGND
VCC3.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,并通过乒乓存储的方式实现数据备份。通过这种方式,可以有效地提高数据的可靠性和系统的稳定性。希望本文对大家有所帮助,欢迎在评论区留言讨论。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2300233.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java零基础入门笔记:(1-2)入门(简介、基础知识)

前言 本笔记是学习狂神的java教程&#xff0c;建议配合视频&#xff0c;学习体验更佳。 【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili - Java简介 Java是一种广泛使用的高级编程语言&#xff0c;具有简单、面向对象、分布式、多线程、动态性、健壮性和安全…

Java 基于 SpringBoot+Vue 的动漫平台(附源码,文档)

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战*✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447…

Ubuntu 系统 cuda12.2 安装 MMDetection3D

DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(free)” 贵在坚持&#xff01; ---------------------------------------…

DDD该怎么去落地实现(3)通用的仓库和工厂

通用的仓库和工厂 我有一个梦&#xff0c;就是希望DDD能够成为今后软件研发的主流&#xff0c;越来越多研发团队都转型DDD&#xff0c;采用DDD的设计思想和方法&#xff0c;设计开发软件系统。这个梦想在不久的将来是有可能达成的&#xff0c;因为DDD是软件复杂性的解决之道&a…

【SpringBoot苍穹外卖】debugDay0 打开前端页面

在某一天学完后&#xff0c;电脑关机&#xff0c;再打开啥都忘了&#xff0c;记起来一点点&#xff0c;前端页面打不开&#xff0c;后端控制台一直循环出错。原来是下面这样哈哈。 查看端口是否被别的程序占用的操作步骤 winR输入cmd打开命令行 netstat -ano | findstr "8…

做谷歌SEO需要了解哪些基本概念?

做谷歌SEO时&#xff0c;必须掌握一些基本的概念。首先是关键词。关键词是用户在搜索框里输入的词汇&#xff0c;它们直接影响到你网站的排名。所以&#xff0c;了解用户的搜索习惯、挑选合适的关键词&#xff0c;是每一个SEO优化者必须做的工作。 内容是关键。谷歌非常看重网…

通过BingAPI爬取Bing半个月内壁纸

通过BingAPI爬取Bing半个月内壁纸 一、前言二、爬虫代码三、代码说明 一、前言 爬取Bing搜索网站首页壁纸的方式主要有两种&#xff0c;第一种为间接爬取&#xff0c;即并不直接对Bing网站发起请求&#xff0c;而是对那些收集汇总了Bing壁纸的网站发起请求&#xff0c;爬取图片…

springboot021-基于协同过滤算法的个性化音乐推荐系统

&#x1f495;&#x1f495;作者&#xff1a; 小九学姐 &#x1f495;&#x1f495;个人简介&#xff1a;十年Java&#xff0c;Python美女程序员一枚&#xff0c;精通计算机专业前后端各类框架。 &#x1f495;&#x1f495;各类成品Java毕设 。javaweb&#xff0c;ssm&#xf…

开关电源实战(一)宽范围DC降压模块MP4560

系列文章目录 文章目录 系列文章目录MP4560MP4560 3.8V 至 55V 的宽输入范围可满足各种降压应用 MOSFET只有250mΩ 输出可调0.8V-52V SW:需要低VF肖特基二极管接地,而且要靠近引脚,高压侧开关的输出。 EN:输入使能,拉低到阈值以下关闭芯片,拉高或浮空启动 COMP:Compens…

【MySQL】我在广州学Mysql 系列——Mysql 日志管理详解

ℹ️大家好&#xff0c;我是练小杰&#xff0c;今天又是新的一周了&#xff0c;又该摆好心态迎接美好的明天了&#xff01;&#xff01;&#xff01;&#x1f606; 本文主要对Mysql数据库中的日志种类以及基本命令进行讨论&#xff01;&#xff01; 回顾&#xff1a;&#x1f4…

《Zookeeper 分布式过程协同技术详解》读书笔记-2

目录 zk的一些内部原理和应用请求&#xff0c;事务和标识读写操作事务标识&#xff08;zxid&#xff09; 群首选举Zab协议&#xff08;ZooKeeper Atomic Broadcast protocol&#xff09;文件系统和监听通知机制分布式配置中心, 简单Demojava code 集群管理code 分布式锁 zk的一…

HTML5+CSS多层级ol标签序号样式问题

在CSS中&#xff0c;ol标签用于创建有序列表&#xff0c;而多层级的ol标签可以通过CSS实现不同的序号样式。以下是一些常见的问题和解决方案&#xff1a; 1. 多层级ol的序号格式问题 默认情况下&#xff0c;多层级的ol标签会自动继承父级的序号格式&#xff0c;但有时我们可能…

网络初始2:网络编程--基于UDP和TCP实现回显器

基础概念 1.发送端与接受端 在通过网络传输信息时&#xff0c;会有两个进程&#xff0c;接收端和发送端。 发送端&#xff1a;数据的发送方进程&#xff0c;即网络通信中的源主机。 接收端&#xff1a;数据的接收方进程&#xff0c;即网路通信中的目的主机。 2.Socet套接字…

vtkCamera类的Dolly函数作用及相机拉近拉远

录 1. 预备知识 1.1.相机焦点 2. vtkCamera类的Dolly函数作用 3. 附加说明 1. 预备知识 要理解vtkCamera类的Dolly函数作用,就必须先了解vtkCamera类表示的相机的各种属性。  VTK是用vtkCamera类来表示三维渲染场景中的相机。vtkCamera负责把三维场景投影到二维平面,如…

车载音频架构图详解(精简)

目录 上图是车载音频架构图,对这个图我们进行详细的分析 左边第一层 是 app 常用的类有MediaPlayer和MediaRecorder, AudioTrack和AudioRecorder 第二层 是framework提供给应用的多媒体功能的AP

使用神经网络对驾驶数据进行道路类型分类

摘要 道路分类&#xff0c;了解我们是在城市、农村地区还是在高速公路上驾驶&#xff0c;可以提高现代驾驶员辅助系统的性能&#xff0c;并有助于了解驾驶习惯。本研究的重点是仅使用车速数据来普遍解决这个问题。已经开发了一种数据记录方法&#xff0c;用于为 On-board Diagn…

S4D480 S4HANA 基于PDF的表单打印

2022年元旦的笔记草稿 SAP的表单打印从最早的SAPScripts 到后来的SMARTFORM&#xff0c;步入S4时代后由于Fiori的逐渐普及&#xff0c;更适应Web的Adobe Form成了SAP主流output文件格式。 目录 一、 基于PDF表单打印系统架构Interface 接口Form 表单ContextLayout 二、表单接…

qt QOpenGLTexture详解

1. 概述 QOpenGLTexture 是 Qt5 提供的一个类&#xff0c;用于表示和管理 OpenGL 纹理。它封装了 OpenGL 纹理的创建、分配存储、绑定和设置像素数据等操作&#xff0c;简化了 OpenGL 纹理的使用。 2. 重要函数 构造函数&#xff1a; QOpenGLTexture(const QImage &image,…

Deepseek-R1推理模型API接入调用指南 ChatGPT Web Midjourney Proxy 开源项目接入Deepseek教程

DeepSeek-R1和OpenAI o1模型都属于推理任务模型&#xff0c;两个模型各有优点&#xff1a;DeepSeek-R1 在后训练阶段大规模使用了强化学习技术&#xff0c;在仅有极少标注数据的情况下&#xff0c;极大提升了模型推理能力。在数学、代码、自然语言推理等任务上&#xff0c;性能…

蓝耘智算携手DeepSeek,共创AI未来

&#x1f31f; 各位看官号&#xff0c;我是egoist2023&#xff01; &#x1f30d; 种一棵树最好是十年前&#xff0c;其次是现在&#xff01; &#x1f680; 今天来学习如何通过蓝耘智算使用DeepSeek R1模型 &#x1f44d; 如果觉得这篇文章有帮助&#xff0c;欢迎您一键三连&a…