前言
SPI 是嵌入式开发中最常用、最高速的同步串行通信协议之一,广泛用于驱动 OLED 屏、TFTLCD、W25QXX Flash、传感器等外设。
一、SPI 通信
1. 什么是 SPI?
SPI(Serial Peripheral Interface)串行外设接口 是一种高速、全双工、同步、主从式的通信总线。
- 串行:数据一位一位传输
- 全双工 :发送和接收可以同时进行
- 同步:主机提供统一时钟,收发双方严格同步
- 主从:STM32 一般作为主机,外设作为从机
2. SPI 4 根核心信号线
| 引脚 | 名称 | 功能 |
|---|---|---|
| SCLK | 串行时钟 | 主机产生,控制通信速度 |
| MOSI | 主机发→从机收 | 主机输出数据 |
| MISO | 从机发→主机收 | 主机接收数据 |
| CS/NSS | 片选信号 | 选中要通信的从设备 |
3. SPI 4 种工作模式(关键)
由 CPOL(时钟空闲电平) 和 CPHA(数据采样沿) 决定:
-
CPOL:时钟空闲时的电平
-
CPHA:数据在第几个时钟边沿采样
CPOL = High(空闲高)
CPHA = 2Edge(第二个边沿采样)
对应 SPI 模式 3,是屏幕、Flash 最常用模式。
4. SPI 通信特点
✅ 速度极快(最高可达系统时钟)✅ 全双工,收发同步✅ 协议简单,硬件自动完成收发✅ 适合短距离高速通信
二、工程文件说明
驱动代码分为两个文件:
- spi.c ------ SPI 驱动实现
- spi.h ------ 函数声明
包含 3 个核心函数:
SPI2_Init():SPI2 初始化SPI2_ReadWriteByte():SPI 读写一个字节SPI2_SetSpeed():动态修改 SPI 通信速度
三、代码
1. spi.h 头文件
#ifndef _SPI_H_ // 防止头文件被重复包含
#define _SPI_H_
#include "stm32f10x.h" // 包含STM32标准库
// 函数声明
void SPI2_Init(void); // SPI2初始化
u8 SPI2_ReadWriteByte(u8 dat); // SPI2读写一个字节
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler); // 设置SPI2速度
#endif
2. spi.c 驱动文件
#include "stm32f10x.h"
#include "spi.h"
/**********************************************************
* 函数名:SPI2_Init
* 功能 :SPI2初始化(GPIO + 通信模式 + 时序 + 速率)
* 备注 :PB13=SCLK PB14=MISO PB15=MOSI
**********************************************************/
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; // GPIO初始化结构体
SPI_InitTypeDef SPI_InitStructure; // SPI初始化结构体
//====================== 1. GPIO初始化 ======================
// SPI属于硬件外设,必须配置为 复用推挽输出(AF_PP)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
// SPI2的三个引脚:PB13、PB14、PB15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
// GPIO速度设置为50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化GPIOB
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 默认拉高三个SPI引脚(空闲状态)
GPIO_SetBits(GPIOB, GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
//====================== 2. SPI参数配置 ======================
// 设置为双线全双工模式(发送和接收同时进行)
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
// STM32作为主机(Master)
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
// 数据宽度:8位(最常用)
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
// 时钟空闲电平:高电平(模式3)
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
// 数据采样沿:第二个时钟沿(模式3)
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
// 片选信号:软件管理(自己用GPIO控制CS)
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
// 初始化波特率:256分频(最慢,最稳定)
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
// 数据传输格式:高位先行(MSB)
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
// CRC校验多项式(一般不用,默认7)
SPI_InitStructure.SPI_CRCPolynomial = 7;
// 根据配置初始化SPI2
SPI_Init(SPI2, &SPI_InitStructure);
// 最后使能SPI外设
SPI_Cmd(SPI2, ENABLE);
}
/**********************************************************
* 函数名:SPI2_ReadWriteByte
* 功能 :SPI2主机读写一个字节(全双工)
* 输入 :dat 要发送的数据
* 返回 :接收到的数据
* 备注 :SPI特性:发1字节 = 同时收1字节
**********************************************************/
u8 SPI2_ReadWriteByte(u8 dat)
{
u8 t = 0; // 超时计数
// 1. 等待发送缓冲区为空(TXE=1表示空)
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
{
t++;
if(t >= 200) return 0; // 超时防止程序卡死
}
// 2. 通过SPI2发送一个字节
SPI_I2S_SendData(SPI2, dat);
t = 0;
// 3. 等待接收缓冲区非空(RXNE=1表示收到数据)
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
{
t++;
if(t >= 200) return 0; // 超时判断
}
// 4. 读取并返回SPI2接收到的数据
return SPI_I2S_ReceiveData(SPI2);
}
/**********************************************************
* 函数名:SPI2_SetSpeed
* 功能 :动态修改SPI2通信速度
* 输入 :分频系数(2、4、8、16...256)
* 注意 :修改SPI配置必须先关闭SPI!
**********************************************************/
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
// 检查输入参数是否合法
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
// 【重要】修改配置前必须先关闭SPI
SPI_Cmd(SPI2, DISABLE);
// 清空CR1寄存器的BR[2:0]位(这三位是设置速度的)
SPI2->CR1 &= 0xFFC7;
// 设置新的分频值
SPI2->CR1 |= SPI_BaudRatePrescaler;
// 重新使能SPI
SPI_Cmd(SPI2, ENABLE);
}
四、代码核心重点讲解
1. 初始化函数 SPI2_Init
- GPIO 必须设为复用推挽输出
- 模式 3(CPOL=1,CPHA=1) 适合绝大多数屏幕和 Flash
- 初始化用低速 256 分频,保证稳定
- 主机模式、8 位数据、高位先行
2. 读写函数 SPI2_ReadWriteByte(SPI 灵魂)
SPI 是全双工:发送 = 接收流程:
- 等发送区空
- 发送数据
- 等接收完成
- 返回读到的数据
带超时处理,防止总线卡死。
3. 速度设置函数 SPI2_SetSpeed
- 修改 SPI 速度必须先关闭 SPI
- 先清零旧速度,再写入新速度
- 可随时切换速度:初始化慢,运行后改快
五、使用示例
// 初始化
SPI2_Init();
// 读写数据
u8 recv_data = SPI2_ReadWriteByte(0x80);
// 改高速(2分频)
SPI2_SetSpeed(SPI_BaudRatePrescaler_2);
六、总结
这套 SPI 代码是STM32 最标准、最通用、最稳定的硬件 SPI 主机驱动:✅ 带完整注释,新手能看懂✅ 带超时保护,不卡死✅ 可动态修改速度✅ 支持所有 SPI 外设(OLED、LCD、Flash、传感器)✅ 模式 3 配置,工业最常用