当前位置: 首页 > ds >正文

STM32之SPI详解

一、SPI和Flash存储

1. SPI基本概述

1.1 SPI 概述

        SPI(Serial Peripheral Interface)是一种高速、全双工、同步的串行通信总线协议,由摩托罗拉 (现为 NXP)公司开发。它被广泛应用于微控制器(MCU)、传感器、存储器(如 FlashSD 卡)、实时时钟(RTC)、数字信号处理器(DSP)等各种外围设备之间的短距离通信。 由于其简单性和高效率,SPI 是嵌入式系统中最常用的通信协议之一。

1.2 SPI的连接方式

  • SPI 属于一主多从方式。Master 主机 Slave 从机
  • SCLK 时钟线,要求主机必须提供主机和从机之间通信使用的时钟频率。用于控制当前 SPI 工作模式
  • MOSI/sdo, Master Output, Slave Input,主机发送数据,从机接收数据,主机发送数据端口。
  • MISO/sdi, Master Input, Slave Output,从机发送数据,主机接受数据,主机接受数据端口
  • SS/cs, SPI Select/chip select, 片选线,根据当前 SS/cs 高电平选择,选择当前主机对应通信的对应从机是哪一个。

补充说明

  • SPI 全双工实现是依赖于 MISO/sdi 和 MOSI/sdo,利用两根数据通信线,基于 SCLK 时钟完成数据通信,可以同时进行读写操作。
  • SS/cs SPI 实现一主多从方式是依赖于片选线,多组设备连接一组 SPI 协议接口,需要提供多个 SS/cs 片选线控制通信外设选择。

1.3 SPI 的四种工作模式

  • CPOL (Clock Polarity):时钟极性

    • 决定了 SCK 时钟信号在空闲时的状态。
    • CPOL = 0时钟空闲时为低电平。
    • CPOL = 1时钟空闲时为高电平。
  • CPHA (Clock Phase):时钟相位

    • 决定了数据是在时钟的第几个边沿被采样 / 捕获 / 读取 ==> 对应 MISO/sdi。
    • CPHA = 0数据在时钟的第一个边沿(即第一个跳变沿)被采样。
    • CPHA = 1数据在时钟的第二个边沿被采样。

依据以上两个控制变量匹配,对应 SPI 有四种工作模式 通过组合 CPOL 和 CPHA,就构成了 SPI 的四种工作模式:

  • 模式 0 (Mode 0)CPOL=0, CPHA=0

  • 模式 1 (Mode 1)CPOL=0, CPHA=1

  • 模式 2 (Mode 2)CPOL=1, CPHA=0

  • 模式 3 (Mode 3)CPOL=1, CPHA=1

工作模式中,使用 SPI0 和 SPI3 较多。

1.4 SPI 数据形式

  • 主要分析 SPI 数据 0 和 数据 1
  • SPI 数据是依赖于 SCLK 时钟线,MISO/sdi 和 MOSI/sdo 数据线完成分析,同时需要考虑当前 SPI 的工作模式
  • 按照 SPI0 工作模式 CPOL = 0(时钟空闲位低电平),CPHA = 0(第一个跳变沿采集数据)分析 SPI 数据 0 和 1

1.5 SPI 环形总线结构

2. STM32 SPI 支持

2.1 SPI 框图和技术特征

        SPI 移位寄存器的核心工作机制是:以数据帧长度(通常 8 位)为容量,在每个时钟沿同步完成 “1 位发送” 和 “1 位接收”—— 按 MSB 优先(默认)或 LSB 优先的方向,待发数据从移位寄存器最高位(或最低位)“移出” 到 MOSI 总线,同时 MISO 总线上的外部数据从最低位(或最高位)“移入” 寄存器,通过连续多个时钟沿的移位动作,最终完成一整帧数据的全双工交换。

SPI 移位寄存器的核心工作机制是:以数据帧长度(通常 8 位)为容量,在每个时钟沿同步完成 “1 位发送” 和 “1 位接收”—— 按 MSB 优先(默认)或 LSB 优先的方向,待发数据从移位寄存器最高位(或最低位)“移出” 到 MOSI 总线,同时 MISO 总线上的外部数据从最低位(或最高位)“移入” 寄存器,通过连续多个时钟沿的移位动作,最终完成一整帧数据的全双工交换。

2.2 SPI 配置

2.2.1 MCU SPI 配置为主模式

2.2.2 2 SPI 对应引脚和时钟分析

  • 根据当前原理图分析,时钟使能对应 GPIOB 和 SPI2
  • GPIOB 对应使用控制寄存器是 APB2ENR
  • SPI2 对应使用控制寄存器是 APB1ENR,注意 APB1 外设最大的时钟频率为 36 MHz

3. W25Q128 Flash 存储芯片

3.1 存储芯片基本内容

W25Q128 是华邦电子(Winbond)生产的一款非常流行和经典的 128M - bit 串行 Flash 存储器芯片。它采用 SPI(串行外设接口)进行通信,因其容量适中、接口简单、成本低廉、可靠性高,被广泛应用于各种电子设备中。

除 W25Q128 版本还有:

  • W25Q128 ==> 实际容量是 16 MB
  • W25Q64 ==> 实际容量是 8 MB
  • W25Q32 ==> 实际容量是 4 MB
  • W25Q16 ==> 实际容量是 2 MB

基于当前 STM32 单片机内部的 Flash 存储空间较少,可以利用外部的 Flash 芯片作为存储设备。可以用于车辆里程信息存储,指纹锁密码和指纹信息存储,RFID 读卡器数据存储。

3.2 W25Q128 芯片技术参考内容

  • W25Q128 是一款高性能、低功耗、高可靠性的串行闪存芯片,适用于嵌入式、IoT、汽车电子、工业控制等多种应用场景。其 SPI/QSPI 接口、高速读写、工业级温度范围等特点,使其成为现代电子系统中的理想存储解决方案
  • 存储容量:128Mbit(16MB),采用 16M×8 的组织结构。
  • 接口类型:
    • 标准 SPI(Serial Peripheral Interface)
    • Dual SPI(双线 SPI)
    • Quad SPI(四线 SPI)
    • QPI(快速四线 SPI)
  • 时钟频率:最高 104MHz(标准 SPI),在 Quad SPI 模式下可进一步提升数据传输速率。
  • 写入速度:
    • 字写周期 50μs
    • 页写周期 3ms(256 字节 / 页)。
  • 擦除单位:
    • 扇区擦除(4KB)
    • 块擦除(64KB)
    • 整片擦除(16MB)。
  • 工作电压:2.7V - 3.6V,适用于多种嵌入式系统。满足 3.3V 标准 MCU 工作电压需求。
  • 温度范围:工业级 - 40°C ~ +85°C,适用于严苛环境。
  • 封装形式:
    • SOP - 8(小型封装)
    • WSON - 8(更紧凑的封装)。
  • 数据保持时间:20 年(非易失性存储)。
  • 擦写寿命:100,000 次(每个存储单元可擦写 10 万次)。
  • 电流数据:
    • 工作电流低于 5 mA
    • 掉电电流低于 1 μA

3.3 W25Q128 控制线和数据模式

  • 片选 CS / SS
    • CS 引脚用于启用或禁用设备操作:
      • 当 CS 为高电平时:
        • 设备处于未选中状态,串行数据输出引脚(DQ 或 IO0、IO1、IO2、IO3)呈高阻状态;
        • 若设备未执行内部擦除、编程或状态寄存器写入操作,其功耗将降至待机水平,电流 1 μA
      • 当 CS 拉低时:
        • 设备被选中,功耗升至工作状态电流不大于 5 mA,此时可向设备写入指令或读取数据;
        • 上电后,必须确保 CS 经历一次从高到低的跳变,设备才会接受新指令。
  • 输入输出 DI、DO
    • 4.2 串行数据输入、输出及输入 / 输出端口(DI、DO 与 IO0、IO1、IO2、IO3)
      W25Q128FV 支持标准 SPI、双线 SPI 和四线 SPI 操作。在标准 SPI 模式下:
      • 单向 DI(输入)引脚用于在串行时钟(CLK)输入引脚的上升沿,向设备串行写入指令、地址或数据;DI 连接的引脚是 MCU MOSI/sdo
      • 单向 DO(输出)引脚则在 CLK 的下降沿从设备读取数据或状态。DO 链接引脚是 MCU 的 MISO/sdi
  • 写保护 WP
    • WP 引脚用于防止状态寄存器被意外写入,其特性如下:
      1. 硬件写保护机制:
      • 通过与状态寄存器的块保护位(CMP、SEC、TB、BP2、BP1、BP0)及状态寄存器保护位(SRP)配合,可实现从最小 4KB 扇区到整个存储阵列的硬件保护。
      • 该引脚为低电平有效(Active Low),当拉低时激活保护功能。
      1. 四线模式下的功能切换:
      • 若状态寄存器 - 2 中的 QE 位(Quad 使能位)被置 1,设备将进入四线 / I/O 模式,此时 I/WP 引脚功能失效,转而作为 IO2 信号线使用。
      • 四线 / I/O 模式的引脚配置详见图 1a 至 1c。
  • Hold 引脚
    • 暂停与恢复控制:
      • 当 CS 为低电平(设备选中)且 HOLD 被拉低时:
        • DO 引脚将进入高阻态;
        • DI 和 CLK 引脚上的信号将被忽略(无效状态)。
      • 当 HOLD 恢复高电平后,设备操作将继续执行。
    • 多设备共享 SPI 总线时的应用:
      • 该功能特别适用于多个设备共享同一组 SPI 信号线的场景,可通过 / HOLD 临时挂起当前设备以切换总线控制权。
      • 引脚为低电平有效(Active Low)。
    • 四线模式下的功能切换:
      • 若状态寄存器 - 2 中的 QE 位(Quad 使能位)被置 1,设备进入四线 / I/O 模式,此时 / HOLD 引脚功能失效,转而作为 IO3 信号线使用。
      • 四线 / I/O 模式的引脚配置详见

3.4 地址规则【重点】

  • 块 Block
    • 块区域字节数是 64 KB,按照一块字节数数量,地址范围是 0x0000 ~ 0xFFFF
    • 当前设备是 W25Q128 对应 16 MB 空间,当前设备一共有 256 块 (64KB),按照块进行编号 0x00 ~ 0xFF
    • 整个 Flash 空间中每一个字节对应的地址是 0x000000 ~ 0xFFFFFF
  • 扇区
    • 每一个扇区占用 4 KB,一块 ==> 16 扇区
    • 在当前块中,对应扇区的编号是 0x0 ~ 0xF (0 ~ 15)
    • 每一页占用 256 字节,一个扇区 ==> 16 页
    • 在当扇区中,对应页的编号是 0x0 ~ 0xF (0 ~ 15)
  • 页内字节
    • 每一页占用 256 字节
    • 页内字节对应的地址凡是 0x00 ~ 0xFF (0 ~ 255)

需要在第 12 块中,第 10 个扇区,第 5 页中的页内 108 个字节写入一个字节数据

  • 对应地址 0x0B946B

例如:

3.5 W25Q128 指令对照表

指令名称操作码 (Hex)功能描述时钟周期 (Clock Cycles)地址字节 (Address Bytes)哑字节 (Dummy Bytes)数据字节 (Data Bytes)
写使能0x06允许后续的写入或擦除操作8000
写禁止0x04禁止后续的写入或擦除操作8000
读状态寄存器 - 10x05读取状态寄存器 1 的值(位 S7 到 S0)8+8001+ (可连续读)
读状态寄存器 - 20x35读取状态寄存器 2 的值(位 S15 到 S8)8+8001+ (可连续读)
写状态寄存器0x01写入状态寄存器(同时配置 S7-S0 和 S15-S8)8+16002
页编程0x02将数据写入芯片(最大 256 字节为一页)8+24301-256
扇区擦除 (4KB)0x20擦除一个 4KB 大小的扇区8+24300
块擦除 (32KB)0x52擦除一个 32KB 大小的块8+24300
块擦除 (64KB)0xD8擦除一个 64KB 大小的块8+24300
整片擦除0xC7 / 0x60擦除整个芯片的所有数据8000
读取数据0x03从指定地址开始读取数据8+24301+ (可连续读)
快速读取0x0B比标准读取更快的读取方式8+24311+ (可连续读)
双线快速读取0x3B使用两条数据线进行快速读取(提高速度)8+24311+ (可连续读)
四线快速读取0x6B使用四条数据线进行快速读取(速度最快)8+24311+ (可连续读)
功率下降0xB9使芯片进入低功耗睡眠模式8000
释放功率下降0xAB将芯片从低功耗睡眠模式中唤醒8+243 (虚设地址)00
读 JEDEC ID0x9F读取制造商标识、内存类型和设备容量 ID8+240

4. 标准库 SPI Flash

4.1 标准库SPI源码移植到项目中

4.2 原码修改
spi_flash.h 头文件修改内容

4.2 代码

main.c:

#include "stm32f10x.h"#include "led.h"
#include "key.h"
#include "delay.h"
#include "beep.h"
#include "usart1.h"
#include "adc.h"
#include "systick.h"
#include "tim6.h"
#include "tim3.h"
#include "sg90.h"
#include "myiic.h"
#include "spi.h"
#include "spi_flash.h"#include "stdio.h"
#include "stdlib.h"
#include "string.h"/*
根据 W25Q128 地址规则,提供对应读写数据地址对应地址 0x012300 ==> 块编号为 1 扇区编号为 2 页编号位 3,页内字节对应 00  PageSize == 0x100 ==> 256 字节
*/
#define WRITE_ADDR (0x012300)
#define READ_ADDR  WRITE_ADDR/*
发送数据和接收数据的对应空间都是按照一页数据空间考虑。
*/
#define W25Q128_PAGE_SZIE (256)
u8 spi_send_buffer[W25Q128_PAGE_SZIE] = "ABCDEFG12345677654321GFEDCBA";
u8 spi_read_buffer[W25Q128_PAGE_SZIE] = {0};int main(void)
{Led_Init();USART1_Init(115200);/*利用 SPI 标准库提供的函数进行 SPI 初始化操作*/sFLASH_Init();/*优先将对应的 W25Q128 指定扇区空间进行擦除操作 删除 4KB*/sFLASH_EraseSector(WRITE_ADDR);/*利用 SPI 标准库提供的 writeBuffer 函数写入数据到 W25Q128 中*/sFLASH_WriteBuffer(spi_send_buffer, WRITE_ADDR, strlen((const char *)spi_send_buffer));/*用 SPI 标准库提供的 ReadBuffer 函数读取 W25Q128 中数据*/sFLASH_ReadBuffer(spi_read_buffer, READ_ADDR, strlen((const char *)spi_send_buffer));printf("spi_read_buffer : %s\n", spi_read_buffer);while (1){Led1_Ctrl(1);}
}

spi.c:

void SPI2_Init(void)
{/*PB12 --> CS/SS- GPIO 模式选择 推挽输出模式PB13 --> SCK- GPIO 模式选择 复用推挽输出模式PB14 --> MISO - Master Input, Slave Output- GPIO 模式选择 浮空输入模式PB15 --> MOSI- Master Output, Slave Input- GPIO 模式选择 复用推挽输出模式时钟使能需要SPI2  ==> RCC_APB1ENR_SPI2ENGPIOB ==> RCC_APB2ENR_IOPBEN*/// 1. 时钟使能RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 2. 准备配置 GPIOB 相关的内容GPIO_InitTypeDef GPIO_InitStructure = {0};// PB12 GPIO 配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);// PB13 , PB15 GPIO 配置 都是复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOB, &GPIO_InitStructure);// PB14 GPIO 配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOB, &GPIO_InitStructure);/*3. GPIO 当前默认电平设置所有引脚全部拉高,作为准备阶段内容。尤其是 CS/SS ==> PB12 一旦处于低电平状态,相当于选择对应芯片。*/GPIO_SetBits(GPIOB, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);// 4. SPI 配置SPI_InitTypeDef SPI_InitStructure = {0};/*SPI 工作模式配置SPI0(00) SPI_CPOL_Low 	空闲时钟为低电平  SPI_CPHA_1Edge 数据采样为第一个跳变沿SPI1(01) SPI_CPOL_Low 	空闲时钟为低电平  SPI_CPHA_2Edge 数据采样为第二个跳变沿SPI2(10) SPI_CPOL_High 	空闲时钟为高电平  SPI_CPHA_1Edge 数据采样为第一个跳变沿SPI3(11) SPI_CPOL_High 	空闲时钟为高电平  SPI_CPHA_2Edge 数据采样为第二个跳变沿*/SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;// 设置 SPI 当前模式为主机模式 MasterSPI_InitStructure.SPI_Mode = SPI_Mode_Master;// 设置 SPI 数据发送高位先出SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;// 设置 SPI 工作数据方向,对应全双工 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;// 设置 SPI 字长为 8 bit ==> 1 byte SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;// 设置 SPI 当前波特率分频倍数,基于当前 APB1 时钟 36MHz// SPI_BaudRatePrescaler 选择 2 ,对应 SPI 工作波特率为 18MHzSPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;// 设置 SPI 触发方式为软件控制触发SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;// 对 SPI 进行初始化操作SPI_Init(SPI2, &SPI_InitStructure);// 开启 SPI2 SPI_Cmd(SPI2, ENABLE);
}

0voice · GitHub

http://www.xdnf.cn/news/19825.html

相关文章:

  • 【IntelliJ IDEA】插件分享
  • 设计软件启动失败?“找不到vcruntime140.dll,无法继续执行代码” 场景化解决方案来了
  • 作为软件专业学生,我眼中新架构实践的‘稳’与‘进’
  • 【算法】哈希表专题
  • 【Lua】题目小练13
  • 多线程的三种实现方法
  • C#基础(⑦user32.dll)
  • 各省市信息化项目管理办法中的网络安全等级保护如何规定的?
  • 前缀树约束大语言模型解码
  • 05 Centos 7尝试是否有网络
  • 深入浅出 RabbitMQ-RabbitMQ消息确认机制(ACK)
  • 解锁WebRTC在数字人领域的无限潜能
  • 【音视频】火山引擎实时、低延时拥塞控制算法的优化实践
  • centos系统如何判断是是x86还是x64?
  • ansible变量+管理机密
  • AV1 HEADERS详解
  • 专为 SOC 分析师和 MSSP 设计的威胁搜寻指南
  • flink中的窗口的介绍
  • mysql5.6+分页时使用 limit+order by 会出现数据重复问题
  • Mysql杂志(七)
  • Shell脚本入门:从零到精通
  • C# 原型模式(C#中的克隆)
  • “转”若惊鸿,电磁“通”——耐达讯自动化RS485转Profinet点亮能源新章
  • 【NestJS】HTTP 接口传参的 5 种方式(含前端调用与后端接收)
  • 【卷积神经网络】卷积神经网络的三大核心优势:稀疏交互、参数共享与等变表示
  • C++之基于正倒排索引的Boost搜索引擎项目介绍
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘black’问题
  • 【提示词】...(后续单元)在Prompt 的作用
  • 【linux仓库】万物至简的设计典范:如何用‘文件’这一个概念操纵整个Linux世界?
  • 在Docker中安装MySQL时3306端口占用问题