【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 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

/* 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__ */

完结。。。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是星凡呢

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

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

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

打赏作者

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

抵扣说明:

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

余额充值