SPI 規定了兩個 SPI 設備之間通訊必須由主設備 (Master) 來控制次設備 (Slave). 一個 Master 設備能夠經過提供 Clock 以及對 Slave 設備進行片選 (Slave Select) 來控制多個 Slave 設備, SPI 協議還規定 Slave 設備的 Clock 由 Master 設備經過 SCK 管腳提供給 Slave 設備, Slave 設備自己不能產生或控制 Clock, 沒有 Clock 則 Slave 設備不能正常工做。git
採用主-從模式(Master-Slave) 的控制方式。github
SPI 規定了兩個 SPI 設備之間通訊必須由主設備 (Master) 來控制次設備 (Slave). 一個 Master 設備能夠經過提供 Clock 以及對 Slave 設備進行片選 (Slave Select) 來控制多個 Slave 設備, SPI 協議還規定 Slave 設備的 Clock 由 Master 設備經過 SCK 管腳提供給 Slave 設備, Slave 設備自己不能產生或控制 Clock, 沒有 Clock 則 Slave 設備不能正常工做。編程
採用同步方式(Synchronous)傳輸數據網絡
Master 設備會根據將要交換的數據來產生相應的時鐘脈衝(Clock Pulse), 時鐘脈衝組成了時鐘信號(Clock Signal) , 時鐘信號經過時鐘極性 (CPOL) 和 時鐘相位 (CPHA) 控制着兩個 SPI 設備間什麼時候數據交換以及什麼時候對接收到的數據進行採樣, 來保證數據在兩個設備之間是同步傳輸的。app
SPI數據交換框圖佈局
上圖只是對 SPI 設備間通訊的一個簡單的描述, 下面就來解釋一下圖中所示的幾個組件(Module):學習
SSPBUF,Synchronous Serial Port Buffer, 泛指 SPI 設備裏面的內部緩衝區, 通常在物理上是以 FIFO 的形式, 保存傳輸過程當中的臨時數據;動畫
SSPSR, Synchronous Serial Port Register, 泛指 SPI 設備裏面的移位寄存器(Shift Regitser), 它的做用是根據設置好的數據位寬(bit-width) 把數據移入或者移出 SSPBUF;ui
Controller, 泛指 SPI 設備裏面的控制寄存器, 能夠經過配置它們來設置 SPI 總線的傳輸模式。spa
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是主從設備在交換數據。也就是說,你發一個數據必然會收到一個數據;你要收一個數據必須也要先發一個數據。
上升沿、降低沿、前沿、後沿觸發。固然也有MSB和LSB傳輸方式.
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的時候;
先說什麼是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;
首先說明一點,capture strobe = latch = read = sample,都是表示數據採樣,數據有效的時刻。相位,對應着數據採樣是在第幾個邊沿(edge),是第一個邊沿仍是第二個邊沿,0對應着第一個邊沿,1對應着第二個邊沿。
對於:
CPHA=0,表示第一個邊沿:
對於CPOL=0,idle時候的是低電平,第一個邊沿就是從低變到高,因此是上升沿;
對於CPOL=1,idle時候的是高電平,第一個邊沿就是從高變到低,因此是降低沿;
CPHA=1,表示第二個邊沿:
對於CPOL=0,idle時候的是低電平,第二個邊沿就是從高變到低,因此是降低沿;
圖例1
圖例2
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便可。
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原理就頗有必要了。
SPI的經常使用應用NorFlash
從數據手冊上看到,SPI傳輸:CKPOL=1 , CKPHA=1
因此STM32的SPI讀取NorFlash的配置以下
抓取下面代碼波形
抓取的波形以下
0100 1011 就是0X4B
其中看到:
起始電平是高電平,也就是CKPOL=1
第二個邊沿採樣,也就是CKPHA=1
其實說成相似IIC的高電平有效也是沒有問題的
下面這句話是寫模擬SPI的核心
本身的理解:在降低沿轉換數據,在上升沿採樣數據
除了抓取波形,在華邦Flash也看到了時序圖
讀取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教程