文章目录
- 前言
- [1. SPI协议基础](#1. SPI协议基础)
-
- [1.1 物理层特性](#1.1 物理层特性)
- [1.2 通信时序(时钟极性CPOL和相位CPHA)](#1.2 通信时序(时钟极性CPOL和相位CPHA))
- 常用模式
-
- [Mode 0](#Mode 0)
- [Mode 3](#Mode 3)
- [1.3 典型通信流程](#1.3 典型通信流程)
- [2. STM32F103RCT6的SPI硬件配置](#2. STM32F103RCT6的SPI硬件配置)
-
- [2.1 硬件连接](#2.1 硬件连接)
- [2.2 CubeMX配置](#2.2 CubeMX配置)
- [3. HAL库代码实现](#3. HAL库代码实现)
-
- [3.1 SPI初始化](#3.1 SPI初始化)
- [3.2 基本读写函数](#3.2 基本读写函数)
-
- [(1) 单字节读写](#(1) 单字节读写)
- [(2) 多字节连续传输](#(2) 多字节连续传输)
- [(3) 带片选控制的读写](#(3) 带片选控制的读写)
- [4. 软件模拟SPI(GPIO模拟)](#4. 软件模拟SPI(GPIO模拟))
-
- [4.1 初始化GPIO](#4.1 初始化GPIO)
- [4.2 模拟时序函数(Mode 0)](#4.2 模拟时序函数(Mode 0))
- [5. 常见问题与调试](#5. 常见问题与调试)
- [6. 完整示例:读取MPU9250的WHO_AM_I寄存器](#6. 完整示例:读取MPU9250的WHO_AM_I寄存器)
- 总结
前言
SPI(Serial Peripheral Interface)是一种高速、全双工的同步串行通信协议 ,广泛用于连接微控制器与Flash、传感器、显示屏等外设。以下是SPI协议的详细说明及在STM32F103RCT6上的代码实现。
1. SPI协议基础
1.1 物理层特性
四线制(标准SPI)
SCK
SCK(Serial Clock):时钟信号,由主机产生。
MOSI
MOSI(Master Out Slave In):主机输出,从机输入。
MISO
MISO(Master In Slave Out):从机输出,主机输入。
NSS/CS
NSS/CS(Slave Select):片选信号(低电平有效)。
三线制(半双工模式)
三线制(半双工模式):共用数据线(如某些传感器)。
通信模式
- 支持主从模式(STM32通常为主机)。
- 全双工或半双工通信。
1.2 通信时序(时钟极性CPOL和相位CPHA)
SPI有4种模式,由**CPOL(Clock Polarity)和CPHA(Clock Phase)**决定:
模式 CPOL CPHA 时钟空闲状态 数据采样边沿
0 0 0 低电平 上升沿采样
1 0 1 低电平 下降沿采样
2 1 0 高电平 下降沿采样
3 1 1 高电平 上升沿采样
常用模式
Mode 0
Mode 0(如Flash芯片)。
Mode 3
Mode 3(如ADXL345加速度计)。
1.3 典型通信流程
- 主机拉低NSS(选中从机)。
- 主机产生SCK时钟 ,通过MOSI发送数据 ,同时从机通过MISO返回数据。
- 通信结束后拉高NSS。
2. STM32F103RCT6的SPI硬件配置
STM32F103RCT6有2个SPI接口(SPI1、SPI2),支持主/从模式。以下以SPI1(PA5=SCK, PA6=MISO, PA7=MOSI)为例:
2.1 硬件连接
SPI信号 STM32引脚 说明
SCK PA5 时钟线
MOSI PA7 主机输出
MISO PA6 主机输入
NSS PA4 片选(软件控制)
GND 共地
2.2 CubeMX配置
- 启用SPI1(模式:Full-Duplex Master)。
- 配置引脚(PA5/PA6/PA7)。
- 设置参数:
Prescaler:时钟分频(如PCLK2/8,9MHz @72MHz主频)。
CPOL/CPHA:根据从机要求选择 (如Mode 0)。
Data Size:8位或16位 。
NSS:软件管理(Hardware NSS设为Disable)。
3. HAL库代码实现
3.1 SPI初始化
c
#include "stm32f1xx_hal.h"
SPI_HandleTypeDef hspi1;
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; // 8位数据
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0(Mode 0)
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制NSS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 9MHz
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // 禁用TI模式
if (HAL_SPI_Init(&hspi1) != HAL_OK) {
Error_Handler();
}
}
3.2 基本读写函数
(1) 单字节读写
c
// 写入1字节并读取1字节(全双工)
uint8_t SPI_ReadWriteByte(uint8_t data) {
uint8_t rx_data;
HAL_SPI_TransmitReceive(&hspi1, &data, &rx_data, 1, 100);
return rx_data;
}
(2) 多字节连续传输
c
// 写入多字节(如Flash芯片写命令)
void SPI_WriteBytes(uint8_t *pData, uint16_t size) {
HAL_SPI_Transmit(&hspi1, pData, size, 100);
}
// 读取多字节(如传感器数据)
void SPI_ReadBytes(uint8_t *pRxData, uint16_t size) {
HAL_SPI_Receive(&hspi1, pRxData, size, 100);
}
(3) 带片选控制的读写
c
void SPI_CS_Enable(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) {
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET); // 拉低片选
}
void SPI_CS_Disable(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) {
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET); // 拉高片选
}
// 示例:读取W25Q32 Flash的ID
uint32_t W25Q_ReadID(void) {
uint8_t cmd = 0x90; // 读ID命令
uint8_t id[4] = {0};
SPI_CS_Enable(GPIOA, GPIO_PIN_4); // 拉低CS
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
HAL_SPI_Receive(&hspi1, id, 3, 100); // 读取3字节ID
SPI_CS_Disable(GPIOA, GPIO_PIN_4); // 释放CS
return (id[2] << 16) | (id[1] << 8) | id[0];
}
4. 软件模拟SPI(GPIO模拟)
如果硬件SPI不可用 ,可用GPIO模拟时序 (适用于低速场景):
4.1 初始化GPIO
c
void SPI_GPIO_Init() {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// SCK=PA5, MOSI=PA7, MISO=PA6, CS=PA4
GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7 | GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // MISO为输入
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 初始状态:SCK和MOSI低电平,CS高电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5 | GPIO_PIN_7, GPIO_PIN_RESET);
}
4.2 模拟时序函数(Mode 0)
// 写入1字节并读取1字节
uint8_t SPI_SW_ReadWriteByte(uint8_t data) {
uint8_t rx_data = 0;
for (uint8_t i = 0; i < 8; i++) {
// 下降沿准备数据
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // SCK=0
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET); // MOSI
data <<= 1;
Delay_us(1);
// 上升沿采样数据
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // SCK=1
rx_data <<= 1;
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)) rx_data |= 0x01; // 读取MISO
Delay_us(1);
}
return rx_data;
}
5. 常见问题与调试
5.1 SPI通信失败原因
时钟模式不匹配
确保主机和从机的CPOL/CPHA设置一致(如Flash芯片通常用Mode 0)。
片选信号未控制
通信前拉低NSS,结束后拉高。
时钟频率过高
降低BaudRatePrescaler(如从SPI_BAUDRATEPRESCALER_2改为_8)。
硬件连接错误
检查MOSI/MISO是否接反,SCK是否正常输出。
5.2 逻辑分析仪抓取波形
使用Saleae或PulseView观察:
- SCK是否连续?
- MOSI/MISO数据 是否对齐时钟边沿?
- NSS 是否在传输期间保持低电平?
6. 完整示例:读取MPU9250的WHO_AM_I寄存器
c
uint8_t MPU9250_ReadID(void) {
uint8_t cmd = 0x75 | 0x80; // 读寄存器命令(0x80表示读)
uint8_t id;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS拉低
HAL_SPI_TransmitReceive(&hspi1, &cmd, &id, 1, 100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS拉高
return id; // 正确应返回0x71
}
总结
硬件SPI
硬件SPI:使用HAL库的HAL_SPI_Transmit/Receive高效可靠。
软件模拟SPI
软件模拟SPI:灵活性高,适合引脚受限或低速场景。
调试关键
调试关键:检查时钟模式、片选信号、逻辑分析仪波形 。
通过上述方法,可稳定实现STM32F103RCT6与SPI设备的通信。