SPI(Serial peripheral interface)即串行外圍設備接口,是由Motorola首先在其MC68HCxx系列單片機上定義的,基於高速全雙工總線的通訊協議。(又是高速,而且全雙工,確實強大)被廣泛應用於ADC、LCD等設備與MCU之間。
跟前面學習I2C、USART一樣,學習一種協議,還是從兩個層面分析:物理層和協議層。
1. SPI物理層
SPI通訊需要使用4條線:3條總線和1條片選
圖1
SPI還是遵循主從模式,3條總線分別是SCK、MOSI和MISO,片選線為nSS(低電平有效),SPI協議適用於一主多從的工作場景:
圖2
(1) nSS(Slave Select):片選信號線,用於選中SPI從設備。每個從設備獨立擁有這條nSS信號線,佔據主機的一個引腳。設備的其他總線是並聯到SPI主機的,即無論多少個從設備,都共同使用這3條總線。當從設備上的nSS引腳被置拉低時表明該從設備被主機選中。
(2) SCK(Serial Clock):時鐘信號線,通訊數據同步用。時鐘信號由通訊主機產生,它決定了SPI的通訊速率。
(3) MOSI(Master Ouput Slave Input):主機(數據)輸出/從設備(數據)輸入引腳,即這條信號線上傳輸從主機到從機的數據。
(4) MISO(Master Input Slave Ouput):主機(數據)輸入/從設備(數據)輸出引腳,即這條信號線上傳輸從機從到主機的數據主從機通過兩條信號線來傳輸數據,那麼自然是全雙工通訊的了。之前的I2C通訊,數據只在一條SDA線上傳輸,主從機數據交互只能採用半雙工。
2. SPI協議層
圖3
如上為SPI通訊時序圖,nSS、SCK、MOSI信號均由主機產生,MISO信號由從機產生。在nSS為低電平的前提下,MOSI和MISO信號才有效,在每個時鐘周期MOSI和MISO傳輸一位數據。
跟I2C通訊類似,SPI通訊也需要通訊的起始/結束信號,有效數據和同步時鐘。
2.1 通訊的起始/結束信號
圖中的nSS信號由高電平變為低電平即為SPI通訊的起始信號,反過來,nSS信號由低電平變為高電平即為SPI通訊的結束信號。這個可比I2C簡單得多吧。當從機檢測到自身的nSS引腳被拉低時就知道自己被主機選中,準備和主機進行通訊。
2.2 有效數據的採集
SPI通訊的數據採集是個相對複雜的環節,先不說其他,以上圖為例:
圖中紅色框框即為有效數據被採集的時間點,”CPOL = 0″所在的脈衝信號表示的是用於進行數據同步的SCK,MOSI和MISO線上的數據在每個SCK時鐘周期傳輸一位數據,注意,數據的輸入/輸出是可以同時進行的。
由圖可見,在SCK為奇數(更正:這裡應該是偶數)邊沿(在這裡該邊沿為下降沿)時,數據得到有效採樣,也就是說,在這個時刻,MISO和MOSI的數據有效,高電平表示數據1,低電平表示數據0,在其它時刻數據並無效,可以理解為為下一次MISO和MOSI的數據傳輸做準備。
數據在傳輸中,高位在先還是低位在先,SPI協議並無明確規定,但是數據要在主從機中正確傳輸,自然雙方要先約定好,一般會採用高位在先(MSB)方式傳輸。
這裡需要再提及的概念是時鐘極性(CPOL)和時鐘相位(CPHA)。
時鐘極性(CPOL)指通訊設備處於空閑狀態(SPI開始通訊前、nSS線無效)時,SCK的狀態。
CPOL = 0:SCK在空閑時為低電平
CPOL = 1:SCK在空閑時為高電平
1 | CPOL = 0:SCK在空閑時為低電平
2 | CPOL = 1:SCK在空閑時為高電平時鐘相位(CPHA)指數據的採樣時刻位於SCK的偶數邊沿採樣還是奇數邊沿採樣。
CPHA = 0:在SCK的奇數邊沿採樣
CPHA = 1:在SCK的偶數邊沿採樣
1 | CPHA = 0:在SCK的奇數邊沿採樣
2 |CPHA = 1:在SCK的偶數邊沿採樣那麼這樣說來,SPI的採樣時刻並非由上升沿/下降沿決定的。注意的是,在數據採樣時刻,MOSI和MOSI的電平為有效電平,數據不能在這個時刻進行切換,在非採樣時刻MOSI和MISO上的信號才能切換。
完整的時序圖如下:
圖4
圖5
所以說,SPI有4中工作模式:
圖6
更正:工作模式3的CPOL應為1。
注意要讓主機和從機需要在相同的工作模式下,這樣才可以實現正常通訊。
下面介紹用STM32庫函數實SPI通訊代碼。
#ifndef __SPI_H
#define __SPI_H
#include "stm32f10x.h"
/* ¶¨ÒåÈ«¾Ö±äÁ¿ */
void SPI2_Config(void);
void SPI2_SetSpeed(uint8_t Speed);
uint8_t SPI2_WriteReadData(uint8_t dat);
void SPI1_Config(void);
void SPI1_SetSpeed(uint8_t speed);
uint8_t SPI1_WriteReadData(uint8_t dat);
/****************************************************************************
* Function Name : SPI1_Config
* Description : ³õʼ»¯SPI2
* Input : None
* Output : None
* Return : None
****************************************************************************/
void SPI1_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
/* SPIµÄIO¿ÚºÍSPIÍâÉè´ò¿ªÊ±ÖÓ */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/* SPIµÄIO¿ÚÉèÖà */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); //PA5.6.7ÉÏÀ
/***************************************************************************/
/************************* ÉèÖÃSPIµÄ²ÎÊý ***********************************/
/***************************************************************************/
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//Ñ¡Ôñȫ˫¹¤SPIģʽ
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //Ö÷»úģʽ
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8λSPI
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //ʱÖÓÐü¿Õ¸ßµçƽ
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //ÔÚµÚ¶þ¸öʱÖӲɼ¯Êý¾Ý
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NssʹÓÃÈí¼þ¿ØÖÆ
/* Ñ¡Ôñ²¨ÌØÂÊÔ¤·ÖƵΪ256 */
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//´Ó×î¸ßλ¿ªÊ¼´«Êä
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Cmd(SPI1, ENABLE);
SPI_Init(SPI1, &SPI_InitStructure);
}
/****************************************************************************
* Function Name : SPI2_Config
* Description : ³õʼ»¯SPI2
* Input : None
* Output : None
* Return : None
****************************************************************************/
void SPI2_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
/* SPIµÄIO¿ÚºÍSPIÍâÉè´ò¿ªÊ±ÖÓ */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
/* SPIµÄIO¿ÚÉèÖà */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/***************************************************************************/
/************************* ÉèÖÃSPIµÄ²ÎÊý ***********************************/
/***************************************************************************/
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//Ñ¡Ôñȫ˫¹¤SPIģʽ
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //Ö÷»úģʽ
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8λSPI
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //ʱÖÓÐü¿Õ¸ßµçƽ
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //ÔÚµÚ¶þ¸öʱÖӲɼ¯Êý¾Ý
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NssʹÓÃÈí¼þ¿ØÖÆ
/* Ñ¡Ôñ²¨ÌØÂÊÔ¤·ÖƵΪ256 */
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//´Ó×î¸ßλ¿ªÊ¼´«Êä
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Cmd(SPI2, ENABLE);
SPI_Init(SPI2, &SPI_InitStructure);
}
/****************************************************************************
* Function Name : SPI1_SetSpeed
* Description : ÉèÖÃSPI1µÄ´«ÊäËÙ¶È¡£
* Input : ËٶȲ¨ÌØÂÊ·ÖÆµ
* Output : None
* Return : None
****************************************************************************/
void SPI1_SetSpeed(uint8_t speed)
{
SPI1->CR1 &= 0xFFC7;
SPI1->CR1 |= speed;
SPI_Cmd(SPI1, ENABLE);
}
/****************************************************************************
* Function Name : SPI2_SetSpeed
* Description : ÉèÖÃSPI2µÄ·ÖƵÊý£¬ÒԸıäSPI2µÄËÙ¶È.
* Input : Speed£º·ÖƵÊý
* Output : None
* Return : None
****************************************************************************/
void SPI2_SetSpeed(uint8_t Speed)
{
SPI2->CR1 &= 0xFFC7;
SPI2->CR1 |= Speed;
SPI_Cmd(SPI2,ENABLE);
}
/****************************************************************************
* Function Name : SPI1_WriteReadData
* Description : ʹÓÃSPI1дÈëÒ»¸ö×Ö½ÚÊý¾Ýͬʱ¶Áȡһ¸ö×Ö½ÚÊý¾Ý¡£
* Input : dat£ºÒªÐ´µÄ8λÊý¾Ý
* Output : None
* Return : ¶ÁÈ¡µ½µÄ8λÊý¾Ý
****************************************************************************/
uint8_t SPI1_WriteReadData(uint8_t dat)
{
uint16_t i = 0;
/* µ±·¢ËÍ»º³åÆ÷¿Õ */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
{
i++;
if(i > 10000)
{
return 0xFF;
}
}
/* ·¢ËÍÊý¾Ý */
SPI_I2S_SendData(SPI1, dat);
/* µÈ´ý½ÓÊÕ»º³åÆ÷Ϊ·Ç¿Õ */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/* ½«¶ÁÈ¡µ½µÄÊýÖµ·µ»Ø */
return SPI_I2S_ReceiveData(SPI1);
}
/****************************************************************************
* Function Name : SPI2_WriteReadData
* Description : ʹÓÃSPI2дÈëÒ»¸ö×Ö½ÚÊý¾Ýͬʱ¶Áȡһ¸ö×Ö½ÚÊý¾Ý¡£
* Input : dat£ºÐ´ÈëµÄÊý¾Ý
* Output : None
* Return : ¶ÁÈ¡µ½µÄÊý¾Ý
* * ¶Áȡʧ°Ü·µ»Ø0xFF
****************************************************************************/
uint8_t SPI2_WriteReadData(uint8_t dat)
{
uint16_t i = 0;
/* µ±·¢ËÍ»º³åÆ÷¿Õ */
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
{
i++;
if(i > 10000)
{
return 0xFF;
}
}
/* ·¢ËÍÊý¾Ý */
SPI_I2S_SendData(SPI2, dat);
/* µÈ´ý½ÓÊÕ»º³åÆ÷Ϊ·Ç¿Õ */
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
/* ½«¶ÁÈ¡µ½µÄÊýÖµ·µ»Ø */
return SPI_I2S_ReceiveData(SPI2);
}
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/222375.html
微信掃一掃
支付寶掃一掃