【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

cpp 复制代码
/* 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 Configuration
    PB5     ------> SPI1_SCK
    PB6     ------> SPI1_MISO
    PB7     ------> 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 Configuration
    PB5     ------> SPI1_SCK
    PB6     ------> SPI1_MISO
    PB7     ------> 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

cpp 复制代码
/* 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__ */

完结。。。

相关推荐
TESmart碲视16 分钟前
HKS201-M24 大师版 8K60Hz USB 3.0 适用于 2 台 PC 1台显示器 无缝切换 KVM 切换器
单片机·嵌入式硬件·物联网·游戏·计算机外设·电脑·智能硬件
花落已飘2 小时前
STM32中实现shell控制台(shell窗口输入实现)
stm32·单片机·嵌入式硬件
花落已飘2 小时前
STM32中实现shell控制台(命令解析实现)
stm32·shell
没有钱的钱仔2 小时前
STM32低功耗模式全面指南
css·stm32·css3
牵牛老人4 小时前
Qt处理USB摄像头开发说明与QtMultimedia与V4L2融合应用
stm32·单片机·qt
宇钶宇夕5 小时前
针对工业触摸屏维修的系统指南和资源获取途径
单片机·嵌入式硬件·自动化
和风化雨5 小时前
stm32的三种开发方式
stm32·单片机·嵌入式硬件
kanhao1006 小时前
三态逻辑详解:单片机GPIO、计算机总线系统举例
单片机·嵌入式硬件
竹照煜_ysn8 小时前
STM32
stm32·单片机·嵌入式硬件
蓬荜生灰9 小时前
永磁无刷电机旋转原理
单片机·嵌入式硬件