文章目錄
SPI介紹
SPI : Serial Peripheral interface ,即串行外圍設備接口。 SPI是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只佔用四根線,主要應用在 EEPROM(一種存儲器),FLASH,實時時鐘, AD 轉換器,還有數字信號處理器和數字信號解碼器之間。
SPI 主要特點: 可以同時發出和接收串行數據; 可以當作主機或從機工作; 提供頻率可
編程時鐘; 發送結束中斷標誌; 寫衝突保護; 總線競爭保護等。
SPI內部結構簡明圖
如圖,SPI 接口一般使用 4 條線通信:
- MISO :主設備數據輸入,從設備數據輸出。
- MOSI :主設備數據輸出,從設備數據輸入。
- SCLK: 時鐘信號,由主設備產生。
- CS :從設備片選信號,由主設備控制。
SPI接口框圖
字節的輸送
從圖中可以看出, 主機和從機都有一個串行移位寄存器,主機通過向它的 SPI 串行寄存器
寫入一個字節來發起一次傳輸。寄存器通過 MOSI 信號線將字節傳送給從機(從最高位開始,取代從機的最高位),從機也將自己的移位寄存器中的內容通過 MISO 信號線返回給主機(從最低位開始,取代主機的最低位)。這樣,兩個移位寄存器中的內容就被交換。
外設的寫操作和讀操作是同步完成的。如果只進行寫操作,主機只需忽略接收到的字節;反之,若主機要讀取從機的一個字節,就必鬚髮送一個空字節來引發從機的傳輸。
時鐘極性與相位
時鐘極性(CPOL)和相位(CPHA)可以進行配置。
時鐘極性(CPOL)對傳輸協議沒有重大的影響。(決定上升沿採集還是下降沿採集)
CPOL=0,串行同步時鐘的空閒狀態爲低電平。
CPOL=1,串行同步時鐘的空閒狀態爲高電平。
時鐘相位(CPHA)能夠配置用於選擇兩種不同的傳輸協議之一進行數據傳輸。
CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降)數據被採樣;
CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降)數據被採樣。
SPI 主模塊和與之通信的外設備時鐘相位和極性應該一致。
SPI特徵
- 3線全雙工同步傳輸
- 8或16位傳輸幀格式選擇
- 主或從操作(支持多主模式)
- 8個主模式波特率預分頻係數(最大爲fpcux/2)
- 從模式頻率 (最大爲fpcu/2)
- 主模式和從模式的快速通信
- 主模式和從模式下均可以由軟件或硬件進行NSS管理:主從操作模式的動態改變
- 可編程的時鐘極性和相位
- 可編程的數據順序,MSB在前或LSB在前
- 可觸發中斷的專用發送和接收標誌
- SPI總線忙狀態標誌
- 支持可靠通信的硬件CRC
-在發送模式下,CRC值可以被作爲最後一個字節發送
-在全雙工模式中對接收到的最後-一個字節自動進行CRC校驗 - 可觸發中斷的主模式故障、 過載以及CRC錯誤標誌
- 支持DMA功能的1字節發送和接收緩衝器:產生髮送和接受請求
STM32 SPI接口可配置爲支持SPI協議或者支持I2S音頻協議,默認是SPI模式。可以通過軟件切換到|2S方式。
從選擇(NSS)腳管理
Negative of Slave Select,也就是同步串行通訊的片選信號(低電平有效)
SS/CS(Slave Select/Chip Select):用於Master片選Slave,使被選中的Slave能夠被Master訪問;
NSS相當於主機的CS(自己理解的)
-
軟件NSS模式: 可以通過設置SPI _CR1寄存器的SSM位來使能這種模式。在這種模式下NSS引腳可以用作它用,而內部NSS信號電平可以通過寫SPI_CR1的SSI位來驅動
-
硬件NSS模式,分兩種情況
-NSS輸出被使能:當STM32 工作爲主SPI,並且NSS輸出已經通過SPI _CR2寄存器的SSOE位使能,這時NSS引腳被拉低,所有NSS引腳與這個主SPI的NSS引腳相連並配置爲硬件NSS的SPI設備,將自動變成從SPI設備。
此時,當一個SPI設備需要發送廣播數據,它必須拉低NSS信號,以通知所有其它的設備它是主設備;如果它不能拉低NSS,這意味着總線上有另外一個主設備在通信,這時將產生-一個
硬件失敗錯誤(Hard Fault)。
-NSS輸出被關閉:允許操作於多主環境。
部分狀態標誌
應用程序通過3個狀態標誌可以完全監控SPI總線的狀態。
發送緩衝器空閒標誌(TXE)
此標誌爲’1’時表明發送緩衝器爲空,可以寫下一個待發送的數據進入緩衝器中。當寫入SPI _DR
時,TXE標誌被清除。
接收緩衝器非空(RXNE)
此標誌爲’1’時表明在接收緩衝器中包含有效的接收數據。讀SPI數據寄存器可以清除此標誌。
忙(Busy)標誌
BSY標誌由硬件設置與清除(寫入此位無效果),此標誌表明SPI通信層的狀態。
配置過程
1.配置相關引腳的複用功能,使能SPlx時鐘
GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
2.初始化SPIx,設置SPlx工作模式
SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)
3.SPI傳輸數據
SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)//發送數據
SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)//接收數據
4.查看SPI傳輸狀態
SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)//類似上面的狀態標誌
相關結構體
typedef struct
{
uint16_t SPI_Direction;//通信方式, 半雙工,全雙工,以及串行發和串行收方式
uint16_t SPI_Mode;//主從模式
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;//時鐘極性
uint16_t SPI_CPHA;//時鐘相位
uint16_t SPI_NSS;//設置 NSS 信號由硬件(NSS 管腳)還是軟件控制
uint16_t SPI_BaudRatePrescaler;//波特率預分頻值
uint16_t SPI_FirstBit;//數據傳輸順序是先MSB 位還是先LSB 位
uint16_t SPI_CRCPolynomial;// CRC 校驗
}SPI_InitTypeDef;
相關配置
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;//相關結構體初始化
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB時鐘使能
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2時鐘使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15複用推輓輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
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_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步時鐘的空閒狀態爲高電平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //採樣串行同步時鐘的第二個跳變沿(上升或下降)數據
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS的軟硬件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //波特率預分頻
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //數據傳輸開始位
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI2, &SPI_InitStructure); //初始化SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外設
SPI2_ReadWriteByte(0xff);//啓動傳輸
}
//SPIx 讀寫一個字節(SPI想要讀必須寫)
//TxData:要寫入的字節
//返回值:讀取到的字節
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //檢查指定的SPI標誌位設置與否:發送緩存空標誌位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通過外設SPIx發送一個數據
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //檢查指定的SPI標誌位設置與否:接受緩存非空標誌位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通過SPIx最近接收的數據
}
以W25Q128爲例的讀寫數據
//pBuffer:數據存儲區
//Read/writeAddr:開始讀取的地址(24bit)
//NumByteToRead/write:要讀取/寫入的字節數(讀取最大65535,寫入不超過該頁的剩餘字節數)
void W25Q128_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16 i;
W25Q128_CS=0; //使能器件
SPI2_ReadWriteByte(W25X_ReadData); //發送讀取命令
SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //發送24bit地址
SPI2_ReadWriteByte((u8)((ReadAddr)>>8));
SPI2_ReadWriteByte((u8)ReadAddr);
for(i=0;i<NumByteToRead;i++)
{
pBuffer[i]=SPI2_ReadWriteByte(0XFF); //循環讀數
}
W25Q128_CS=1;
}
void W25Q128_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u16 i;
W25Q128_Write_Enable(); //SET WEL
W25Q128_CS=0; //使能器件
SPI2_ReadWriteByte(W25X_PageProgram); //發送寫頁命令
SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); //發送24bit地址
SPI2_ReadWriteByte((u8)((WriteAddr)>>8));
SPI2_ReadWriteByte((u8)WriteAddr);
for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循環寫數
W25Q128_CS=1; //取消片選
W25Q128_Wait_Busy(); //等待寫入結束
}