STM32中SPI协议详解

前言

在嵌入式系统中,设备间的数据传输协议多种多样,SPI(Serial Peripheral Interface,串行外设接口)凭借其高速、全双工、易用性等特点,成为连接STM32与高速外设(如OLED屏、Flash芯片、AD转换器、无线模块等)的首选方案。与UART的异步通信不同,SPI采用同步通信方式,通过时钟信号协调数据传输,适合需要高频数据交换的场景。

本文将从SPI协议基础出发,详细解析STM32 SPI外设的工作原理、硬件设计、软件配置及实战案例,涵盖寄存器级编程、HAL库应用、DMA传输优化等核心内容,同时提供调试技巧与常见问题解决方案,旨在帮助嵌入式开发者全面掌握STM32中SPI的应用。

一、SPI协议基础

1.1 什么是SPI?

SPI是一种同步串行通信协议,由摩托罗拉公司提出,最初用于短距离设备间的高速数据传输。其核心特点是“同步通信”——通信双方通过同一时钟信号(由主机产生)同步数据传输,因此无需像UART那样依赖波特率约定,传输速率更高(通常可达几十Mbps,甚至数百Mbps,取决于硬件支持)。

SPI是一种“主从式”协议,通信系统由一个主机(Master) 和一个或多个从机(Slave) 组成,主机负责产生时钟信号并发起通信,从机被动响应。

1.2 SPI信号线组成

SPI通信至少需要4根信号线(全双工模式),部分场景可简化为3根(单工或半双工),各信号线功能如下:

信号线全称方向(主机视角)功能描述
SCKSerial Clock输出时钟信号,由主机产生,用于同步数据传输(频率决定传输速率)。
MOSIMaster Out Slave In输出主机发送数据到从机的信号线(主机输出,从机输入)。
MISOMaster In Slave Out输入从机发送数据到主机的信号线(从机输出,主机输入)。
NSS/CSSlave Select/Chip Select输出从机选择信号(低电平有效,部分设备高电平有效),主机通过该信号指定通信的从机。

注意

  • 多从机场景下,所有从机的SCK、MOSI、MISO信号线并联,主机通过独立的NSS引脚分别控制各从机(或通过软件模拟NSS信号);
  • 部分简单从机可能省略NSS引脚(如某些OLED屏),需通过硬件接线固定选中状态(如将NSS引脚直接接地)。

1.3 SPI通信时序与工作模式

SPI的核心是“时序同步”,数据在SCK的边沿(上升沿或下降沿)被采样或输出,不同设备对时钟边沿的要求不同,因此SPI定义了4种工作模式,由时钟极性(CPOL)时钟相位(CPHA) 组合决定。
在这里插入图片描述

1.3.1 时钟极性(CPOL)
  • CPOL=0:SCK空闲状态为低电平(0);
  • CPOL=1:SCK空闲状态为高电平(1)。
1.3.2 时钟相位(CPHA)
  • CPHA=0:数据在SCK的第一个边沿(从空闲状态到有效状态的跳变)被采样;
  • CPHA=1:数据在SCK的第二个边沿(从有效状态回到空闲状态的跳变)被采样。
1.3.3 4种工作模式组合
模式CPOLCPHA采样时机(数据稳定阶段)典型应用设备
000SCK从低→高(上升沿)采样,数据在下降沿输出SD卡、W25Q系列Flash
101SCK从高→低(下降沿)采样,数据在上升沿输出部分AD转换器
210SCK从高→低(下降沿)采样,数据在上升沿输出较少见
311SCK从低→高(上升沿)采样,数据在下降沿输出OLED屏(如SSD1306)、RFID模块

关键:通信双方必须使用相同的工作模式,否则会出现数据错位(例如主机用模式0,从机也必须用模式0)。

1.4 SPI数据传输过程

SPI数据传输以“帧”为单位(通常为8位或16位),全双工模式下,主机和从机的传输同步进行(主机发送1字节的同时,从机也会返回1字节)。典型传输流程如下:

  1. 主机拉低目标从机的NSS信号(选中从机);
  2. 主机通过SCK线产生时钟信号,同时在MOSI线上逐位发送数据(高位在前或低位在前,由设备约定,通常为高位在前);
  3. 从机在SCK的对应边沿采样MOSI线上的数据,同时通过MISO线向主机返回数据;
  4. 传输完成后,主机拉高NSS信号(释放从机)。

示例:主机向从机发送0x55(二进制01010101),同时从机返回0xAA(10101010),模式0(CPOL=0,CPHA=0)的时序如下:

  • 空闲时SCK为低电平,NSS为高电平;
  • NSS拉低后,SCK开始跳动:第1个上升沿(低→高)采样第1位(主机发送0,从机返回1);
  • 后续每一个上升沿依次采样第2~8位,最终主机接收0xAA,从机接收0x55。

1.5 SPI与其他协议的对比

协议同步方式信号线数量传输速率通信方式典型应用场景
SPI同步(时钟)3~4根几Mbps~数百Mbps全双工/半双工高速外设(Flash、OLED、AD)
UART异步(波特率)2根最高数Mbps全双工调试、低速传感器
I2C同步(时钟)2根最高几Mbps半双工中低速外设(EEPROM、传感器)

SPI优势:速率高、全双工、无地址冲突(通过NSS选择)、协议简单(无复杂应答机制);
SPI劣势:信号线较多(多从机时NSS线增加)、无内置纠错机制(需软件实现校验)。

二、STM32 SPI外设详解

STM32系列芯片(如F1、F4、H7等)普遍集成多个SPI外设(如F103有2个SPI,F407有3个SPI,H743有6个SPI),支持主模式、从模式及多种高级特性,满足不同场景需求。

2.1 SPI外设主要特性

以STM32F103(中低端型号)为例,其SPI外设的核心特性如下:

  • 支持主模式(Master)和从模式(Slave);
  • 传输速率:主模式下最高18Mbps(F103,APB1时钟36MHz时,SPI1挂载APB2最高72MHz,速率可达36Mbps);
  • 支持8位或16位数据帧;
  • 支持4种工作模式(CPOL/CPHA可配置);
  • 支持全双工、半双工和单线模式;
  • 支持软件或硬件NSS管理;
  • 支持中断和DMA传输(减少CPU占用);
  • 支持CRC校验(可选,用于数据完整性检查);
  • 支持数据收发中断(TXE:发送缓冲区空;RXNE:接收缓冲区非空)。

高端型号(如F4、H7)的SPI外设性能更强,例如F407的SPI最高速率可达45Mbps(APB2时钟90MHz时),H7系列甚至支持数百Mbps的传输速率,适合更高速的外设通信。

2.2 SPI外设引脚映射

STM32的SPI引脚通过“复用功能”配置,不同型号的引脚映射不同,需参考芯片数据手册的“复用功能表”。以STM32F103C8T6为例,其SPI1(高速SPI,挂载APB2总线)的默认引脚为:

  • SCK:PA5(复用推挽输出);
  • MOSI:PA7(复用推挽输出);
  • MISO:PA6(浮空输入或上拉输入);
  • NSS:PA4(推挽输出,硬件NSS时使用)。

若默认引脚被占用,可通过“重映射”功能切换到其他引脚(如SPI1可重映射到PB3/4/5),配置时需注意:

  • 重映射需使能AFIO时钟(RCC->APB2ENR |= RCC_APB2ENR_AFIOEN);
  • 通过AFIO重映射寄存器(如AFIO->MAPR)配置具体映射关系。

2.3 时钟源与速率计算

SPI的时钟来自APB总线(APB1或APB2),速率由“分频系数”决定:

  • SPI挂载在APB1时,时钟源为PCLK1(F103中最高36MHz);
  • SPI挂载在APB2时,时钟源为PCLK2(F103中最高72MHz)。

主模式下,SPI的传输速率计算公式为:
SPI时钟频率 = APB总线时钟频率 / 分频系数

分频系数可通过SPI控制寄存器1(SPI_CR1)的BR[2:0]位配置,可选值为2、4、8、16、32、64、128、256。例如:

  • 若SPI1挂载APB2(72MHz),BR[2:0]配置为001(分频系数4),则传输速率为72MHz/4=18Mbps;
  • 若SPI2挂载APB1(36MHz),BR[2:0]配置为000(分频系数2),则传输速率为36MHz/2=18Mbps。

2.4 核心寄存器解析

SPI的配置与操作主要通过以下寄存器实现(以STM32F103为例),理解这些寄存器是实现底层编程的基础。

2.4.1 SPI控制寄存器1(SPI_CR1)
位段功能描述
CPOL[1]时钟极性:0=空闲低电平;1=空闲高电平。
CPHA[0]时钟相位:0=第一个边沿采样;1=第二个边沿采样。
MSTR[2]主从模式选择:1=主模式;0=从模式。
BR[5:3]波特率分频:000=2分频,001=4分频,…,111=256分频。
SPE[6]SPI使能:1=使能;0=禁用(配置时需先禁用)。
LSBFIRST[7]数据位顺序:0=高位在前(MSB);1=低位在前(LSB)。
SSI[8]内部从机选择(软件NSS模式下有效):1=内部拉高;0=内部拉低。
SSM[9]软件从机管理:1=使能软件NSS(通过SSI控制);0=硬件NSS(通过引脚控制)。
RXONLY[10]接收-only模式:1=只接收(半双工);0=全双工。
DFF[11]数据帧格式:0=8位;1=16位。
CRCNEXT[12]下一个传输为CRC:1=下一次传输发送CRC值;0=正常数据传输。
CRCEN[13]CRC使能:1=启用CRC校验;0=禁用。
BIDIOE[14]双向模式输出使能(单线模式):1=输出使能;0=输入。
BIDIMODE[15]双向模式:1=单线双向模式;0=双线全双工模式。
2.4.2 SPI控制寄存器2(SPI_CR2)
位段功能描述
RXDMAEN[0]接收DMA使能:1=使能RXNE事件的DMA请求。
TXDMAEN[1]发送DMA使能:1=使能TXE事件的DMA请求。
SSOE[2]从机选择输出使能(硬件NSS模式):1=NSS引脚输出使能(主模式下自动拉低)。
ERRIE[5]错误中断使能:1=使能错误中断(如CRC错、溢出)。
TXEIE[7]发送缓冲区空中断使能:1=TXE事件触发中断。
RXNEIE[6]接收缓冲区非空中断使能:1=RXNE事件触发中断。
2.4.3 SPI状态寄存器(SPI_SR)
位段功能描述
RXNE[0]接收缓冲区非空:1=有数据待读取(读SPI_DR可清0)。
TXE[1]发送缓冲区空:1=可发送下一字节(写SPI_DR可清0)。
CHSIDE[2]通道侧(仅双线单向模式):0=主通道;1=副通道。
UDR[3]未读数据丢失:1=新数据接收时旧数据未读(溢出)。
CRCERR[4]CRC错误:1=接收CRC与计算值不匹配。
MODF[5]模式错误(主从冲突):1=主模式下NSS被拉低(硬件NSS时)。
OVR[6]溢出错误:1=接收缓冲区未空时新数据到来。
BSY[7]忙标志:1=SPI正在传输数据(禁止配置参数)。
2.4.4 SPI数据寄存器(SPI_DR)
  • 8位或16位寄存器,发送时写入数据,接收时读取数据;
  • 主模式下,写入SPI_DR后,传输自动开始(SCK开始产生时钟);
  • 全双工模式下,发送和接收同步进行,每次写入会触发一次传输,同时接收的数据存入DR。

三、SPI硬件设计要点

SPI硬件连接的可靠性直接影响通信稳定性,尤其在高速传输场景下,需注意信号线布局、电平匹配和抗干扰设计。

3.1 基本连接方式

3.1.1 单从机连接
  • 主机(STM32)与单个从机的连接只需4根线(SCK、MOSI、MISO、NSS);
  • 若从机无需主动发送数据(如只接收命令的OLED屏),可省略MISO线(半双工发送);
  • 若从机固定选中(无需切换),可将从机的NSS引脚直接接地(省去主机NSS控制)。
3.1.2 多从机连接

多从机场景下,采用“菊花链”或“独立NSS”两种方式:

  • 独立NSS:所有从机的SCK、MOSI、MISO并联,主机通过独立的NSS引脚(如PA4、PB0、PB1)分别控制各从机(推荐,控制灵活);在这里插入图片描述

  • 菊花链:从机1的MISO连接从机2的MOSI,依次串联,仅最后一个从机的MISO返回主机(适合低速、简单场景,如多个移位寄存器)。
    在这里插入图片描述

3.2 电平匹配与信号完整性

  • 电平匹配:STM32的GPIO为3.3V电平,若从机为5V电平(如部分Flash芯片),需通过电平转换芯片(如74HC4050)实现3.3V与5V的转换,避免损坏STM32引脚;
  • 信号线长度:SPI速率较高时(>10Mbps),信号线应尽量短(<10cm),减少信号延迟和干扰;
  • 阻抗匹配:高速传输时,可在信号线(SCK、MOSI、MISO)串联50Ω电阻(靠近主机端),匹配传输线阻抗,抑制信号反射;
  • 接地处理:所有设备共地,避免地电位差导致的电平偏移;敏感场景下可采用屏蔽线包裹信号线,屏蔽层单端接地。

3.3 NSS信号设计

NSS信号是SPI从机选择的核心,其设计需根据场景选择硬件或软件控制:

  • 硬件NSS:主机通过SPI_CR2的SSOE位使能NSS输出,主模式下NSS会自动拉低(选中从机),传输完成后自动拉高(需确保从机NSS为低电平有效);优点是无需软件干预,适合高速场景;
  • 软件NSS:通过普通GPIO模拟NSS信号(如PA4配置为推挽输出),传输前拉低,完成后拉高;优点是灵活(可任意选择引脚),适合多从机场景。

注意:硬件NSS模式下,若主模式中NSS引脚被外部拉低(如误操作),会触发MODF(模式错误),导致SPI进入从模式,需通过软件清除错误标志并重新配置。

四、SPI软件配置步骤

本节以STM32F103为主机,实现与从机的SPI通信,分别介绍寄存器级和HAL库的配置方法,以“主模式、全双工、8位数据、软件NSS、模式0(CPOL=0,CPHA=0)”为基础配置。

4.1 寄存器级配置(SPI1,主模式,18Mbps)

步骤1:使能时钟
// 使能GPIOA、SPI1和AFIO时钟(若需重映射)
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_AFIOEN;
步骤2:配置GPIO引脚
// 配置SCK(PA5)、MOSI(PA7)为复用推挽输出(50MHz)
GPIOA->CRL &= ~(GPIO_CRL_MODE5 | GPIO_CRL_CNF5);  // 清除PA5配置
GPIOA->CRL |= GPIO_CRL_MODE5_1 | GPIO_CRL_MODE5_0; // 输出速率50MHz
GPIOA->CRL |= GPIO_CRL_CNF5_1;                    // 复用推挽输出GPIOA->CRL &= ~(GPIO_CRL_MODE7 | GPIO_CRL_CNF7);  // 清除PA7配置
GPIOA->CRL |= GPIO_CRL_MODE7_1 | GPIO_CRL_MODE7_0; // 输出速率50MHz
GPIOA->CRL |= GPIO_CRL_CNF7_1;                    // 复用推挽输出// 配置MISO(PA6)为浮空输入
GPIOA->CRL &= ~(GPIO_CRL_MODE6 | GPIO_CRL_CNF6);  // 清除PA6配置
GPIOA->CRL |= GPIO_CRL_CNF6_0;                    // 浮空输入(或上拉输入)// 配置NSS(PA4)为推挽输出(软件控制)
GPIOA->CRL &= ~(GPIO_CRL_MODE4 | GPIO_CRL_CNF4);  // 清除PA4配置
GPIOA->CRL |= GPIO_CRL_MODE4_1 | GPIO_CRL_MODE4_0; // 输出速率50MHz
GPIOA->CRL |= GPIO_CRL_CNF4_0;                    // 通用推挽输出
GPIOA->BSRR = GPIO_BSRR_BS4;                      // 初始拉高NSS(释放从机)
步骤3:配置SPI1参数
// 禁用SPI1(配置前必须禁用)
SPI1->CR1 &= ~SPI_CR1_SPE;// 配置主模式、8位数据、高位在前、软件NSS
SPI1->CR1 &= ~(SPI_CR1_DFF | SPI_CR1_LSBFIRST | SPI_CR1_SSM);
SPI1->CR1 |= SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI;  // 主模式,软件NSS,内部拉高// 配置工作模式0(CPOL=0,CPHA=0)
SPI1->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);// 配置波特率:APB2时钟72MHz,分频系数4(72/4=18Mbps)
SPI1->CR1 &= ~SPI_CR1_BR;
SPI1->CR1 |= SPI_CR1_BR_0;  // BR[2:0]=001 → 4分频// 使能SPI1
SPI1->CR1 |= SPI_CR1_SPE;
步骤4:实现基本收发函数
// 拉低NSS,选中从机
void SPI1_SelectSlave(void) {GPIOA->BSRR = GPIO_BSRR_BR4;  // PA4置低
}// 拉高NSS,释放从机
void SPI1_DeselectSlave(void) {GPIOA->BSRR = GPIO_BSRR_BS4;  // PA4置高
}// 发送1字节并接收1字节(全双工)
uint8_t SPI1_TransmitReceive(uint8_t tx_data) {// 等待发送缓冲区空while (!(SPI1->SR & SPI_SR_TXE));// 发送数据SPI1->DR = tx_data;// 等待接收完成while (!(SPI1->SR & SPI_SR_RXNE));// 返回接收数据return SPI1->DR;
}// 仅发送1字节(忽略接收数据)
void SPI1_Transmit(uint8_t tx_data) {SPI1_TransmitReceive(tx_data);
}// 仅接收1字节(发送无效数据0xFF)
uint8_t SPI1_Receive(void) {return SPI1_TransmitReceive(0xFF);
}

4.2 HAL库配置(基于STM32CubeMX)

步骤1:创建工程与时钟配置
  • 打开STM32CubeMX,选择芯片型号(如STM32F103C8T6);
  • 配置RCC:选择HSE时钟,配置系统时钟为72MHz(APB2时钟72MHz,APB1时钟36MHz)。
步骤2:配置SPI1
  • 在“Pinout & Configuration”中,左侧选择“Connectivity”→“SPI1”;
  • 模式选择“Full-Duplex Master”(全双工主模式);
  • 参数配置:
    • Prescaler:4(分频系数4,72MHz/4=18Mbps);
    • Clock Polarity (CPOL):Low(0);
    • Clock Phase (CPHA):1 Edge(0);
    • Data Size:8 Bits;
    • First Bit:MSB First;
    • NSS Signal Type:Software(软件控制NSS);
  • 确认引脚:SPI1_SCK=PA5,SPI1_MISO=PA6,SPI1_MOSI=PA7;
  • 配置NSS引脚(PA4)为GPIO_Output(推挽输出,初始高电平)。
步骤3:生成代码
  • 配置工程路径和IDE(如Keil MDK);
  • 生成代码(确保SPI初始化函数MX_SPI1_Init()被正确生成)。
步骤4:HAL库收发函数实现
// 选中从机
void SPI1_SelectSlave(void) {HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
}// 释放从机
void SPI1_DeselectSlave(void) {HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}// 全双工传输(发送并接收1字节)
uint8_t SPI1_TransmitReceive(uint8_t tx_data) {uint8_t rx_data;// 阻塞式传输,超时100msHAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, 100);return rx_data;
}// 仅发送
void SPI1_Transmit(uint8_t *tx_buf, uint16_t len) {HAL_SPI_Transmit(&hspi1, tx_buf, len, 100);
}// 仅接收
void SPI1_Receive(uint8_t *rx_buf, uint16_t len) {// 发送0xFF作为无效数据uint8_t dummy = 0xFF;for (uint16_t i = 0; i < len; i++) {rx_buf[i] = SPI1_TransmitReceive(dummy);}
}

五、实战案例:SPI外设通信

5.1 案例1:与W25Q128 Flash芯片通信

W25Q128是一款128Mbit(16MB)的SPI Flash芯片,支持SPI模式0/3,最高传输速率104Mbps,常用于存储固件、日志等数据。
在这里插入图片描述

5.1.1 通信流程
  1. 读取芯片ID:通过发送指令0x90(读JEDEC ID),可获取厂商ID(0xEF)和设备ID(0x4018),验证通信是否正常。

    // 寄存器级实现
    uint32_t W25Q_ReadID(void) {uint32_t id = 0;SPI1_SelectSlave();SPI1_Transmit(0x90);       // 发送读ID指令SPI1_Transmit(0x00);       // 发送3字节地址(0x000000)SPI1_Transmit(0x00);SPI1_Transmit(0x00);id |= (SPI1_Receive() << 16);  // 厂商ID(0xEF)id |= (SPI1_Receive() << 8);   // 设备ID高8位(0x40)id |= SPI1_Receive();          // 设备ID低8位(0x18)SPI1_DeselectSlave();return id;
    }
    
  2. 读取数据:发送指令0x03(读数据)+ 3字节地址,后续接收的数据即为地址处的内容。

    void W25Q_ReadData(uint32_t addr, uint8_t *buf, uint16_t len) {SPI1_SelectSlave();SPI1_Transmit(0x03);                  // 读数据指令SPI1_Transmit((addr >> 16) & 0xFF);   // 地址高位SPI1_Transmit((addr >> 8) & 0xFF);    // 地址中位SPI1_Transmit(addr & 0xFF);           // 地址低位for (uint16_t i = 0; i < len; i++) {buf[i] = SPI1_Receive();          // 读取数据}SPI1_DeselectSlave();
    }
    
  3. 页编程:W25Q128的最小写入单位为“页”(256字节),需先发送0x02指令+地址,再写入数据(不超过256字节)。

    void W25Q_PageProgram(uint32_t addr, uint8_t *buf, uint16_t len) {if (len > 256) len = 256;  // 不超过页大小W25Q_WriteEnable();        // 使能写入(发送0x06指令)SPI1_SelectSlave();SPI1_Transmit(0x02);                  // 页编程指令SPI1_Transmit((addr >> 16) & 0xFF);SPI1_Transmit((addr >> 8) & 0xFF);SPI1_Transmit(addr & 0xFF);for (uint16_t i = 0; i < len; i++) {SPI1_Transmit(buf[i]);            // 写入数据}SPI1_DeselectSlave();W25Q_WaitBusy();          // 等待写入完成(轮询状态寄存器)
    }
    

5.2 案例2:与SSD1306 OLED屏通信(SPI模式)

SSD1306是一款常用的OLED控制器,支持SPI通信(模式3:CPOL=1,CPHA=1),需注意其数据/命令区分(通过DC引脚,高电平数据,低电平命令)。在这里插入图片描述

步骤1:硬件连接补充
  • OLED的DC引脚连接STM32的PA3(推挽输出),用于区分数据和命令。
步骤2:OLED初始化(发送命令序列)
void OLED_Init(void) {// 初始化前延时(确保电源稳定)HAL_Delay(100);SPI1_DeselectSlave();HAL_Delay(10);SPI1_SelectSlave();// 发送初始化命令(参考SSD1306数据手册)OLED_WriteCmd(0xAE);  // 关闭显示OLED_WriteCmd(0xD5);  // 设置时钟分频OLED_WriteCmd(0x80);OLED_WriteCmd(0xA8);  // 设置多路复用率OLED_WriteCmd(0x3F);// ... 其他初始化命令省略OLED_WriteCmd(0xAF);  // 开启显示SPI1_DeselectSlave();
}// 向OLED发送命令(DC=0)
void OLED_WriteCmd(uint8_t cmd) {HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);  // DC=0(命令)SPI1_Transmit(cmd);
}// 向OLED发送数据(DC=1)
void OLED_WriteData(uint8_t data) {HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);    // DC=1(数据)SPI1_Transmit(data);
}
步骤3:显示字符(填充GRAM)
// 在指定位置显示一个字符(8x16字体)
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t ch) {// 计算字符在字库中的偏移(假设字库数组为font8x16)uint8_t *p = &font8x16[(ch - ' ') * 16];// 设置显示区域OLED_SetWindow(x, y, x + 7, y + 15);// 写入16字节数据(8列x16行)for (uint8_t i = 0; i < 16; i++) {OLED_WriteData(p[i]);}
}

5.3 案例3:SPI DMA传输(高速批量数据)

对于大数据量传输(如从Flash读取固件、向显示屏刷新图像),使用DMA可减少CPU干预,提高效率。

步骤1:配置DMA(STM32CubeMX)
  • 在SPI1配置中,“DMA Settings”→“Add”:
    • 接收:Stream选择“DMA1 Stream0”(SPI1_RX对应DMA1_Stream0),方向“Peripheral to Memory”;
    • 发送:Stream选择“DMA1 Stream3”(SPI1_TX对应DMA1_Stream3),方向“Memory to Peripheral”;
  • 模式选择“Normal”(单次传输),数据宽度“Byte”。
步骤2:DMA发送示例(刷新OLED全屏图像)
// 定义全屏图像缓冲区(128x64像素,共1024字节)
uint8_t OLED_GRAM[1024];// 使用DMA发送GRAM数据到OLED
void OLED_Refresh(void) {OLED_SetWindow(0, 0, 127, 7);  // 设置全屏显示区域HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);  // DC=1(数据)SPI1_SelectSlave();// DMA发送1024字节数据HAL_SPI_Transmit_DMA(&hspi1, OLED_GRAM, 1024);// 等待DMA发送完成(可选,根据需求)while (HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);SPI1_DeselectSlave();
}// DMA发送完成回调函数(可选)
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {if (hspi == &hspi1) {// 发送完成后的处理(如置标志位)}
}
步骤3:DMA接收示例(从Flash读取大数据)
// 从W25Q128读取1024字节数据到缓冲区(DMA方式)
void W25Q_ReadData_DMA(uint32_t addr, uint8_t *buf) {W25Q_WriteEnable();SPI1_SelectSlave();// 先发送读指令和地址(阻塞式)uint8_t cmd[4] = {0x03, (addr>>16)&0xFF, (addr>>8)&0xFF, addr&0xFF};HAL_SPI_Transmit(&hspi1, cmd, 4, 100);// DMA接收1024字节HAL_SPI_Receive_DMA(&hspi1, buf, 1024);// 等待接收完成while (HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);SPI1_DeselectSlave();
}

六、SPI高级特性与优化

6.1 中断方式传输

对于非阻塞场景,可使用SPI中断实现数据收发,避免CPU轮询等待。

6.1.1 中断配置(寄存器级)
void SPI1_IT_Init(void) {// 使能RXNE中断(接收非空)和TXE中断(发送空)SPI1->CR2 |= SPI_CR2_RXNEIE | SPI_CR2_TXEIE;// 配置NVIC(中断优先级)NVIC_EnableIRQ(SPI1_IRQn);NVIC_SetPriority(SPI1_IRQn, 2);
}// SPI1中断服务函数
void SPI1_IRQHandler(void) {uint8_t data;// 接收非空中断if (SPI1->SR & SPI_SR_RXNE) {data = SPI1->DR;  // 读取接收数据(清中断标志)// 处理接收数据(如存入缓冲区)}// 发送空中断if (SPI1->SR & SPI_SR_TXE) {// 发送下一字节(若有数据)if (tx_buf_index < tx_buf_len) {SPI1->DR = tx_buf[tx_buf_index++];} else {// 发送完成,关闭TXE中断SPI1->CR2 &= ~SPI_CR2_TXEIE;}}
}
6.1.2 HAL库中断使用
// 开启中断接收
HAL_SPI_Receive_IT(&hspi1, rx_buf, rx_len);// 中断接收完成回调
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {if (hspi == &hspi1) {// 处理接收数据rx_complete_flag = 1;}
}

6.2 单线模式与半双工

6.2.1 单线双向模式(仅用MOSI/MISO一根线)
  • 配置SPI_CR1->BIDIMODE=1(单线模式);
  • 发送时BIDIOE=1(输出使能),接收时BIDIOE=0(输入)。
6.2.2 半双工接收模式
  • 配置SPI_CR1->RXONLY=1(仅接收),此时MOSI线无效,数据从MISO接收。

6.3 CRC校验

SPI支持硬件CRC校验,用于确保数据传输的完整性,步骤如下:

  1. 使能CRC:SPI_CR1->CRCEN=1
  2. 配置CRC多项式(默认0x07,可通过SPI_CRCPR修改);
  3. 传输完成后,主机发送CRC值(CRCNEXT=1),从机比较接收的CRC与本地计算值,若不匹配则置CRCERR标志。

七、常见问题与调试技巧

SPI通信失败是嵌入式开发中的常见问题,多数源于时序不匹配、硬件连接错误或配置参数错误,以下是排查思路与解决方案。

7.1 通信失败的核心排查点

7.1.1 时序与模式错误
  • 现象:接收数据全为0xFF或乱码,发送后从机无响应;
  • 原因:主机与从机的CPOL/CPHA不匹配,或波特率过高(从机不支持);
  • 排查
    • 用示波器测量SCK和MOSI波形,确认CPOL(空闲电平)和CPHA(采样边沿)是否与从机一致;
    • 降低波特率(如先尝试1Mbps),排除速率不支持问题。
7.1.2 引脚连接错误
  • 现象:无任何数据传输,SCK线无波形;
  • 原因
    • SCK/MOSI引脚未配置为复用功能(仍为普通GPIO);
    • 引脚接反(如MOSI接MISO);
    • NSS未拉低(从机未选中);
  • 排查
    • 用万用表测量NSS引脚电平,确认传输时为低电平;
    • 检查SPI初始化代码,确保SPI_CR1->SPE=1(SPI已使能)。
7.1.3 数据位与格式错误
  • 现象:接收数据错位(如0x55变成0xAA);
  • 原因
    • 数据位长度不匹配(主机8位,从机16位);
    • 高位/低位顺序反了(MSB/LSB设置错误);
  • 排查:在从机数据手册中确认数据格式,修改DFFLSBFIRST参数。
7.1.4 中断与DMA配置错误
  • 现象:DMA传输无数据,或中断不触发;
  • 原因
    • DMA通道选择错误(如SPI1_TX对应DMA1_Stream3,而非Stream0);
    • 中断优先级被更高优先级中断抢占;
    • DMA传输长度超过缓冲区大小;
  • 排查:用HAL_DMA_GetState()查看DMA状态,检查NVIC中断使能情况。

7.2 调试工具与方法

  1. 示波器/逻辑分析仪

    • 测量SCK、MOSI、MISO、NSS四线波形,对比从机数据手册的时序图;
    • 确认SCK频率是否正确(如18Mbps对应周期约55.5ns);
    • 检查NSS是否在传输期间保持低电平。
  2. 最小系统验证

    • 用“回环测试”验证SPI外设是否正常:将MOSI与MISO短接,主机发送数据后检查是否接收相同数据;
    • 排除从机问题:先确保主机SPI自身工作正常,再连接外设。
  3. 分步调试

    • 先实现基础收发功能(如读取从机ID),再调试复杂功能(如页编程、DMA传输);
    • 在关键步骤添加printf输出(通过UART),打印寄存器状态(如SPI_SR的BSY、RXNE位)。
  4. 参考官方例程

    • STM32Cube固件包中提供了SPI示例(如STM32Cube_FW_F1_V1.8.4\Examples\SPI),可对比配置差异。

7.3 高速传输优化技巧

  • 缩短信号线长度:高速下(>10Mbps),信号线长度控制在5cm以内,避免信号反射;
  • 使用DMA+中断组合:大数据量传输用DMA,小数据量用中断,平衡效率与实时性;
  • 关闭CRC校验:若对数据完整性要求不高,可禁用CRC以减少传输延迟;
  • 批量传输:减少NSS切换次数(如一次选中从机后传输多帧数据),避免频繁使能/禁用从机。

八、总结与扩展

SPI作为一种高速同步通信协议,在STM32与外设的交互中扮演着重要角色。本文从协议基础到实战案例,系统讲解了SPI的工作原理、STM32配置方法及调试技巧,核心要点包括:

  • SPI的4种工作模式由CPOL和CPHA决定,通信双方必须一致;
  • STM32 SPI外设支持主/从模式、中断/DMA传输,配置时需注意时钟源与引脚复用;
  • 硬件设计需关注电平匹配、信号线布局和NSS控制;
  • 实战中需根据外设特性(如Flash、OLED)调整命令序列和传输方式。

未来学习可扩展至:

  • 多从机SPI网络的冲突处理;
  • 低功耗模式下的SPI唤醒机制;
  • 与其他协议(如I2C、UART)的混合通信系统设计;
  • 高速SPI(>50Mbps)的PCBLayout优化。

掌握SPI不仅是嵌入式开发的基础技能,更是理解同步通信原理的关键。通过不断实践与调试,可逐步提升对SPI协议的理解与应用能力。

附录:常用代码片段

  1. SPI回环测试(验证外设功能)
uint8_t SPI1_LoopbackTest(void) {uint8_t tx = 0xAA, rx;// 短接MOSI和MISOSPI1_SelectSlave();rx = SPI1_TransmitReceive(tx);SPI1_DeselectSlave();return (rx == tx) ? 0 : 1;  // 0=成功,1=失败
}
  1. SPI错误处理
void SPI1_ClearError(void) {// 清除错误标志(OVR、MODF、CRCERR)if (SPI1->SR & SPI_SR_OVR) {(void)SPI1->DR;  // 读DR清除OVR}if (SPI1->SR & SPI_SR_MODF) {SPI1->CR1 &= ~SPI_CR1_SPE;  // 禁用SPISPI1->CR1 |= SPI_CR1_SPE;   // 重新使能}if (SPI1->SR & SPI_SR_CRCERR) {SPI1->SR &= ~SPI_SR_CRCERR; // 清除CRC错误}
}
  1. 多从机管理
// 从机列表(NSS引脚)
#define SLAVE1_NSS_PIN GPIO_PIN_4
#define SLAVE2_NSS_PIN GPIO_PIN_5// 选择指定从机
void SPI_SelectSlave(uint16_t pin) {// 先释放所有从机HAL_GPIO_WritePin(GPIOA, SLAVE1_NSS_PIN | SLAVE2_NSS_PIN, GPIO_PIN_SET);// 选中目标从机HAL_GPIO_WritePin(GPIOA, pin, GPIO_PIN_RESET);
}

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

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

相关文章

TypeScript 接口全解析:从基础到高级应用

TypeScript 接口全解析&#xff1a;从基础到高级应用在 TypeScript 中&#xff0c;接口是定义数据结构和行为规范的强大工具&#xff0c;它能够显著提升代码的可读性、可维护性和类型安全性。本文将全面讲解 TypeScript 接口的相关知识点&#xff0c;从基础语法到高级特性&…

主存(DRAM)是什么?

主存&#xff08;DRAM&#xff09;是什么&#xff1f; 主存&#xff08;DRAM&#xff09;详解 主存&#xff08;Main Memory&#xff09; 通常由 DRAM&#xff08;Dynamic Random Access Memory&#xff0c;动态随机存取存储器&#xff09; 构成&#xff0c;是计算机系统中用于…

Python 机器学习核心入门与实战进阶 Day 6 - 模型保存与加载(joblib / pickle)

✅ 今日目标 掌握如何将训练好的模型持久化保存到文件熟悉两种主流保存方式&#xff1a;joblib 和 pickle加载模型并应用于新数据预测实现完整的“训练 → 保存 → 加载 → 预测”流程为后续部署做准备&#xff08;如 Flask、FastAPI&#xff09;&#x1f9f0; 一、模型保存工具…

【SigNoz部署安装】Ubuntu环境部署SigNoz:Docker容器化监控的全流程指南

文章目录前言1.关于SigNoz2.本地部署SigNoz3.SigNoz简单使用4. 安装内网穿透5.配置SigNoz公网地址6. 配置固定公网地址前言 在分布式架构主导的现代运维体系中&#xff0c;系统性能监控正面临范式变革的关键转折。当微服务架构遭遇服务雪崩、无服务器架构出现冷启动延迟等复杂…

NV298NV312美光固态闪存NW639NW640

美光固态闪存技术全景解析&#xff1a;从NV298到NW640的深度探索近年来&#xff0c;美光科技凭借其在3D NAND闪存技术上的持续突破&#xff0c;推出了多款备受市场关注的固态硬盘产品。本文将从技术评测、产品对比、市场趋势、用户反馈及应用场景等多个维度&#xff0c;深入剖析…

2025.07.04【服务器】|使用万兆网卡提升服务器间互联速度,实现快速数据传输

文章目录1. **万兆网卡概述**2. **为什么选择万兆网卡**3. **万兆网卡配置与安装**3.1 **安装网卡**3.2 **安装驱动程序**3.3 **检查网卡状态**4. **配置网络接口**4.1 **Linux 系统配置**4.2 **Windows 系统配置**5. **优化性能**5.1 **使用多线程传输**5.2 **开启 TCP/UDP 窗…

光伏发电量精准估算,提升投资效益

在光伏产业规模化发展进程中&#xff0c;准确估算光伏发电量是提升项目投资效益的关键环节。科学的发电量预测不仅能为项目可行性研究提供依据&#xff0c;更能在电站全生命周期内优化运营策略&#xff0c;实现投资回报最大化。基于多维度数据整合与智能算法构建的精准预测体系…

Linux的互斥锁、Linux的POSIX信号量(二值、计数)、RTOS的二值信号量

锁和信号量最大的区别就是:锁严格要求 “谁占用谁释放”,而信号量允许 “一个任务 / 线程释放,另一个任务 / 线程获取”。 特性互斥锁(Mutex)POSIX 信号量(Semaphore)初始状态初始为 “锁定”(PTHREAD_MUTEX_INITIALIZER),需显式获取(pthread_mutex_lock)。初始值可…

基于Java+SpringBoot 协同过滤算法私人诊所管理系统

源码编号&#xff1a;S607源码名称&#xff1a;基于SpringBoot5的协同过滤算法的私人诊所管理系统用户类型&#xff1a;双角色&#xff0c;患者、医生、管理员数据库表数量&#xff1a;15 张表主要技术&#xff1a;Java、Vue、ElementUl 、SpringBoot、Maven运行环境&#xff1…

什么是DINO?

DINO 是一个由 Meta AI (当时的 Facebook AI) 在 2021 年提出的自监督学习框架&#xff0c;其全称是 “self-DIstillation with NO labels”&#xff0c;直译为“无标签的自我蒸馏”。这个名字精准地概括了它的核心思想。 DINO 的出现是一个里程碑&#xff0c;因为它首次有力地…

如何在 Android Framework层面控制高通(Qualcomm)芯片的 CPU 和 GPU。

如何在 Android Framework层面控制高通&#xff08;Qualcomm&#xff09;芯片的 CPU 和 GPU。 参考&#xff1a;https://blog.csdn.net/YoungHong1992/article/details/117047839?utm_source%20%20uc_fansmsg 作为一名 Framework 开发者&#xff0c;您拥有系统级的权限&#…

程序员在线接单

十年Java全栈工程师在线接单Java程序代做&#xff0c;兼职接单&#xff0c;系统代做&#xff0c;二次开发&#xff0c;网站开发部署&#xff0c;项目合作&#xff0c;商业项目承包 全栈开发&#xff0c;支持定制各种管理系统、小程序 商用或个人使用等项目都接 服务二: Java调试…

Python 异步爬虫(aiohttp)高效抓取新闻数据

一、异步爬虫的优势 在传统的同步爬虫中&#xff0c;爬虫在发送请求后会阻塞等待服务器响应&#xff0c;直到收到响应后才会继续执行后续操作。这种模式在面对大量请求时&#xff0c;会导致大量的时间浪费在等待响应上&#xff0c;爬取效率较低。而异步爬虫则等待可以在服务器…

Jenkins Pipeline(二)

1.Pipeline 变量 在 Jenkins 管道&#xff08;Pipeline&#xff09;中&#xff0c;变量是一种非常有用的功能&#xff0c;它们可以帮助你在构建过程中存储和传递数据。Jenkins 管道支持多种方式来定义和使用变量&#xff0c;包括环境变量、脚本变量以及全局变量。 1.2 脚本变…

springsecurity02

提前打开Redis1&#xff09;通过内置的用户名和密码登录spring-boot-starter-security.jar2&#xff09;使用自定义用户名和密码登录UserDetailService自定义类实现UserDetailService接口&#xff0c;重写loadUserByUsername方法class UserDetailServiceImpl implements UserDe…

Apache组件遭大规模攻击:Tomcat与Camel高危RCE漏洞引发数千次利用尝试

漏洞态势分析帕洛阿尔托网络公司Unit 42团队最新研究报告显示&#xff0c;针对Apache Tomcat和Apache Camel关键漏洞的网络攻击正在全球激增。2025年3月披露的这三个远程代码执行&#xff08;RCE, Remote Code Execution&#xff09;漏洞——CVE-2025-24813&#xff08;Tomcat&…

Odoo 中国特色高级工作流审批模块研发

本文旨在为基于Odoo 18平台开发一款符合中国用户习惯的、功能强大的通用工作流审批模块提供一份全面的技术实现与产品设计方案。该模块的核心特性包括&#xff1a;为最终用户设计的图形化流程设计器、对任意Odoo模型的普适性、复杂的审批节点逻辑&#xff08;如会签、条件分支、…

unplugin-vue-components 最佳实践手册

&#x1f3a8; unplugin-vue-components 最佳实践手册 整理不易&#xff0c;收藏、点赞、关注支持下&#xff01;本文详细介绍了 unplugin-vue-components 插件的作用、配置方法、常用场景及与 unplugin-auto-import 配合使用的实战技巧&#xff0c;特别适合 Vue 3 Vite 项目。…

⿻ Java 学习日志 01

Java 运行机制&#xff1a; 原文件>编译器>字节码&#xff08;class后缀&#xff09;>JVM虚拟机>操作系统既有编译的过程也有解释的过程。JVM&#xff1a;Java Virture Machine/执行字节码的虚拟机&#xff0c;是实现跨平台——Java核心机制的核心。 JRE&…

基于Flutter的web登录设计

基于Flutter的web登录设计 1. 概述 本文档详细介绍了基于Flutter Web的智能家居系统登录模块的设计与实现。登录模块作为系统的入口&#xff0c;不仅提供了用户身份验证功能&#xff0c;还包括注册新用户的能力&#xff0c;确保系统安全性的同时提供良好的用户体验。 本文档…