開工了,開工了,新年新氣象,軟件和硬件SPI方式都有,可以參考
一、SPI的通信原理
SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主設備和一個或多個從設備,需要至少4根線,事實上3根也可以(單向傳輸時)。也是所有基于SPI的設備共有的,它們是SDI(數據輸入),SDO(數據輸出),SCK(時鐘),CS(片選)。
- SDO – 主設備數據輸出,從設備數據輸入 對應MOSI master output slave input
- SDI – 主設備數據輸入,從設備數據輸出 對應MISO master input slave output
- SCLK – 時鐘信號,由主設備產生
- CS – 從設備使能信號,由主設備控制
CS: 其中CS是控制芯片是否被選中的,也就是說只有片選信號為預先規定的使能信號時(高電位或低電位),對此芯片的操作才有效,這就允許在同一總線上連接多個SPI設備成為可能。
SDI/SDO/SCLK: 通訊是通過數據交換完成的,這里先要知道SPI是串行通訊協議,也就是說數據是一位一位的傳輸的。這就是SCK時鐘線存在的原因,由SCK提供時鐘脈沖,SDI,SDO則基于此脈沖完成數據傳輸。數據輸出通過 SDO線,數據在時鐘上升沿或下降沿時改變,在緊接著的下降沿或上升沿被讀取。完成一位數據傳輸,輸入也使用同樣原理。這樣,在至少8次時鐘信號的改變(上沿和下沿為一次),就可以完成8位數據的傳輸。
要注意的是,SCK信號線只由主設備控制,從設備不能控制信號線。同樣,在一個基于SPI的設備中,至少有一個主控設備。
這樣傳輸的特點:這樣的傳輸方式有一個優點,與普通的串行通訊不同,普通的串行通訊一次連續傳送至少8位數據,而SPI允許數據一位一位的傳送,甚至允許暫停,因為SCK時鐘線由主控設備控制,當沒有時鐘跳變時,從設備不采集或傳送數據,也就是說,主設備通過對SCK時鐘線的控制可以完成對通訊的控制。SPI還是一個數據交換協議:因為SPI的數據輸入和輸出線獨立,所以允許同時完成數據的輸入和輸出。不同的SPI設備的實現方式不盡相同,主要是數據改變和采集的時間不同,在時鐘信號上沿或下沿采集有不同定義,具體請參考相關器件的文檔。
在點對點的通信中,SPI接口不需要進行尋址操作,且為全雙工通信,顯得簡單高效。在多個從設備的系統中,每個從設備需要獨立的使能信號,硬件上比I2C系統要稍微復雜一些。
最后,SPI接口的一個缺點:沒有指定的流控制,沒有應答機制確認是否接收到數據。
再看看 SPI 通訊的通訊時序,見圖 SPI 通訊時序。
二、SPI的通信模式
經常忘記SPI4種工作模式,學了忘了,現在記下方便以后查閱。在芯片資料上極性和相位一般表示為CPOL(Clock POLarity)和CPHA(Clock PHAse), 極性和相位組合成4種工作模式。
CPOL CPHA
MODE0 0 0
MODE1 0 1
MODE2 1 0
MODE3 1 1
CPOL: SPI空閑時的時鐘信號電平(1:高電平, 0:低電平)CPHA: SPI在時鐘第幾個邊沿采樣(1:第二個邊沿開始, 0:第一個邊沿開始)MODE0和MODE3最常用。
我們結合實例分析SPI模塊通信,采用W25Q64,看手冊如何選擇通信模式
我們只能采用模式0和3,這是spi讀數據
/*
*********************************************************************************************************
* 函 數 名: bsp_spiRead1
* 功能說明: 從SPI總線接收8個bit數據。 SCK上升沿采集數據, SCK空閑時為高電平
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
uint8_t bsp_spiRead1(void)
{
#ifdef SOFT_SPI /* 軟件SPI */
uint8_t i;
uint8_t read = 0;
for (i = 0; i < 8; i++)
{
SCK_0();
bsp_spiDelay();
read = read << 1;
if (MISO_IS_HIGH())
{
read++;
}
SCK_1();
bsp_spiDelay();
}
return read;
#endif
#ifdef HARD_SPI /* 硬件SPI */
uint8_t read;
/* 等待發送緩沖區空 */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
/* 發送一個字節 */
SPI_I2S_SendData(SPI1, 0);
/* 等待數據接收完畢 */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/* 讀取接收到的數據 */
read = SPI_I2S_ReceiveData(SPI1);
/* 返回讀到的數據 */
return read;
#endif
}
這是寫數據
/*
*********************************************************************************************************
* 函 數 名: bsp_spiWrite0
* 功能說明: 向SPI總線發送一個字節。SCK上升沿采集數據, SCK空閑時為低電平。
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_spiWrite0(uint8_t _ucByte)
{
#ifdef SOFT_SPI /* 軟件SPI */
uint8_t i;
for(i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
MOSI_1();
}
else
{
MOSI_0();
}
bsp_spiDelay();
SCK_1();
_ucByte <<= 1;
bsp_spiDelay();
SCK_0();
}
bsp_spiDelay();
#endif
#ifdef HARD_SPI /* 硬件SPI */
/* 等待發送緩沖區空 */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
/* 發送一個字節 */
SPI_I2S_SendData(SPI1, _ucByte);
/* 等待數據接收完畢 */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/* 讀取接收到的數據 */
SPI_I2S_ReceiveData(SPI1);
#endif
}
/*
*********************************************************************************************************
* 函 數 名: bsp_spiRead0
* 功能說明: 從SPI總線接收8個bit數據。 SCK上升沿采集數據, SCK空閑時為低電平。
* 形 參: 無
* 返 回 值: 讀到的數據
*********************************************************************************************************
*/
uint8_t bsp_spiRead0(void)
{
#ifdef SOFT_SPI /* 軟件SPI */
uint8_t i;
uint8_t read = 0;
for (i = 0; i < 8; i++)
{
read = read<<1;
if (MISO_IS_HIGH())
{
read++;
}
SCK_1();
bsp_spiDelay();
SCK_0();
bsp_spiDelay();
}
return read;
#endif
#ifdef HARD_SPI /* 硬件SPI */
uint8_t read;
/* 等待發送緩沖區空 */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
/* 發送一個字節 */
SPI_I2S_SendData(SPI1, 0);
/* 等待數據接收完畢 */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/* 讀取接收到的數據 */
read = SPI_I2S_ReceiveData(SPI1);
/* 返回讀到的數據 */
return read;
#endif
}
具體資料在配套資料