【STM32筆記】HAL庫中的SPI傳輸(可利用中斷或DMA進行連續傳輸)
SPI 是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍裝置接口。是Motorola(摩托羅拉)首先在其MC68HCXX系列處理器上定義的。
SPI,是一種高速的,全雙工,同步的通信總線,并且在晶片的管腳上隻占用四根線,節約了晶片的管腳,同時為PCB的布局上節省空間,提供友善,主要應用在 EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信号處理器和數字信号解碼器之間。
SPI主從模式
SPI分為主、從兩種模式,一個SPI通訊系統需要包含一個(且隻能是一個)主裝置,一個或多個從裝置。提供時鐘的為主裝置(Master),接收時鐘的裝置為從裝置(Slave),SPI接口的讀寫操作,都是由主裝置發起。當存在多個從裝置時,通過各自的片選信号進行管理。
SPI是全雙工且SPI沒有定義速度限制,一般的實作通常能達到甚至超過10 Mbps
SPI信号線
SPI接口一般使用四條信号線通信:
SDI(資料輸入),SDO(資料輸出),SCK(時鐘),CS(片選)
MISO: 主裝置輸入/從裝置輸出引腳。該引腳在從模式下發送資料,在主模式下接收資料。
MOSI: 主裝置輸出/從裝置輸入引腳。該引腳在主模式下發送資料,在從模式下接收資料。
SCLK:串行時鐘信号,由主裝置産生。
CS/SS:從裝置片選信号,由主裝置控制。它的功能是用來作為“片選引腳”,也就是選擇指定的從裝置,讓主裝置可以單獨地與特定從裝置通訊,避免資料線上的沖突。
硬體上為4根線。
四線SPI可以同時發送和接收資料
另外,還有一種三線SPI,即SCLK、CS、DIO,通過DIO一條線實作MISO和MOSI的功能,三線SPI同時發送或接收
SPI協定可以一對多傳輸 拉低哪個CS就同哪個晶片通信
SPI工作模式
根據時鐘極性(CPOL)及相位(CPHA)不同,SPI有四種工作模式。
時鐘極性(CPOL)定義了時鐘空閑狀态電平:
CPOL=0為時鐘空閑時為低電平
CPOL=1為時鐘空閑時為高電平
時鐘相位(CPHA)定義資料的采集時間。
CPHA=0:在時鐘的第一個跳變沿(上升沿或下降沿)進行資料采樣。
CPHA=1:在時鐘的第二個跳變沿(上升沿或下降沿)進行資料采樣。
SPI通信的時序
傳輸一個位元組
如圖為傳輸一個24位的資料 在此期間片選SYNC一直為拉低的
SPI配置
這是一般情況的配置
SPI配置中設定資料長度為8bit,MSB先輸出分頻為64分頻,則波特率為125KBits/s。其他為預設設定。
Motorla格式,CPOL設定為Low,CPHA設定為第二個邊沿。不開啟CRC檢驗,NSS為軟體控制。
(CPOL=0,CPHA=1)
CRC根據裝置需求來
NSS片選這裡選擇的是軟體片選(GPIO設定為輸出,由GPIO控制拉高拉低) 之是以推薦這個配置 後面會詳細說明
CPOL和CPHA根據晶片來定
工作模式選擇全雙工
有主機模式全雙工/半雙工
從機模式全雙工/半雙工
隻接收主機模式/隻接收從機模式
隻發送主機模式
SPI函數
在stm32f1xx_hal_spi.h頭檔案中可以看到spi的操作函數。分别對應輪詢,中斷和DMA三種控制方式。
輪詢: 最基本的發送接收函數,就是正常的發送資料和接收資料(阻塞)
中斷: 在SPI發送或者接收完成的時候,會進入SPI回調函數,使用者可以編寫回調函數,實作設定功能(非阻塞)
DMA: DMA傳輸SPI資料(非阻塞)
利用SPI接口發送和接收資料主要調用以下兩個函數:
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//發送資料
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收資料
SPI發送資料函數:
參數:
*hspi: 選擇SPI1/2,比如&hspi1,&hspi2
*pData : 需要發送的資料,可以為數組
Size: 發送資料的位元組數,1 就是發送一個位元組資料
Timeout: 逾時時間,就是執行發送函數最長的時間,超過該時間自動退出發送函數
SPI接收資料函數:
參數:
*hspi: 選擇SPI1/2,比如&hspi1,&hspi2
*pData : 接收發送過來的資料的數組
Size: 接收資料的位元組數,1 就是接收一個位元組資料
Timeout: 逾時時間,就是執行接收函數最長的時間,超過該時間自動退出接收函數
SPI接收回調函數:
當SPI上接收出現了 CommSize個位元組的資料後,中斷函數會調用SPI回調函數:
使用者可以重新定義回調函數,編寫預定功能即可,在接收完成之後便會進入回調函數
另外,最常用又最友善的是:
此函數可以同時發送和接收
比如發送2個位元組而後又接收3個位元組,則Size=5(實際上發送5個位元組,在發送2個位元組後,開始接收3個位元組)
若要發送2個位元組的同時接收2個位元組,則Size=2
若要發送2個位元組,但在發送1個位元組後接收一個位元組,則Size=2
在這裡 發送和接收同時進行,根據需求 Size填入的值為時序的總長度
SPI連續傳輸
在HAL庫中,SPI的傳輸是不連續的
若是選擇硬體NSS,則每次發送一個位元組後,NSS都會拉高
是以我們選擇軟體NSS,這樣就可以在完成傳輸後手動拉高
另外,若CPHA設定為1edge,則預設開啟NSSP,在每次傳輸1個位元組後,都會有一段空閑,設定為2或關閉NSSP則沒有
如圖:
若是用阻塞的方式進行傳輸,則每傳輸完兩個位元組後會有一個空閑,如圖:
為了使每兩個位元組傳輸中不間隔(連續傳輸)
則使用HAL_SPI_TransmitReceive_IT或HAL_SPI_TransmitReceive_DMA
同時在cubemx中開啟中斷或DMA(普通模式,開啟TX和RX)
(其實說白了 DMA也算中斷的一種 DMA不經過CPU傳輸 發送完成以後也會進入DMA中斷回調函數)
由于這兩個函數為非阻塞 固在使用時要加上阻塞判斷
HAL_SPI_TransmitReceive_IT(hspi,pData,buf,x+y);
while(hspi->State!=HAL_SPI_STATE_READY);
Set_SPI_CS(hspi,GPIO_PIN_SET);
若不加 軟體片選會變成這樣:
SPI函數包裝如下:
/*!
* @brief 對SPI裝置進行發送和讀取
*
* @param [in] hspi: SPI_HandleTypeDef 變量位址
* [in] pData: 需要發送的資料變量位址
* [in] x: 發送資料個數
* [in] y: 讀取資料個數,最大為4,若大于4,則傳回0
* [in] us: 拉高CS後的延時時長
* [in] sync_flag: 同步标志
* 當sync_flag為true時,發送資料和讀取資料同時進行,片選始終拉低,接收的資料為發送x個資料以後接收的y個資料
* 當sync_flag為false時,發送資料和讀取資料分别進行,片選分兩次拉低,接收的資料為第二次片選拉低時的資料
*
* @return dat: SPI讀取資料傳回
*/
uint32_t SPI_Send_x_Read_y(SPI_HandleTypeDef *hspi, uint8_t *pData, uint8_t x,uint8_t y,uint8_t us,bool sync_flag)
{
Set_SPI_CS(hspi,GPIO_PIN_SET);
uint8_t buf[x+y];
memset(buf,0,sizeof(buf));
uint32_t dat=0;
if(y>4 || x+y==0)
{
return 0;
}
if(sync_flag)
{
Set_SPI_CS(hspi,GPIO_PIN_RESET);
if(pData!=NULL)
{
HAL_SPI_TransmitReceive_IT(hspi,pData,buf,x+y);
while(hspi->State!=HAL_SPI_STATE_READY);
Set_SPI_CS(hspi,GPIO_PIN_SET);
delay_us(us);
}
else
{
Set_SPI_CS(hspi,GPIO_PIN_SET);
delay_us(us);
return 0;
}
}
else
{
if(pData!=NULL && x!=0)
{
Set_SPI_CS(hspi,GPIO_PIN_RESET);
HAL_SPI_Transmit_IT(hspi,pData,x);
while(hspi->State!=HAL_SPI_STATE_READY);
Set_SPI_CS(hspi,GPIO_PIN_SET);
delay_us(us);
}
Set_SPI_CS(hspi,GPIO_PIN_RESET);
HAL_SPI_Receive_IT(hspi,buf,y);
while(hspi->State!=HAL_SPI_STATE_READY);
Set_SPI_CS(hspi,GPIO_PIN_SET);
delay_us(us);
x=0;
}
for(uint8_t i=0;i<y;i++)
{
dat|=buf[x+i]<<(8*(y-1-i));
}
Set_SPI_CS(hspi,GPIO_PIN_SET);
return dat;
}
連續傳輸後的時序如圖:
軟體片選中的拉高延遲50us,是為了滿足有的裝置對片選拉高時長的要求 50us可以滿足大多數裝置了
另外,傳輸完成的拉高也可以放在IT和DMA的回調中去,但是回調也是非阻塞的,若是兩次資料間隔時間長,則可以這樣使用,這樣就可以壓縮CS的時間。但如果兩次資料間隔很短,就要按剛剛說的軟體片選拉高後給延時,如果用回調的話,延時部分會被壓縮,原本延時50us,可能隻能延時40us,是以盡量不用這個。
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
Set_SPI_CS(hspi,GPIO_PIN_SET);
if (hspi == (&hspi2))
{
}
}