前言:在前面我有文章介紹了關于單片機的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種模式:
值得注意的是:發送和接收必須使用相同的時鐘配置,否則會出現數據偏移或失真。
?詳細的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__ */
完結。。。?