STM32 SPI 实验全面解析
系统讲解 STM32 的 SPI 实验,用STM32F103ZET6 和 HAL 库,内容将围绕:
SPI 原理简介
HAL SPI 的关键函数及寄存器解释
实验流程 & 代码讲解
典型应用场景
错误排查方法
🧠 一、SPI 原理:我如何用自己的话教会你?
什么是 SPI?
SPI(Serial Peripheral Interface,串行外设接口)是一种全双工、主从结构、同步传输的串行通信协议。
简单说,SPI 就像一根高速的传送带,主机发数据、从机同步收,同时从机也能发数据回来。
SPI 通信线:
| 引脚 | 名称 | 方向(以主机为参考) | 说明 |
|---|---|---|---|
| SCK | 串行时钟线 | 主机输出 → 从机输入 | 提供数据同步时钟 |
| MOSI | 主出从入线 | 主机输出 → 从机输入 | 主机发送数据 |
| MISO | 主入从出线 | 从机输出 → 主机输入 | 从机发送数据 |
| NSS | 片选信号(可选) | 主机输出 → 从机输入 | 选中从设备,低电平有效 |
SPI 特点:
主机产生时钟,主机控制通信节奏
数据边沿对齐(CPOL/CPHA 控制)
通信速度快(MHz级别)
适用于短距离高速通信
🧪 二、实验内容描述
我们现在做一个 SPI主机 实验,STM32作为主机,通过 SPI1 发送一串数据给从设备(可以是另一个 MCU,或 SPI Flash 等)。
🔧 三、硬件连接示意
以 SPI1 为例:
| 信号 | 管脚(STM32F103ZET6) |
|---|---|
| SCK | PA5 |
| MISO | PA6 |
| MOSI | PA7 |
| NSS | PA4(手动GPIO控制) |
📚 四、HAL库函数讲解 + 寄存器关系
1. HAL_SPI_Init()
函数作用:初始化 SPI 结构体并配置 SPI 寄存器
HAL_SPI_Init(&hspi1);
| 参数 | 含义 |
|---|---|
| hspi1.Instance = SPI1 | 指定使用 SPI1 |
| hspi1.Init.Mode = SPI_MODE_MASTER | 主机模式 |
| hspi1.Init.Direction = SPI_DIRECTION_2LINES | 双线全双工 |
| hspi1.Init.DataSize = SPI_DATASIZE_8BIT | 每次传 8 bit |
| hspi1.Init.CLKPolarity = SPI_POLARITY_LOW | 空闲低电平 |
| hspi1.Init.CLKPhase = SPI_PHASE_1EDGE | 第一个边沿采样 |
| hspi1.Init.NSS = SPI_NSS_SOFT | NSS 手动控制 |
| hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16 | 分频控制速度 |
| hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB | 高位先发 |
| hspi1.Init.TIMode = SPI_TIMODE_DISABLE | 禁用 TI 模式 |
| hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE | 不用 CRC |
2. HAL_SPI_Transmit()
发送数据(阻塞)
HAL_SPI_Transmit(&hspi1, data, len, timeout);
data:要发的数据数组
len:数据长度
timeout:超时时间(单位:ms)
3. HAL_SPI_Receive()
接收数据(阻塞)
HAL_SPI_Receive(&hspi1, recv_buf, len, timeout);
4. HAL_SPI_TransmitReceive()
同时收发(全双工通信)
HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, len, timeout);
🧪 五、SPI主机发送实验 – 完整代码
1. SPI 初始化(CubeMX 生成或手写)
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;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT; // NSS由软件控制
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
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();
}
}
2. 主函数中测试:
uint8_t tx_data[] = {0xAA, 0xBB, 0xCC};
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // NSS = 0,开始通信
HAL_SPI_Transmit(&hspi1, tx_data, sizeof(tx_data), 1000); // 发送数据
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // NSS = 1,结束通信
📍 六、SPI寄存器背后做了什么?
| 寄存器 | 功能解释 |
|---|---|
| CR1 | SPI模式设置(主从、CPOL、CPHA、数据大小、使能等) |
| SR | 状态寄存器,判断 TXE(发送缓冲区空)/ RXNE(接收缓冲区非空) |
| DR | 数据寄存器,写入=发出,读取=接收 |
| CR2 | 中断、DMA 等扩展控制 |
🎯 七、典型应用
驱动外部 SPI Flash(W25Qxx)
与 OLED 屏通信
驱动 AD/DA 芯片
通信模块(如 NRF24L01)
🧰 八、常见问题排查
| 问题 | 原因 |
|---|---|
| 无法发送 | NSS没有拉低、SPI没初始化成功 |
| 收到全0或全1 | 没接收数据、MISO引脚浮空 |
| SPI总线卡死 | CPOL/CPHA 配置不一致 |
| 收发数据错位 | SPI模式配置不匹配 |
✅ 总结
SPI 是一种主从同步全双工协议
STM32 使用 SPI 的关键函数是 HAL_SPI_Init()、Transmit() 和 Receive()
SPI 的行为受寄存器 CR1 和 SR 控制
用 NSS 控制设备选通(GPIO 控制)
实验中 STM32 作为主机发数据给从机
作者:不如出家吧