單片機
返回首頁

STM32—SPI詳解

2021-09-02 來源:eefocus

一.什么是SPI

SPI是串行外設接口(Serial Peripheral Interface)的縮寫,SPI是一種高速、全雙工、同步通信的通信總線,被廣泛應用在ADC、LCD等與MCU的通信過程中,特點就是快。


二.SPI協議

就像IIC、串口一樣,SPI也有其通信協議,我們一般按照分層的思想來學習SPI的協議,主要分為物理層和協議層。


物理層

首先看一下SPI通信設備之間的常用連接方式,主機和從機之間通過三條總線和片選線組成:

在這里插入圖片描述

NSS:片選設備線,每個從機都有自己的一條單獨的總線與主機連接,此總線的作用就是為主機選擇對應的從機進行傳輸數據,每個從機與主機之間的NSS總線互不相干。SPI中規定通信以NSS信號線拉低為開始,拉高為結束。

SCK:時鐘信號線,因為SPI是同步通信,所以需要一根時鐘信號線來統一主機和從機之間的數據傳輸,只有在有效的時鐘信號下才能正常傳輸數據,不同設備支持的最高傳輸頻率可能不一樣,在傳輸過程中傳輸頻率受限于低速的一方。

MOSI:(Master Output, Slave Input),顧名思義,MOSI就是主機輸出/從機輸入,因為SPI是全雙工的通信總線,即主機和從機可以同時收發數據,這樣的話就需要倆條線同時分別負責:主->從和從->主這倆條傳輸線路。而MOSI就專門負責主機向從機傳輸數據。

MISO:(Master Input,, Slave Output),與MOSI恰恰相反,MISO專門負責從機向主機傳輸數據。


協議層

和IIC一樣,SPI協議層規定了傳輸過程中的起始信號和停止信號、數據有效性、時鐘同步、通訊模式,接下來依據通訊時序圖來剖析協議層的內容。


1.通訊時序圖

如圖所示是SPI的一種通信模式下的時序圖:

在這里插入圖片描述

所有的運作都是基于SCK時鐘線的,SCK對于SPI的作用就像心臟對于人體的作用,SCK為低電平就代表心臟停止跳動。


2.起始和停止信號

前面物理層說過,SPI通訊的起始和停止由NSS信號線控制,當NSS為低電平時代表起始信號,當NSS為高電平時代表停止信號。時序圖中1和6部分代表起始信號和停止信號。


3.數據有效性

SPI中使用MOSI和MISO來進行全雙工傳輸數據,SCK來同步數據傳輸,即MOSI和MISO同時工作,在時鐘信號線SCK為有效時對MOSI、MISO數據線進行采樣,采到的信息即為傳輸的信息。IIC中通訊中的數據是在SCL總線為高電平時對數據采樣,SPI中數據的采樣是在SCK的上升沿或下降沿時進行的。圖示模式中3和5部分就是對數據進行采樣的時刻,可以看出圖示中數據是在SCK的下降沿進行采樣的。MOSI和MISO的高低電平代表了1和0。


4.通訊模式

SPI有四種通訊模式,他們的主要依靠總線空閑時SCK的時鐘狀態和數據采樣時刻來區別。這里就涉及到時鐘極性CPOL和時鐘相位CPHA的知識。

時鐘極性CPOL:CPOL是指NSS總線空閑時SCK的電平信號,如果SCK為高電平,CPOL=1;SCK為低電平,CPOL=0。下面的這種情況CPOL=0.

在這里插入圖片描述

時鐘相位CPHA:CPHA是指數據的采樣時刻,SCK的信號可以看作方波,CPHA=0時會在SCK的奇數邊沿采樣;CPHA=1時會在SCK的偶數邊沿采樣。

如圖:NSS空閑時SCK為低電平,而且在SCk的下降沿(也就是第二個邊沿)采樣,所以這種通訊模式下CPOL=0,CPHA=1.

在這里插入圖片描述

四種通訊模式:所以,根據CPOL和CPHA的搭配可以得出四種不同的通訊模式,如下:

在這里插入圖片描述

三.STM32中的SPI

簡介

STM32中集成了專門用于SPI通訊的外設。支持最高的 SCK 時鐘頻率為 fpclk/2

(STM32F103 型號的芯片默認 fpclk1為 72MHz, fpclk2為 36MHz),完全支持 SPI 協議的 4 種模式,數據幀長度可設置為 8 位或 16 位,可設置數據 MSB 先行或 LSB 先行。它還支持雙線全雙工、雙線單向以及單線模式。其中雙線單向模式可以同時使用 MOSI 及 MISO 數據線向一個方向傳輸數據,可以加快一倍的傳輸速度。而單線模式則可以減少硬件接線,當然這樣速率會受到影響。


功能框圖

在這里插入圖片描述

STM32中SPI外設的功能框圖可以大體分為四部分,對應的1、2、3、4分別是:通訊引腳、時鐘控制邏輯、數據控制邏輯、整體控制邏輯,下面進行一一分析。


1.通訊引腳

STM32中有多個SPI外設,這些SPI的MOSI、MISO、SCK、NSS都有對應的引腳,在使用相應的SPI時必須配置這些對應的引腳,STM32中的三個SPI外設的引腳分布情況如下:

在這里插入圖片描述

根據他們的引腳分布知道SPI1是掛載在APB2總線上的,SPI2和SPI3掛載在APB1總線上,這掛載在不同的總線上的主要區別就是,APB1和APB2總線的時鐘頻率不同,導致三個SPI的通訊速率收到總線時鐘頻率的影響。而且SPI3的引腳的默認功能是下載,如果要使用SPI3,必須禁用這幾個口的下載功能。


2.時鐘控制邏輯

這一塊的內容主要是配置SCK的時鐘頻率和SPI的通訊模式(CPOL和CPHA)。

時鐘頻率的配置:

波特率發生器通過控制“控制寄存器CR1”中的BR[2:0]三個位來配置fpclk的分頻因子,對fpclk分頻后的頻率就是SCK的時鐘頻率,具體配置如下圖所示:

在這里插入圖片描述

(PS:fpclk為對應SPI掛載總線的時鐘頻率)

通訊模式的配置:

通過配置“控制寄存器CR”中的CPOL位和CPHA位將通訊模式配置為上文所說的四種模式之一。


3.數據控制邏輯

這部分主要控制數據的接收和發送以及數據幀格式和MSB/LSB先行,和串口通訊類似,SPI的收發數據也是通過緩沖區和移位寄存器來實現的。MOSI和MISO都與移位寄存器相連以便傳輸數據,下面具體說明一下主機發送數據和接收數據的流程。當發送完一幀數據的時候,“狀態寄存器 SR”中的“TXE 標志位”會被置 1,表

示傳輸完一幀,發送緩沖區已空;類似地,當接收完一幀數據的時候,“ RXNE

標志位”會被置 1,表示傳輸完一幀,接收緩沖區非空;

發送數據:

地址和數據總線會在相應的地址上取到要發送的數據,將數據放入發送緩沖區,當向外發送數據時,移位寄存器會以發送緩沖區為數據源,一位一位的將數據發送出去。

接收數據:

接收數據時,移位寄存器把數據線采樣到的數據一位一位的傳到接收緩沖區,再由總線讀取接收緩沖區中的數據。

數據幀格式:

通過配置“控制寄存器CR1”的“DFF為”可以控制數據幀格式為8位還是16位,即一次接收或發送數據的大小。

先行位:

通過配置“控制寄存器CR1”的“LSBFIRST 位”可選擇 MSB(最高有效位) 先行還是 LSB(最低有效位) 先行。


4.整體邏輯控制

在外設工作時,控制邏輯會根據外設的工作狀態修改“狀態寄存器(SR)”,我們只要讀取狀態寄存器相關的寄存器位,就可以了解 SPI 的工作狀態了。除此之外,控制邏輯還根據要求,負責控制產生 SPI 中斷信號、DMA 請求及控制NSS 信號線,不過NSS信號線我們時一般是連接GPIO口,通過軟件來控制電平輸出,從而產生起始信號和停止信號。


初始化結構體

庫函數編程中幾乎每一個外設的靈魂部分就是其初始化結構體了,初始化結構體中包含了外設運作的狀態、工作模式、對象等重要信息,配置好初始化結構體后,通過初始化函數將初始化結構體中的信息寫入相應的寄存器中。SPI外設的初始化結構體如下:


typedef struct

 {

 uint16_t SPI_Direction; /*設置 SPI 的單雙向模式 */

 uint16_t SPI_Mode; /*設置 SPI 的主/從機端模式 */

 uint16_t SPI_DataSize; /*設置 SPI 的數據幀長度,可選 8/16 位 */

 uint16_t SPI_CPOL; /*設置時鐘極性 CPOL,可選高/低電平*/

 uint16_t SPI_CPHA; /*設置時鐘相位,可選奇/偶數邊沿采樣 */

 uint16_t SPI_NSS; /*設置 NSS 引腳由 SPI 硬件控制還是軟件控制*/

 uint16_t SPI_BaudRatePrescaler; /*設置時鐘分頻因子, fpclk/分頻數=fSCK */

 uint16_t SPI_FirstBit; /*設置 MSB/LSB 先行 */

 uint16_t SPI_CRCPolynomial; /*設置 CRC 校驗的表達式 */

 } SPI_InitTypeDef;


這些結構體成員說明如下,其中括號內的文字是對應參數在 STM32 標準庫中定義的宏:

(1) SPI_Direction

本成員設置 SPI 的通訊方向,可設置為雙線全雙工(SPI_Direction_2Lines_FullDuplex),雙線只接(SPI_Direction_2Lines_RxOnly),單線只接收(SPI_Direction_1Line_Rx)、單線只發送模(SPI_Direction_1Line_Tx)。

(2) SPI_Mode

本成員設置 SPI 工作在主機模式(SPI_Mode_Master)或從機模式(SPI_Mode_Slave ),這兩個模式的最大區別為 SPI 的 SCK 信號線的時序, SCK 的時序是由通訊中的主機產生的。若被配置為從機模式, STM32 的 SPI 外設將接受外來的 SCK 信號。

(3) SPI_DataSize

本成員可以選擇 SPI 通訊的數據幀大小是為 8 位(SPI_DataSize_8b)還是 16 位

(SPI_DataSize_16b)。

(4) SPI_CPOL 和 SPI_CPHA

這兩個成員配置 SPI 的時鐘極性 CPOL 和時鐘相位 CPHA,這兩個配置影響到 SPI 的通訊模式,時鐘極性 CPOL 成員,可設置為高電平(SPI_CPOL_High)或低電平(SPI_CPOL_Low )。時鐘相位 CPHA 則可以設置為 SPI_CPHA_1Edge(在 SCK 的奇數邊沿采集數據) 或SPI_CPHA_2Edge (在 SCK 的偶數邊沿采集數據) 。

(5) SPI_NSS

本成員配置 NSS 引腳的使用模式,可以選擇為硬件模式(SPI_NSS_Hard )與軟件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片選信號由 SPI 硬件自動產生,而軟件模式則需要我們親自把相應的 GPIO 端口拉高或置低產生非片選和片選信號。實際中軟件模式應用比較多。

(6) SPI_BaudRatePrescaler

本成員設置波特率分頻因子,分頻后的時鐘即為 SPI 的 SCK 信號線的時鐘頻率。這個成員參數可設置為 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分頻。

(7) SPI_FirstBit

所有串行的通訊協議都會有 MSB 先行(高位數據在前)還是 LSB 先行(低位數據在前)的問題,而 STM32 的 SPI 模塊可以通過這個結構體成員,對這個特性編程控制。

(8) SPI_CRCPolynomial

這是 SPI 的 CRC 校驗中的多項式,若我們使用 CRC 校驗時,就使用這個成員的參數(多項式),來計算 CRC 的值。


初始配置函數

void SPI_Config(void)

{

/* 初始化SPI和相對應的GPIO口 */

SPI_InitTypeDef  SPI_InitStruct;

GPIO_InitTypeDef GPIO_InitStruct;

/* 打開SPI1和GPIOA的時鐘 */

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);

/* 將NSS配置為普通推挽模式 SCK、MISO、MOSI配置為復用推挽模式 */

GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;

GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;

GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;

GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);

GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;

GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;

GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;

GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);

GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;

GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;

GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;

GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);

GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;

GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;

GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;

GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);

/* 配置SPI為四分頻、SCK空閑高電平、偶數邊沿采樣、8位數據幀、MSB先行、軟件NSS、雙線全雙工模式,SPI外設為主機端 */

SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;

SPI_InitStruct.SPI_CPHA = SPIx_CPHA;

SPI_InitStruct.SPI_CPOL = SPIx_CPOL;

SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;

SPI_InitStruct.SPI_DataSize = SPIx_DataSize;

SPI_InitStruct.SPI_Direction = SPIx_Direction;

SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;

SPI_InitStruct.SPI_Mode = SPIx_Mode;

SPI_InitStruct.SPI_NSS = SPIx_NSS;

/* 將SPI外設配置信息寫入寄存器,并且使能SPI外設 */

SPI_Init(SPIx, &SPI_InitStruct);

SPI_Cmd(SPIx, ENABLE);

/* 拉高NSS */

SPI_NSS_Stop();

}


}


這段代碼中,把 STM32 的== SPI 外設配置為主機端,雙線全雙工模式,數據幀長度為 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引腳由軟件控制以及 MSB 先行模式。 代碼中把 SPI 的時鐘頻率配置成了 4 分頻==。 最后一個成員為 CRC 計算式,由于我們不需要 CRC 校驗,并沒有使能 SPI 的 CRC 功能,這時 CRC 計算式的成員值是無效的。賦值結束后調用庫函數 SPI_Init 把這些配置寫入寄存器,并調用 SPI_Cmd 數使能外設。


發送、接收一個字節

/* 發送一個幀數據,同時接收一個幀數據 */

uint8_t SPI_SendData( uint8_t data)

{

uint16_t timeout=0x2710;   //10,000

while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的狀態讀取可以隨時就行,這個不受SPI是否在傳輸數據的影響

if((timeout--)==0) return printf("發送等待失敗!n");


SPI_I2S_SendData(SPIx, data);

timeout=0x2710;          //10,000次循環無果后為失敗

while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)

if((timeout--)==0) return printf("接收等待失敗!n");

return SPI_I2S_ReceiveData(SPIx);

}


/* 讀取一個幀數據 */

uint16_t SPI_ReadData(void)

{

return SPI_SendData( 1);//此時發送的值可以為任意值

}


發送數據前要等待發送緩沖區為空,靠TXE標志判斷,所以開始的while循環是等待發送緩沖區為空,同時,等待接收緩沖區是否有數據,靠RXNE標志來判斷,把接收緩沖區的數據作為返回值返回。由于發送和接收是同時進行的,而且要接收一個數據時必須在有效的SCK下,而只有發送數據才能產生有效的SCK,所以接收數據的函數時在發送數據的函數的基礎上,將發送的數據設置為Dummy_Byte假數據來騙取有效的SCK。


頭文件

#ifndef __SPI_H

#define __SPI_H


#include "stm32f10x.h"


#define SPIx                        SPI1

#define SPI_Clock                   RCC_APB2Periph_SPI1

#define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA


/* SCK/MOSI/MISO都配置為復用推挽輸出,NSS由軟件控制配置為普通的推挽輸出 */

#define SPI_SCK_GPIO_PORT           GPIOA

#define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP

#define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz

#define SPI_SCK_GPIO_PIN            GPIO_Pin_5


#define SPI_MOSI_GPIO_PORT          GPIOA

#define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP

#define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz

#define SPI_MOSI_GPIO_PIN           GPIO_Pin_7


#define SPI_MISO_GPIO_PORT          GPIOA

#define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP

#define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz

#define SPI_MISO_GPIO_PIN           GPIO_Pin_6


#define SPI_NSS_GPIO_PORT          GPIOA

#define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP

#define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz

#define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因為串行FLASH的CS引腳是PA4,SPI的NSS要和串行的一致


/* 配置SPI信息 */

#define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分頻,SPI1掛載在APB2上,四分頻后波特率為18MHz

#define SPIx_CPHA                  SPI_CPHA_2Edge//偶數邊沿采樣

#define SPIx_CPOL                  SPI_CPOL_High//空閑時SCK高電平

#define SPIx_CRCPolynomial         7//不使用CRC功能,所以無所謂

#define SPIx_DataSize              SPI_DataSize_8b//數據幀格式為8位

#define SPIx_Direction             SPI_Direction_2Lines_FullDuplex

#define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行

#define SPIx_Mode                  SPI_Mode_Master//主機模式

#define SPIx_NSS                   SPI_NSS_Soft//軟件模擬


/***************************************************************************************/

#define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)

#define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)

#endif  /*__SPI_H*/


四.資源鏈接

附上野火有關SPI的教學視頻:

鏈接:https://pan.baidu.com/s/1b5IEq8pjo00945l4EMsDvA

提取碼:umek

進入單片機查看更多內容>>
相關視頻
  • TI 新一代 C2000? 微控制器:全方位助力伺服及馬達驅動應用

  • MSP430電容觸摸技術 - 防水Demo演示

  • 直播回放: Microchip Timberwolf? 音頻處理器在線研討會

  • 新唐 8051單片機教程

  • 基于靈動MM32W0系列MCU的指夾血氧儀控制及OTA升級應用方案分享

  • 基于靈動MM32SPIN系列MCU的無感FOC便攜冰箱應用方案分享

    相關電子頭條文章
萝卜大香蕉