1、SPI簡介
SPI 規定了兩個 SPI 設備之間通信必須由主設備 (Master) 來控制次設備 (Slave). 一個 Master 設備可以通過提供 Clock 以及對 Slave 設備進行片選 (Slave Select) 來控制多個 Slave 設備, SPI 協議還規定 Slave 設備的 Clock 由 Master 設備通過 SCK 管腳提供給 Slave 設備, Slave 設備本身不能產生或控制 Clock, 沒有 Clock 則 Slave 設備不能正常工作。
2、SPI特點
2.1、SPI控制方式
采用主-從模式(Master-Slave) 的控制方式。
SPI 規定了兩個 SPI 設備之間通信必須由主設備 (Master) 來控制次設備 (Slave). 一個 Master 設備可以通過提供 Clock 以及對 Slave 設備進行片選 (Slave Select) 來控制多個 Slave 設備, SPI 協議還規定 Slave 設備的 Clock 由 Master 設備通過 SCK 管腳提供給 Slave 設備, Slave 設備本身不能產生或控制 Clock, 沒有 Clock 則 Slave 設備不能正常工作。
2.2、SPI傳輸方式
采用同步方式(Synchronous)傳輸數據
Master 設備會根據將要交換的數據來產生相應的時鍾脈沖(Clock Pulse), 時鍾脈沖組成了時鍾信號(Clock Signal) , 時鍾信號通過時鍾極性 (CPOL) 和 時鍾相位 (CPHA) 控制着兩個 SPI 設備間何時數據交換以及何時對接收到的數據進行采樣, 來保證數據在兩個設備之間是同步傳輸的。
2.3、SPI數據交換
SPI數據交換框圖
上圖只是對 SPI 設備間通信的一個簡單的描述, 下面就來解釋一下圖中所示的幾個組件(Module):
SSPBUF,Synchronous Serial Port Buffer, 泛指 SPI 設備里面的內部緩沖區, 一般在物理上是以 FIFO 的形式, 保存傳輸過程中的臨時數據;
SSPSR, Synchronous Serial Port Register, 泛指 SPI 設備里面的移位寄存器(Shift Regitser), 它的作用是根據設置好的數據位寬(bit-width) 把數據移入或者移出 SSPBUF;
Controller, 泛指 SPI 設備里面的控制寄存器, 可以通過配置它們來設置 SPI 總線的傳輸模式。
SPI 設備間的數據傳輸之所以又被稱為數據交換, 是因為 SPI 協議規定一個 SPI 設備不能在數據通信過程中僅僅只充當一個 "發送者(Transmitter)" 或者 "接收者(Receiver)". 在每個 Clock 周期內, SPI 設備都會發送並接收一個 bit 大小的數據, 相當於該設備有一個 bit 大小的數據被交換了. 一個 Slave 設備要想能夠接收到 Master 發過來的控制信號, 必須在此之前能夠被 Master 設備進行訪問 (Access). 所以, Master 設備必須首先通過 SS/CS pin 對 Slave 設備進行片選, 把想要訪問的 Slave 設備選上. 在數據傳輸的過程中, 每次接收到的數據必須在下一次數據傳輸之前被采樣. 如果之前接收到的數據沒有被讀取, 那么這些已經接收完成的數據將有可能會被丟棄, 導致 SPI 物理模塊最終失效. 因此, 在程序中一般都會在 SPI 傳輸完數據后, 去讀取 SPI 設備里的數據, 即使這些數據(Dummy Data)在我們的程序里是無用的。
上面的過程轉為動畫
初始狀態
主機讀取一個bit過程
總結:
沒有讀和寫的說法,因為實質上每次SPI是主從設備在交換數據。也就是說,你發一個數據必然會收到一個數據;你要收一個數據必須也要先發一個數據。
2.4、SPI傳輸模式
上升沿、下降沿、前沿、后沿觸發。當然也有MSB和LSB傳輸方式.
3、工作機制
3.1、相關縮寫
SPI的極性Polarity和相位Phase,最常見的寫法是CPOL和CPHA,不過也有一些其他寫法,簡單總結如下:
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (時鍾)極性
(2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (時鍾)相位
(3) SCK=SCLK=SPI的時鍾
(4) Edge=邊沿,即時鍾電平變化的時刻,即上升沿(rising edge)或者下降沿(falling edge)
對於一個時鍾周期內,有兩個edge,分別稱為:
Leading edge=前一個邊沿=第一個邊沿,對於開始電壓是1,那么就是1變成0的時候,對於開始電壓是0,那么就是0變成1的時候;
Trailing edge=后一個邊沿=第二個邊沿,對於開始電壓是1,那么就是0變成1的時候(即在第一次1變成0之后,才可能有后面的0變成1),對於開始電壓是0,那么就是1變成0的時候;
3.2、CPOL極性
先說什么是SCLK時鍾的空閑時刻,其就是當SCLK在數發送8個bit比特數據之前和之后的狀態,於此對應的,SCLK在發送數據的時候,就是正常的工作的時候,有效active的時刻了。
先說英文,其精簡解釋為:Clock Polarity = IDLE state of SCK。
再用中文詳解:
SPI的CPOL,表示當SCLK空閑idle的時候,其電平的值是低電平0還是高電平1:
CPOL=0,時鍾空閑idle時候的電平是低電平,所以當SCLK有效的時候,就是高電平,就是所謂的active-high;
CPOL=1,時鍾空閑idle時候的電平是高電平,所以當SCLK有效的時候,就是低電平,就是所謂的active-low;
3.3、CPHA相位
首先說明一點,capture strobe = latch = read = sample,都是表示數據采樣,數據有效的時刻。相位,對應着數據采樣是在第幾個邊沿(edge),是第一個邊沿還是第二個邊沿,0對應着第一個邊沿,1對應着第二個邊沿。
對於:
CPHA=0,表示第一個邊沿:
對於CPOL=0,idle時候的是低電平,第一個邊沿就是從低變到高,所以是上升沿;
對於CPOL=1,idle時候的是高電平,第一個邊沿就是從高變到低,所以是下降沿;
CPHA=1,表示第二個邊沿:
對於CPOL=0,idle時候的是低電平,第二個邊沿就是從高變到低,所以是下降沿;
3.4、極性和相位圖示
圖例1
圖例2
3.5 、軟件設置極性和相位
SPI分主設備和從設備,兩者通過SPI協議通訊。
而設置SPI的模式,是從設備的模式,決定了主設備的模式。
所以要先去搞懂從設備的SPI是何種模式,然后再將主設備的SPI的模式,設置和從設備相同的模式,即可正常通訊。
對於從設備的SPI是什么模式,有兩種:
1.固定的,有SPI從設備硬件決定的
SPI從設備,具體是什么模式,相關的datasheet中會有描述,需要自己去datasheet中找到相關的描述,即:
關於SPI從設備,在空閑的時候,是高電平還是低電平,即決定了CPOL是0還是1;
然后再找到關於設備是在上升沿還是下降沿去采樣數據,這樣就是,在定了CPOL的值的前提下,對應着可以推算出CPHA是0還是1了。
2.可配置的,由軟件自己設定
從設備也是一個SPI控制器,4種模式都支持,此時只要自己設置為某種模式即可。
然后知道了從設備的模式后,再去將SPI主設備的模式,設置為和從設備模式一樣,即可。
對於如何配置SPI的CPOL和CPHA的話,不多細說,多數都是直接去寫對應的SPI控制器中對應寄存器中的CPOL和CPHA那兩位,寫0或寫1即可。
4、STM32的SPI控制模塊
SPI是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍設備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在EEPROM,FLASH,實時時鍾,AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只占用四根線,節約了芯片的管腳,同時為PCB的布局上節省空間,提供方便,正是出於這種簡單易用的特性,現在越來越多的芯片集成了這種通信協議,STM32也有SPI接口。
SPI接口一般使用4條線通信:
MISO 主設備數據輸入,從設備數據輸出。
MOSI 主設備數據輸出,從設備數據輸入。
SCLK 時鍾信號,由主設備產生。
CS 從設備片選信號,由主設備控制。
SPI主要特點有:可以同時發出和接收串行數據;可以當作主機或從機工作;提供頻率可編程時鍾;發送結束中斷標志;寫沖突保護;總線競爭保護等。
STM32的SPI功能很強大,SPI時鍾最多可以到18Mhz,支持DMA,可以配置為SPI協議或者I2S協議
關於SPI,從數據手冊查到
STM32F207VCT6共有3個SPI。
從下表查出每個SPI對應的管腳
STM32標准外設庫SPI_InitTypeDef的定義
typedef struct { uint16_t SPI_Direction; // 設置SPI 的通信方式,可以選擇為半雙工,全雙工,以及串行發和串行收方式 uint16_t SPI_Mode; // 設置SPI 的主從模式 uint16_t SPI_DataSize; // 為8 位還是16 位幀格式選擇項 uint16_t SPI_CPOL; // 設置時鍾極性 uint16_t SPI_CPHA; // 設置時鍾相位 uint16_t SPI_NSS; //設置NSS 信號由硬件(NSS管腳)還是軟件控制 uint16_t SPI_BaudRatePrescaler; //設置SPI 波特率預分頻值 uint16_t SPI_FirstBit; //設置數據傳輸順序是MSB 位在前還是LSB 位在前 uint16_t SPI_CRCPolynomial; //設置CRC 校驗多項式,提高通信可靠性,大於1 即可 }SPI_InitTypeDef;
第一個參數SPI_Direction 是用來設置SPI的通信方式,可以選擇為半雙工,全雙工,以及串行發和串行收方式,這里我們選擇全雙工模式SPI_Direction_2Lines_FullDuplex。
第二個參數SPI_Mode用來設置SPI的主從模式,這里我們設置為主機模式 SPI_Mode_Master,當然有需要你也可以選擇為從機模式 SPI_Mode_Slave。
第三個參數SPI_DataSiz為8位還是16位幀格式選擇項,這里我們是8位傳輸,選擇SPI_DataSize_8b。
第四個參數SPI_CPOL用來設置時鍾極性,我們設置串行同步時鍾的空閑狀態為高電平所以我們選擇SPI_CPOL_High。
第五個參數SPI_CPHA 用來設置時鍾相位,也就是選擇在串行同步時鍾的第幾個跳變沿(上升或下降)數據被采樣,可以為第一個或者第二個條邊沿采集,這里我們選擇第二個跳變沿,所以選擇 SPI_CPHA_2Edge
第六個參數SPI_NSS設置NSS信號由硬件(NSS管腳)還是軟件控制,這里我們通過軟件控制NSS關鍵,而不是硬件自動控制,所以選擇 SPI_NSS_Soft。
第七個參數 SPI_BaudRatePrescaler很關鍵,就是設置 SPI 波特率預分頻值也就是決定 SPI 的時鍾的參數,從不分頻道256分頻8個可選值,初始化的時候我們選擇256分頻值SPI_BaudRatePrescaler_256,傳輸速度為36M/256=140.625KHz。
第八個參數 SPI_FirstBit設置數據傳輸順序是 MSB 位在前還是LSB位在前,這里我們選擇SPI_FirstBit_MSB高位在前。
第九個參數 SPI_CRCPolynomial是用來設置CRC校驗多項式,提高通信可靠性,大於1即可。
示例代碼:
void SPIInit( void ) { SPI_InitTypeDef SPI_InitStructure; FLASH_GPIO_Init(); /*!< Deselect the FLASH: Chip Select high */ GPIO_SetBits( GPIOA, GPIO_Pin_4 ); /*!< SPI configuration */ SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; /* 雙線雙向全雙工 */ SPI_InitStructure.SPI_Mode = SPI_Mode_Master; /* 主 SPI */ SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; /* SPI 發送接收 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_16; /* 預分頻 16 */ SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; /* 數據傳輸從 MSB 位開始 */ SPI_InitStructure.SPI_CRCPolynomial = 7; /* CRC 值計算的多項式 */ SPI_Init( SPI1, &SPI_InitStructure ); /*!< Enable the sFLASH_SPI */ SPI_Cmd( SPI1, ENABLE ); }
看到這里,可能覺的前面講原理並沒有太大的用處,因為STM32集成了SPI控制器,配置一下即可。
一方面我們學習原理是為了更好的理解SPI,用於對接不同的SPI設備,像norflash的spi驅動網上有大量的例子,不容易出錯。但並不是特別常見的,spi驅動SD卡,SPI驅動網絡PHY,SPI驅動ESP8266,甚至在設計兩個IC通信時,由於沒有過多GPIO,又覺的IIC通信速度慢的話,可以設計兩個IC之間使用SPI通信,顯然這些場景就需要了解SPI的原理
另外一方面,實際應用中,有可能因為芯片其他管腳用於特殊功能,留下的管腳沒有硬件SPI功能,只能模擬實現,這個時候學習SPI原理就很有必要了。
5、SPI的應用
SPI的常用應用NorFlash
從數據手冊上看到,SPI傳輸:CKPOL=1 , CKPHA=1
所以STM32的SPI讀取NorFlash的配置如下
抓取下面代碼波形
抓取的波形如下
0100 1011 就是0X4B
其中看到:
起始電平是高電平,也就是CKPOL=1
第二個邊沿采樣,也就是CKPHA=1
其實說成類似IIC的高電平有效也是沒有問題的
下面這句話是寫模擬SPI的核心
自己的理解:在下降沿轉換數據,在上升沿采樣數據
除了抓取波形,在華邦Flash也看到了時序圖
6、code
讀取norflash
使用STM32F207硬件SPI模塊
/** * @brief Initializes the peripherals used by the SPI FLASH driver. * @param None * @retval None */ void FLASH_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; /*!< Enable the SPI clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); /*!< Enable GPIO clocks */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); /*!< SPI pins configuration *************************************************/ /*!< Connect SPI pins to AF5 */ GPIO_PinAFConfig(GPIOA, 5, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, 6, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOB, 5, GPIO_AF_SPI1); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//GPIO_PuPd_DOWN; /*!< SPI SCK pin configuration */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOA, &GPIO_InitStructure); /*!< SPI MISO pin configuration */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA, &GPIO_InitStructure); /*!< SPI MOSI pin configuration */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOB, &GPIO_InitStructure); /*!< Configure sFLASH Card CS pin in output pushpull mode */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOE, &GPIO_InitStructure); } /** * @brief Initializes the peripherals used by the SPI FLASH driver. * @param None * @retval None */ void FLASH_SPIInit(void) { SPI_InitTypeDef SPI_InitStructure; FLASH_GPIO_Init(); /*!< Deselect the FLASH: Chip Select high */ GPIO_SetBits(GPIOE,GPIO_Pin_12); /*!< SPI configuration */ SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//雙線雙向全雙工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主 SPI SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;// SPI 發送接收 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_16;//預分頻 16 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//數據傳輸從 MSB 位開始 SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC 值計算的多項式 SPI_Init(SPI1, &SPI_InitStructure); /*!< Enable the sFLASH_SPI */ SPI_Cmd(SPI1, ENABLE); }
軟件模擬SPI協議
/** * @brief Sends a byte through the SPI interface and return the byte received * from the SPI bus. * @param byte: byte to send. * @retval The value of the received byte. */ uint8_t SPI_ReadWriteByte(uint8_t data) { uint8_t i,data_read = 0; if(data!=0xA5){ for(i=0;i<8;i++){ MSPI_CLK_L(); if(data&0x80){ MSPI_MOSI_H(); }else{ MSPI_MOSI_L(); } MSPI_DELAY(); data = data<<1; MSPI_CLK_H(); MSPI_DELAY(); } return data_read; }else{ for(i=0;i<8;i++){ MSPI_CLK_L(); MSPI_DELAY(); data_read = data_read<<1; MSPI_CLK_H(); if(MSPI_READ_IN()){ data_read |= 0x01; } MSPI_DELAY(); } return data_read; } } /** * @brief Initializes the peripherals used by the SPI FLASH driver. * @param None * @retval None */ void FLASH_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; /*!< Enable GPIO clocks */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); /*!< Configure sFLASH Card CS pin in output pushpull mode */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOE, &GPIO_InitStructure); /*!< SPI SCK pin configuration */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOA, &GPIO_InitStructure); MSPI_CLK_H(); /*!< SPI MOSI pin configuration */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOB, &GPIO_InitStructure); MSPI_MOSI_H(); /*!< SPI MISO pin configuration */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_Init(GPIOA, &GPIO_InitStructure); } /** * @brief Initializes the peripherals used by the SPI FLASH driver. * @param None * @retval None */ void FLASH_SPIInit(void) { FLASH_GPIO_Init(); /*!< Deselect the FLASH: Chip Select high */ GPIO_SetBits(GPIOE,GPIO_Pin_12); }
開源地址:
https://github.com/strongercjd/STM32F207VCT6
點擊查看本文所在的專輯,STM32F207教程