【STM32CubeMX学习教程】——11.SPI


一、SPI简介

SPI,全称为 Serial Peripheral Interface(串行外设接口),是一种由Motorola公司开发的同步、全双工、主从式的串行通信协议。它因其简单性和高速性而被广泛应用于微控制器、传感器、存储器、SD卡、液晶屏等嵌入式设备之间的短距离通信。

二、通信特点

  • 同步通信: 通信双方由一条时钟信号线 同步。所有数据传输都与时钟脉冲同步,发送和接收方无需预先约定波特率,避免了异步通信(如UART)的时序误差问题。
  • 全双工: 数据可以在同一时刻双向传输。主设备在向从设备发送数据的同时,也可以接收从设备发来的数据。
  • 主从模式: 通信由一个主设备 发起和控制。一个主设备可以连接多个从设备,通过片选信号来选择与哪一个从设备通信。
  • 高速传输: 相比I2C和UART,SPI没有复杂的寻址和应答机制,因此可以实现非常高的数据传输速率,通常可达几十MHz甚至上百MHz。

三、信号线构成

一个SPI总线系统通常包含以下4条信号线:

信号线名称全称方向描述
SCKSerial Clock主 → 从串行时钟, 由主设备产生,用于同步数据位。
MOSIMaster Out Slave In主 → 从主设备数据输出,从设备数据输入。
MISOMaster In Slave Out主 ← 从主设备数据输入,从设备数据输出。
CS/SSChip Select / Slave Select主 → 从片选(或从设备选择),由主设备控制,低电平有效。

注:

  • 每个从设备都需要一条独立的CS线。主设备通过将某条CS线拉至低电平,来激活对应的从设备。
  • MISO和MOSI是从主设备的角度来命名的。

四、工作原理与数据交换流程

    1. 主设备初始化通信:
    • 主设备产生SCK时钟。
    • 主设备将目标从设备的CS引脚拉低,激活该从设备。
    1. 数据移位与传输:
    • 在每一个SCK时钟周期内,发生一次完整的数据交换:
      • 主设备通过MOSI线移出1位数据给从设备。
      • 从设备通过MISO线移出1位数据给主设备。
    • 经过8个或16个时钟周期后,主从设备就完成了一个字节(或一个字)的交换。
    1. 通信结束:
    • 主设备将CS引脚拉高,结束本次通信。

关键理解: SPI的通信本质是主从设备之间交换移位寄存器中的数据。 你发送一个字节,必然会同时收到一个字节。这个收到的字节可能是有意义的传感器数据,也可能是无意义的哑元数据。

    1. 工作模式(时钟极性与相位):
      SPI有4种工作模式,由CPOLCPHA 两个参数决定,用于定义时钟的极性和数据采样的边沿。
模式CPOL (Clock Polarity)CPHA (Clock Phase)描述
000时钟空闲为低电平,数据在第一个时钟边沿(上升沿) 采样。
101时钟空闲为低电平,数据在第二个时钟边沿(下降沿) 采样。
210时钟空闲为高电平,数据在第一个时钟边沿(下降沿) 采样。
311时钟空闲为高电平,数据在第二个时钟边沿(上升沿) 采样。

配置要点: 主设备和从设备必须使用相同的工作模式, 否则无法正确通信。模式0和模式3最为常用。

    1. 优缺点总结:

优点:

  • 速度快: 远超I2C和UART。
  • 协议简单: 无需复杂的地址和应答机制,硬件实现容易。
  • 全双工: 数据传输效率高。

缺点:

  • 需要较多引脚: 每个从设备都需要一条独立的片选线,当从设备多时,会占用大量主设备引脚。
  • 没有硬件应答机制: 无法确认数据是否被成功接收。
  • 没有流控制: 数据一旦开始传输,发送方就会以它设定的固定速度(由SCK频率决定)持续发送数据,而不管接收方是否已经准备好接收。
  • 没有统一的设备地址方案, 它使用一根独立的片选线来选中从设备。想跟哪个从设备通信,就把对应的片选线拉低。

五、STM32CubeMX SPI配置

1. 新建工程

  1. 双击打开桌面下载好的STM32CubeMX,点击File–>New Project,或直接点击ACCEE TO MCU SELECTOR

Alt

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

Alt

2. 设置RCC时钟源

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

Alt

3. 设置SPI参数

  1. 可选模式如下
    Alt

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

  3. SPI1参数
    Alt

备注:
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 中使能中断
Alt

适用场景:
①需要同时处理多个任务的系统
②中等数据量传输
③对系统响应速度有要求的应用

3. DMA 模式(开DMA中断)

最高效的模式,适用于大数据量传输。

工作原理: DMA 控制器在 SPI 和内存之间直接搬运数据,完全不需要 CPU 参与。仅在传输开始和结束时需要 CPU 干预。

配置示例:
Alt

适用场景:
①音频、图像数据传输
②高速数据采集
③液晶屏刷新
④任何大数据量SPI传输

5. 时钟配置

  1. 时钟源设置
    默认时钟源是由内部RC振荡器产生,可通过图中按钮进行修改,外部晶振数值取决于实际电路板上的晶振大小.

Alt
提示:

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

Alt
①PLLM—PLL输入时钟分频系数,根据自己需要的系统时钟频率来进行修改
②PLLN—主PLL倍频系数(自动计算)
③PLLP—主PLL分频系数(自动计算)
④SYSCLK—系统时钟
⑤HCLK—AHB总线时钟,由系统时钟SYSCLK 分频得到,一般不分频,等于系统时钟
⑥APB1/APB2 Prescaler—APB1/APB2总线的预分频系数,可根据需要修改

6. 工程文件设置

  1. 工程设置
    注:工程路径中不能有中文,否则会输出错误。
    库文件要提前下载好,具体方法在 “【STM32CubeMX学习教程】——1.软件安装” 这一篇文章中有提到。

Alt

  1. 代码生成设置

Alt

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

Alt

  1. 编译成功
    Alt

六、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");
  }
}

以上就是本章的全部内容,如果对你有帮助,欢迎点赞支持,谢谢!

### 使用STM32CubeMX配置STM32F103C8T6工程 #### 1. 下载并安装STM32CubeMX 为了开始配置STM32F103C8T6的工程,首先需要下载并安装STM32CubeMX工具。该工具有助于快速生成初始化代码,并支持多种外设的配置[^1]。 #### 2. 创建新项目 打开STM32CubeMX后,点击“New Project”,在弹出的窗口中输入目标芯片型号 `STM32F103C8T6` 或者通过 Commercial Part Number 进行查找。找到对应型号后双击确认,进入下一步配置界面[^4]。 #### 3. 配置时钟树 (RCC) 在Pinout & Configuration页面下,切换到Clock Configuration选项卡,调整PLL设置以满足应用所需的系统频率。通常情况下,默认值即可适用于大多数场景,但如果需要更高的性能或者更低功耗,则需手动调节相关参数[^3]。 #### 4. 外设功能启用与引脚分配 根据实际需求开启必要的外设模块,比如USART、SPI、I2C等通信接口以及定时器等功能单元。对于特定的应用场合如矩阵键盘扫描,还需要合理规划GPIO端口作为行/列信号线[^2]。 #### 5. 中断及DMA控制器设定 如果某些操作依赖中断机制来提高效率或响应速度,则应在NVIC(嵌套向量中断控制器)部分激活相应事件触发源;另外当涉及到大量数据传输任务时可考虑利用DMA资源减轻CPU负担[^3]。 #### 6. 定义输出目录及相关属性 最后一步是在Project Management环节指定保存位置和文件夹命名规则(建议全部采用英文字符),同时选择合适的IDE集成环境链接方式——此处推荐选用Keil MDK-ARM完成后续编译调试流程[^3]。 ```c // 示例:简单的LED闪烁程序片段 #include "stm32f1xx_hal.h" void SystemClock_Config(void); static void MX_GPIO_Init(void); int main(void){ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while (1){ HAL_Delay(500); HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); } } /** * @brief 初始化GPIO用于控制LED灯状态变化 */ static void MX_GPIO_Init(void){ __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef gpio_InitStruct; gpio_InitStruct.Pin = GPIO_PIN_5; gpio_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; gpio_InitStruct.Pull = GPIO_NOPULL; gpio_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA,&gpio_InitStruct); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玥山山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值