串行外設接口SPI(Serial Peripheral Interface)是由Motorola公司開發的一種通用數據總線。
????????在某些芯片上,SPI接口可以配置為支持SPI協議或者支持I2S音頻協議。 SPI接口默認工作在SPI方式,可以通過軟件把功能從SPI模式切換到I2S模式,具體需參考操作手冊
????????串行外設接口(SPI)允許芯片與外部設備以半/全雙工、同步、串行方式通信。此接口可以被配置成主模式,并為外部從設備提供通信時鐘(SCK)。接口還能以多主配置方式工作。
它可用于多種用途,包括使用一條雙向數據線的雙線單工同步傳輸,還可使用CRC校驗的可靠通信。
????????I2S也是一種3引腳的同步串行接口通訊協議。它支持四種音頻標準,包括飛利浦I2S標準, MSB和LSB對齊標準,以及PCM標準。它在半雙工通訊中,可以工作在主和從2種模式下。當它作為主設備時,通過接口向外部的從設備提供時鐘信號。
SPI特點
- 四根通信線:SCK、MOSI、MISO、SS
- 支持全雙工同步傳輸
- 支持總線掛載多設備(一主多從)
- 支持多主機模型,主或從操作
- 支持DMA
- 可配置8位/16位數據幀
- 時鐘頻率:f(PCLK)/(2, 4, 8, 16, 32, 64, 128, 256)
- 兼容I2S協議等
電路連接
- 所有SPI設備的SCK、MOSI、MISO、分別連接在一起。
- 主機另外引出多條SS控制線,分別接到各從機的SS引腳,需要找誰通信就置誰的SS為低電平,同一時間主機只能選擇一個從機。
- 輸出引腳配置為推挽輸出,輸入引腳配置為浮空或上拉輸入。
- 所有設備需要共地。
?SPI移位示意圖
?
SPI時序
起始條件:SS從高電平切換到地點哦
終止條件:SS從低電平切換到高電平
SPI模式
SPI有四種模式通過CPOL時鐘極性和CPHA時鐘相位來定義
- CPOL=0,表示當SCLK=0時處于空閑態,所以有效狀態就是SCLK處于高電平時
- CPOL=1,表示當SCLK=1時處于空閑態,所以有效狀態就是SCLK處于低電平時
- CPHA=0,在時鐘的第一個跳變沿(上升沿或下降沿)進行數據采樣。在第2個邊沿發送數據
- CPHA=1,在時鐘的第二個跳變沿(上升沿或下降沿)進行數據采樣。在第1個邊沿發送數據
?軟件SPI設置時序單元
數據采樣==移入數據
模式0
模式1
模式2
模式3
軟件SPI代碼
以模式0為例,只需對相位和極性修改即可得到其他模式
//SPI寫SS引腳電平
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);//根據BitValue,設置SS引腳的電平
}//SPI寫SCK引腳電平
void MySPI_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);//根據BitValue,設置SCK引腳的電平
}//SPI寫MOSI引腳電平
void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //根據BitValue,設置MOSI引腳的電平,BitValue要實現非0即1的特性
}//SPI讀MISO引腳電平
uint8_t MySPI_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //讀取MISO電平并返回
}//SPI初始化
void MySPI_Init(void)
{/*開啟時鐘*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //開啟GPIOA的時鐘/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //將PA4、PA5和PA7引腳初始化為推挽輸出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //將PA6引腳初始化為上拉輸入/*設置默認電平*/MySPI_W_SS(1); //SS默認高電平MySPI_W_SCK(0); //SCK默認低電平
}//SPI起始
void MySPI_Start(void)
{MySPI_W_SS(0); //拉低SS,開始時序
}//SPI終止
void MySPI_Stop(void)
{MySPI_W_SS(1); //拉高SS,終止時序
}//SPI交換傳輸一個字節,使用SPI模式0
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00; //定義接收的數據,并賦初值0x00,此處必須賦初值0x00,后面會用到for (i = 0; i < 8; i ++) //循環8次,依次交換每一位數據{MySPI_W_MOSI(ByteSend & (0x80 >> i)); //使用掩碼的方式取出ByteSend的指定一位數據并寫入到MOSI線MySPI_W_SCK(1); //拉高SCK,上升沿移出數據if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //讀取MISO數據,并存儲到Byte變量//當MISO為1時,置變量指定位為1,當MISO為0時,不做處理,指定位為默認的初值0MySPI_W_SCK(0); //拉低SCK,下降沿移入數據}return ByteReceive; //返回接收到的一個字節數據// for(i=0;i<8;i++)
// {
// MySPI_W_MOSI(ByteSend & 0x80);
// ByteSend<<=1;
// MySPI_W_SCK(1);
// if(MySPI_R_MISO()==1){ByteSend|=0x01;}
// MySPI_W_SCK(0);
// }
// return ByteSend;
}
硬件SPI
SPI采用高位先行的模式
SPI框圖
數據寄存器DR分為TDR和RDR
1.當需要發送數據時,第一個數據寫入TDR,當移位寄存器沒有數據進行移位時,TDR數據會立刻轉入移位寄存器開始移位。
轉入時刻會置狀態寄存器的TXE位1,表示發送寄存器空,當檢查TXE為1后,下一個數據便可寫入TDR等候。
2.移位寄存器檢測到有數據也會自動產生時鐘將數據移出,在移出的過程中MISO的數據也會移入。
3.一旦數據移出完成,數據移入也完成了,這是移位寄存器就會將數據整體轉出到RDR,
這時會置狀態寄存器RXNE為1,表示接收寄存器非空。當檢測到RXNE為1就需在下一個數據到來之前將數據讀出來,不然數據會被覆蓋。
SPI時序框圖
連續傳輸
模式三
非連續傳輸?
硬件SPI代碼
?
#include "stm32f10x.h" // Device header//SPI寫SS引腳電平,SS仍由軟件模擬
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);//根據BitValue,設置SS引腳的電平
}//SPI初始化
void MySPI_Init(void)
{/*開啟時鐘*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //開啟GPIOA的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //開啟SPI1的時鐘/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //將PA4引腳初始化為推挽輸出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //將PA5和PA7引腳初始化為復用推挽輸出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //將PA6引腳初始化為上拉輸入/*SPI初始化*/SPI_InitTypeDef SPI_InitStructure; //定義結構體變量SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //模式,選擇為SPI主模式SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,選擇2線全雙工SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //數據寬度,選擇為8位SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //先行位,選擇高位先行SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率分頻,選擇128分頻SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //SPI極性,選擇低極性SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //SPI相位,選擇第一個時鐘邊沿采樣,極性和相位決定選擇SPI模式0SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS,選擇由軟件控制SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多項式,暫時用不到,給默認值7SPI_Init(SPI1, &SPI_InitStructure); //將結構體變量交給SPI_Init,配置SPI1/*SPI使能*/SPI_Cmd(SPI1, ENABLE); //使能SPI1,開始運行/*設置默認電平*/MySPI_W_SS(1); //SS默認高電平
}//SPI起始
void MySPI_Start(void)
{MySPI_W_SS(0); //拉低SS,開始時序
}//SPI終止
void MySPI_Stop(void)
{MySPI_W_SS(1); //拉高SS,終止時序
}//SPI交換傳輸一個字節,使用SPI模式0
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET); //等待發送數據寄存器空SPI_I2S_SendData(SPI1, ByteSend); //寫入數據到發送數據寄存器,開始產生時序while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);//等待接收數據寄存器非空return SPI_I2S_ReceiveData(SPI1); //讀取接收到的數據并返回
}