文章目录
一、SPI简介
SPI,全称为 Serial Peripheral Interface(串行外设接口),是一种由Motorola公司开发的同步、全双工、主从式的串行通信协议。它因其简单性和高速性而被广泛应用于微控制器、传感器、存储器、SD卡、液晶屏等嵌入式设备之间的短距离通信。
二、通信特点
- 同步通信: 通信双方由一条时钟信号线 同步。所有数据传输都与时钟脉冲同步,发送和接收方无需预先约定波特率,避免了异步通信(如UART)的时序误差问题。
- 全双工: 数据可以在同一时刻双向传输。主设备在向从设备发送数据的同时,也可以接收从设备发来的数据。
- 主从模式: 通信由一个主设备 发起和控制。一个主设备可以连接多个从设备,通过片选信号来选择与哪一个从设备通信。
- 高速传输: 相比I2C和UART,SPI没有复杂的寻址和应答机制,因此可以实现非常高的数据传输速率,通常可达几十MHz甚至上百MHz。
三、信号线构成
一个SPI总线系统通常包含以下4条信号线:
| 信号线名称 | 全称 | 方向 | 描述 |
|---|---|---|---|
| SCK | Serial Clock | 主 → 从 | 串行时钟, 由主设备产生,用于同步数据位。 |
| MOSI | Master Out Slave In | 主 → 从 | 主设备数据输出,从设备数据输入。 |
| MISO | Master In Slave Out | 主 ← 从 | 主设备数据输入,从设备数据输出。 |
| CS/SS | Chip Select / Slave Select | 主 → 从 | 片选(或从设备选择),由主设备控制,低电平有效。 |
注:
- 每个从设备都需要一条独立的CS线。主设备通过将某条CS线拉至低电平,来激活对应的从设备。
- MISO和MOSI是从主设备的角度来命名的。
四、工作原理与数据交换流程
-
- 主设备初始化通信:
- 主设备产生SCK时钟。
- 主设备将目标从设备的CS引脚拉低,激活该从设备。
-
- 数据移位与传输:
- 在每一个SCK时钟周期内,发生一次完整的数据交换:
- 主设备通过MOSI线移出1位数据给从设备。
- 从设备通过MISO线移出1位数据给主设备。
- 经过8个或16个时钟周期后,主从设备就完成了一个字节(或一个字)的交换。
-
- 通信结束:
- 主设备将CS引脚拉高,结束本次通信。
关键理解: SPI的通信本质是主从设备之间交换移位寄存器中的数据。 你发送一个字节,必然会同时收到一个字节。这个收到的字节可能是有意义的传感器数据,也可能是无意义的哑元数据。
-
- 工作模式(时钟极性与相位):
SPI有4种工作模式,由CPOL 和CPHA 两个参数决定,用于定义时钟的极性和数据采样的边沿。
- 工作模式(时钟极性与相位):
| 模式 | CPOL (Clock Polarity) | CPHA (Clock Phase) | 描述 |
|---|---|---|---|
| 0 | 0 | 0 | 时钟空闲为低电平,数据在第一个时钟边沿(上升沿) 采样。 |
| 1 | 0 | 1 | 时钟空闲为低电平,数据在第二个时钟边沿(下降沿) 采样。 |
| 2 | 1 | 0 | 时钟空闲为高电平,数据在第一个时钟边沿(下降沿) 采样。 |
| 3 | 1 | 1 | 时钟空闲为高电平,数据在第二个时钟边沿(上升沿) 采样。 |
配置要点: 主设备和从设备必须使用相同的工作模式, 否则无法正确通信。模式0和模式3最为常用。
-
- 优缺点总结:
优点:
- 速度快: 远超I2C和UART。
- 协议简单: 无需复杂的地址和应答机制,硬件实现容易。
- 全双工: 数据传输效率高。
缺点:
- 需要较多引脚: 每个从设备都需要一条独立的片选线,当从设备多时,会占用大量主设备引脚。
- 没有硬件应答机制: 无法确认数据是否被成功接收。
- 没有流控制: 数据一旦开始传输,发送方就会以它设定的固定速度(由SCK频率决定)持续发送数据,而不管接收方是否已经准备好接收。
- 没有统一的设备地址方案, 它使用一根独立的片选线来选中从设备。想跟哪个从设备通信,就把对应的片选线拉低。
五、STM32CubeMX SPI配置
1. 新建工程
- 双击打开桌面下载好的STM32CubeMX,点击File–>New Project,或直接点击ACCEE TO MCU SELECTOR

- 在左边搜索栏里输入使用的芯片型号,右边选中并开始创建

2. 设置RCC时钟源
设置高/低速时钟源都由外部晶振产生。

3. 设置SPI参数
-
可选模式如下

-
模式选择全双工主机模式,不使能硬件NSS。如果引脚不使用默认引脚,可以通过点击右边的引脚视图修改,看是否有其他引脚支持。

-
SPI1参数

备注:
1. Basic Parameters(基本参数)
-
Frame Format (帧格式): 简单来说,Frame Format 定义了组成一次完整数据传输的“数据包”的结构和规则。即当“发送一个数据”时,这个数据究竟是如何被打包、在线上传输,以及如何被解析的。Motorola 模式: 这就是我们通常所说的标准 SPI 协议或模式 0,1,2,3。 它是由Motorola公司(现为 NXP)最初制定的,也是绝大多数 SPI 设备所使用的格式。使用 NSS 信号(片选)的下降沿标志帧开始,上升沿标志帧结束。TI 模式: 一种变体,由德州仪器定义,使用不同的帧同步方式。简单来说:99% 的情况下,都选择的是 Motorola 模式。 除非你明确知道要连接的从设备是 TI 模式的(例如某些 TI 的音频编解码器)。
-
Data Size (数据大小):8 bits: 一次传输 1 个字节。适用于绝大多数传感器、存储器(如读取一个寄存器地址、一个数据值)。16 bits: 一次传输 2 个字节。适用于某些需要连续传输16位数据的设备(如高精度ADC/DAC)。优先选择 8 bits, 除非从设备数据手册明确要求 16 位帧格式。
-
First Bit (首位): MSB First: 最高位先行。数据字节的最高位(Bit 7)最先在数据线上传输。LSB First: 最低位先行。数据字节的最低位(Bit 0)最先在数据线上传输。超过99%的SPI设备要求 MSB First。 这是事实上的工业标准。
2. Clock Parameters(时钟参数)
- Prescaler(for Baud Rate) (分频器(用于波特率)): 通过对 SPI 外设的输入时钟进行分频,来产生最终的 SPI 串行时钟 SCK。
- Baud Rate (波特率/时钟频率): 定义 SCK 时钟的频率。
- Clock Polarity(CPOL) (时钟极性): 定义 SCK 时钟在空闲时的电平,Low or High。
- Clock Phase(CPHA) (时钟相位): 定义数据在时钟的哪个边沿被采样,1 Edge or 2 Edge。
3. Advanced Parameters(高级参数)
- CRC Calculation (循环冗余校验计算): 是一种用于检测数据传输错误的硬件机制。Disable: 默认选项。不进行CRC校验,用于大多数常规应用。Enable: 使能硬件CRC计算。用于对数据可靠性要求高的场合。
- NSS Signal Type(从设备选择信号类型): 决定了片选信号的管理方式。Software: 软件控制NSS,使用普通GPIO引脚模拟片选,在绝大多数场景下应用,灵活简单。
4. 关于中断
4.1 SPI 中断概念
SPI 中断 是一种 异步事件处理机制。它允许 CPU 在启动 SPI 传输后不再等待,而是去执行其他任务。当 SPI 传输完成或发生特定事件时,SPI 外设会通过中断信号"通知" CPU,CPU 然后暂停当前工作,跳转到一个特定的函数(中断服务程序)来处理这个 SPI 事件,处理完后再返回原任务。
4.2 三种模式深度解析
| 模式 | 核心特点 | CPU 状态 | 函数后缀 |
|---|---|---|---|
| 阻塞模式 | 不开中断,原地等待 | 全程占用 | HAL_SPI_Transmit() |
| 中断模式 | 开中断,异步通知 | 被释放 | HAL_SPI_Transmit_IT() |
| DMA 模式 | 开DMA中断,硬件搬运 | 被释放 | HAL_SPI_Transmit_DMA() |
1. 阻塞模式(不开中断)
最简单的模式,适用于简单应用或初学者。
工作原理: 调用传输函数后,CPU 进入循环,不断检查 SPI 状态寄存器中的完成标志位(如 TXE 或 RXNE),直到传输完成,函数才返回。
适用场景:
①简单的单任务程序
②传输数据量小且不频繁
2. 中断模式(开中断)
平衡了效率与复杂性的模式,适用于多任务系统。
工作原理: 调用传输函数后,函数立即返回,CPU 被释放。SPI 外设在后台进行数据传输。当传输完成时,SPI 触发一个中断,CPU 跳转到对应的中断服务函数执行后续处理。
配置示例:
在 STM32CubeMX 中使能中断

适用场景:
①需要同时处理多个任务的系统
②中等数据量传输
③对系统响应速度有要求的应用
3. DMA 模式(开DMA中断)
最高效的模式,适用于大数据量传输。
工作原理: DMA 控制器在 SPI 和内存之间直接搬运数据,完全不需要 CPU 参与。仅在传输开始和结束时需要 CPU 干预。
配置示例:

适用场景:
①音频、图像数据传输
②高速数据采集
③液晶屏刷新
④任何大数据量SPI传输
5. 时钟配置
- 时钟源设置
默认时钟源是由内部RC振荡器产生,可通过图中按钮进行修改,外部晶振数值取决于实际电路板上的晶振大小.

提示:
- 这里用到的芯片的最大时钟频率是100MHz,有的芯片最大只有72MHz,实际最大频率可通过查看芯片数据手册确定。
- 时钟频率设置

①PLLM—PLL输入时钟分频系数,根据自己需要的系统时钟频率来进行修改
②PLLN—主PLL倍频系数(自动计算)
③PLLP—主PLL分频系数(自动计算)
④SYSCLK—系统时钟
⑤HCLK—AHB总线时钟,由系统时钟SYSCLK 分频得到,一般不分频,等于系统时钟
⑥APB1/APB2 Prescaler—APB1/APB2总线的预分频系数,可根据需要修改
6. 工程文件设置
- 工程设置
注:工程路径中不能有中文,否则会输出错误。
库文件要提前下载好,具体方法在 “【STM32CubeMX学习教程】——1.软件安装” 这一篇文章中有提到。

- 代码生成设置

- 点击右上角按钮生成代码,之后会出现下面的窗口,再点击打开工程即可在用keil查看工程代码。

- 编译成功

六、HAL库中常用的SPI相关代码
1. STM32CubeMX 自动生成的初始化函数
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 (模式0)
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件NSS控制
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 波特率预分频
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // Motorola模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁用CRC
hspi1.Init.CRCPolynomial = 10; // CRC多项式
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
2. 阻塞模式传输 (读取芯片ID)
/**
* @brief SPI1读写一个字节数据
* @param txdata : 要发送的数据(1字节)
* @retval 接收到的数据(1字节)
*/
uint8_t hal_spi1ReadWriteByte(uint8_t txdata)
{
uint8_t RxData = 0x00;
if(HAL_SPI_TransmitReceive(&hspi1, &txdata, &RxData, 1, 1000) != HAL_OK)
{
RxData = 0XFF;
}
return RxData; /* 返回收到的数据 */
}
/**
* @brief 读取芯片ID (W25Q128JV--制造商ID:FEh 设备ID:17h)
* @param 无
* @retval FLASH芯片ID
*/
unsigned short W25QXX_ReadID(void)
{
unsigned short ID = 0;
HAL_GPIO_WritePin(SPI1_CS_PORT,SPI1_CS_PIN,GPIO_PIN_RESET); //拉低片选引脚
//发送指令90h和24位地址000000h
hal_spi1ReadWriteByte(0x90);
hal_spi1ReadWriteByte(0x00);
hal_spi1ReadWriteByte(0x00);
hal_spi1ReadWriteByte(0x00);
//由SPI全双工和同步的特性决定,需要先发一组无效数据例如0xFF才能接收到数据
ID = hal_spi1ReadWriteByte(0xFF)<<8; //制造商ID:FEh
ID |= hal_spi1ReadWriteByte(0xFF); //设备ID:17h
HAL_GPIO_WritePin(SPI1_CS_PORT,SPI1_CS_PIN,GPIO_PIN_SET); //拉高片选
return ID; //ID:0xEF17
}
3. 中断模式传输
// 启动中断传输
uint8_t tx_data[10] = {0};
uint8_t rx_data[10];
HAL_SPI_TransmitReceive_IT(&hspi1, tx_data, rx_data, 10);
// 传输完成回调函数
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1) {
// 处理接收到的数据
printf("SPI transfer completed!\n");
}
}
// 错误回调函数
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
printf("SPI error occurred: 0x%lX\n", HAL_SPI_GetError(hspi));
}
4. DMA 模式传输
// 启动DMA传输
uint8_t large_tx_buffer[1000];
uint8_t large_rx_buffer[1000];
HAL_SPI_TransmitReceive_DMA(&hspi1, large_tx_buffer, large_rx_buffer, 1000);
// DMA传输完成回调(与中断模式使用相同的回调函数)
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1) {
printf("DMA transfer completed!\n");
}
}
以上就是本章的全部内容,如果对你有帮助,欢迎点赞支持,谢谢!
2095

被折叠的 条评论
为什么被折叠?



