当前位置: 首页 > news >正文

【STM32项目实战】一文了解单片机的SPI驱动外设功能

前言:在前面我有文章介绍了关于单片机的SPI外设CUBEMX配置,但是要想使用好SPI这个外设我们还必须对其原理性的时序有一个详细的了解,所以这篇文章就补充一下SPI比较偏向底层的时序性的逻辑。


1,SPI简介

SPI是MCU最常见的对外通信口之一,由摩托罗拉在上世纪80年代中开发,用于嵌入式系统中器件之间的短距离数据通信,标准模式使用四条信号线。目前常见的应用器件有:LCD模组、以太网模块、SPI串行Flash和很多传感器等,大部分SD卡都具有SPI操作模式

其采用主从模式(Master Slave)架构:支持多 slave 模式应用,般仅支持单 Master。时钟由 Master 控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB frst):SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几 Mbps 的水平。

总结一下关键特点:

  • 同步通信:通信双方共享一个时钟信号

  • 全双工传输:支持同时发送与接收

  • 速度快:常见支持几MHz甚至几十MHz

  • 多从机支持:主机通过CS片选控制多个从设备

2,SPI的物理接口

文章上面第一个章节我们有提到SPI的标准模式使用四条信号线,比如下面这个图,其中的CSS片选线如果只有一个从机就可以忽略不计(单从机也就是说可以使用三根线),如果有多个从机那么CSS片选线的数量等于从机的数量

 

主从机内部结构简化图: 

 

 3,SPI通信原理详解

按照时钟极性和相位(CPOL & CPHA)可以将SPI协议分成4种模式:

如果 CPOL=0 ,串行同步时钟的空闲状态为低电平;如果 CPOL=1 ,串行同步时钟的空闲状态为高电平。时钟相位 (CPHA) 能够配置用于选择两种不同的传输协议之一进行数据传输。如果 CPHA=0 ,在串行同步时钟的第一个跳变沿 ( 上升或下降 ) 数据被采样;如果 CPHA=1 ,在串行同步时钟的第二个跳变沿 ( 上升或下降 ) 数据被采样

值得注意的是:发送和接收必须使用相同的时钟配置,否则会出现数据偏移或失真。

 详细的4种模式的时序示意图:

4,MCU中的SPI外设结构

STM32 MCU 为例,SPI模块一般包括以下部分:

  • 寄存器控制:用于配置波特率、主从模式、CPOL/CPHA等参数

  • TX/RX 缓冲器:发送与接收使用各自的 FIFO

  • 状态寄存器:可判断是否发送完成、是否接收到数据等

  • 中断控制:可设置中断方式发送/接收

  • DMA支持:支持高速数据传输而不占用CPU

常用寄存器:

  • SPI_CR1:控制寄存器(如主从、CPOL、CPHA)

  • SPI_SR:状态寄存器(如TXE/RXNE位)

  • SPI_DR:数据寄存器(发送/接收)

当然,想了解MCU的SPI外设的特性,主要还是得看对应某一款芯片的参考手册。下面就以STM32F4的参考手册为例。

 SPI框图:

5,基于STM32G474的SPI配置示例

都是CUBEMX生成的,大家看看参考一下就可以了。。。

spi.c

/* USER CODE BEGIN Header */
/********************************************************************************* @file    spi.c* @brief   This file provides code for the configuration*          of the SPI instances.******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "spi.h"/* USER CODE BEGIN 0 *//* USER CODE END 0 */SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_tx;
DMA_HandleTypeDef hdma_spi1_rx;// #define SPI_DMA_BUFFER_SIZE 16// uint8_t spi_tx_buffer[SPI_DMA_BUFFER_SIZE];
// uint8_t spi_rx_buffer[SPI_DMA_BUFFER_SIZE];
/* spi1 init function */
void MX_SPI1_Init(void)
{/* USER CODE BEGIN spi1_Init 0 *//* USER CODE END spi1_Init 0 *//* USER CODE BEGIN spi1_Init 1 *//* USER CODE END spi1_Init 1 */hspi1.Instance = SPI1;hspi1.Init.Mode = SPI_MODE_MASTER;hspi1.Init.Direction = SPI_DIRECTION_2LINES;hspi1.Init.DataSize = SPI_DATASIZE_16BIT;hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;hspi1.Init.NSS = SPI_NSS_SOFT;hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 7;hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;if (HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN SPI1_Init 2 */}void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(spiHandle->Instance==SPI1){/* USER CODE BEGIN SPI1_MspInit 0 *//* USER CODE END SPI1_MspInit 0 *//* SPI1 clock enable */__HAL_RCC_SPI1_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_DMA1_CLK_ENABLE();/**SPI1 GPIO ConfigurationPB5     ------> SPI1_SCKPB6     ------> SPI1_MISOPB7     ------> SPI1_MOSI*/GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* USER CODE BEGIN SPI1_MspInit 1 *//* SPI1 interrupt Init */HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);HAL_NVIC_EnableIRQ(SPI1_IRQn);/* USER CODE END SPI1_MspInit 1 */}
}void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{if(spiHandle->Instance==SPI1){/* USER CODE BEGIN SPI1_MspDeInit 0 *//* USER CODE END SPI1_MspDeInit 0 *//* Peripheral clock disable */__HAL_RCC_SPI1_CLK_DISABLE();/**SPI1 GPIO ConfigurationPB5     ------> SPI1_SCKPB6     ------> SPI1_MISOPB7     ------> SPI1_MOSI*/HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);/* SPI1 interrupt Deinit */HAL_NVIC_DisableIRQ(SPI1_IRQn);/* USER CODE BEGIN SPI1_MspDeInit 1 *//* USER CODE END SPI1_MspDeInit 1 */}
}/* USER CODE BEGIN 1 */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{if (hspi == &hspi1){// printf("SPI1 DMA Transfer Completed!\r\n");}
}/* USER CODE END 1 */

spi.h

/* USER CODE BEGIN Header */
/********************************************************************************* @file    spi.h* @brief   This file contains all the function prototypes for*          the spi.c file******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __SPI_H__
#define __SPI_H__#ifdef __cplusplus
extern "C" {
#endif/* Includes ------------------------------------------------------------------*/
#include "main.h"/* USER CODE BEGIN Includes *//* USER CODE END Includes */extern SPI_HandleTypeDef hspi1;
extern DMA_HandleTypeDef hdma_spi1_tx;
extern DMA_HandleTypeDef hdma_spi1_rx;/* USER CODE BEGIN Private defines *//* USER CODE END Private defines */void MX_SPI1_Init(void);/* USER CODE BEGIN Prototypes *//* USER CODE END Prototypes */#ifdef __cplusplus
}
#endif#endif /* __SPI_H__ */

完结。。。 

相关文章:

  • Vue3 中用 canvas 封装抽奖转盘组件:设定中奖概率及奖项图标和名称
  • LeetCode:二叉树的中序遍历
  • 【SpringBoot】SpringBoot中使用AOP实现日志记录功能
  • [HOT 100] 2646. 最小化旅行的价格总和
  • 前景理论——AI与思维模型【95】
  • 量子跃迁:破解未来计算的“时空密码”​
  • 分库分表后复杂查询的应对之道:基于DTS实时性ES宽表构建技术实践
  • enum4linux:渗透测试中的Windows信息收割机!全参数详细教程!Kali Linux教程!
  • Cursor 被封解决方案
  • MySQL 常用函数分类
  • Leetcode 刷题记录 08 —— 链表第二弹
  • LeetCode 热题 100 279. 完全平方数
  • 【Spring Boot 注解】@SpringBootApplication
  • homebrew安装配置Python(MAC版)
  • “wsl --install -d Ubuntu-22.04”下载慢,中国地区离线安装 Ubuntu 22.04 WSL方法(亲测2025年5月6日)
  • 网络安全等级保护有关工作事项[2025]
  • 网工实验——静态路由与BFD联动
  • 如何将本地 Jar 包安装到 Maven 仓库(以 Aspose 为例)
  • vue3 computed方法使用详细讲解
  • MUSIQ ,MANIQA,CLIP-IQA,FID是什么指标,分别是如何计算的(图像恢复领域评价指标
  • 标普500指数连涨四日,大型科技股多数下跌
  • 上海黄浦江挡潮闸工程建设指挥部成立,组成人员名单公布
  • 上海制造佳品汇大阪站即将启幕,泡泡玛特领潮出海
  • 新任美国驻华大使庞德伟抵京履职,外交部回应
  • 晋级四强!WTA1000罗马站:郑钦文2比0萨巴伦卡
  • 为何选择上海?两家外企提到营商环境、人才资源……