前言
這次我們要介紹的主題是SPI通訊界面。一般在使用SPI介面傳輸的感測器或裝置時,通常還會再另外搭配其他函式庫,與ESP32原生的SPI關連不大,有關實際應用的內容我們會留到下一篇再進行說明。這篇文章將介紹如何使用ESP32的SPI,並且說明如何在兩片ESP32開發板之間使用SPI通訊。
什麼是SPI?
序列周邊介面(Serial Peripheral Interface, SPI),是微控制器常用的一種全雙工串列同步通訊界面,適用於與周邊設備進行短距離且高速的傳輸。它採用了主從式架構,由一個主設備和一至多個周邊設備組成,最早由摩托羅拉(Motorola)公司在1980年代中期開發。
在Flash(快閃記憶體)、EEPROM、RTC(實時時鐘)、ADC(類比數位轉換器)、DAC(數位類比轉換器)、液晶顯示器,或是兩個微控制器間的通訊都可以看到SPI的應用。但由於SPI不是一套具有標準規範的協議,它只是一種業界標準(de facto standard),所以不是每個使用SPI的設備都用相同的方式實現。
新舊接腳名稱對照:
接腳名稱(舊) | 接腳名稱(新) |
---|---|
MISO (Master In Slave Out) | CIPO (Controller In Peripheral Out) |
MOSI (Master Out Slave In) | COPI (Controller Out Peripheral In) |
SS (Slave Select Pin) | CS (Chip Select Pin) |
SPI通訊採用主從式架構,由主設備發起傳輸訊號,統一控制所有裝置傳輸,並控制設備與設備之間發送和讀取資料的時機。所有設備都共享同一個由主設備提供的時脈(clock)訊號。
一般SPI會有4條資料線,分別是:
- CIPO/MISO: 周邊裝置輸出資料到控制器。
- COPI/MOSI: 控制器輸出資料到周邊裝置。
- SCK/SCLK(Serial Clock): 串列時脈,由主設備發出。時脈速度也決定了傳輸速度。
- CS/SS: 晶片選擇接腳,低電位驅動。當同一條SPI Bus上有多個設備時,主設備會用CS接腳選擇要進行通訊的周邊設備。
SPI模式
當設備傳送資料時,在什麼時間點讀取訊號,會直接的影響設備之間的通訊內容。通常在SPI中,會以模式(Mode)來定義。模式由兩個條件構成:時脈相位(clock phase)與時脈極性(clock polarity)。
- 時脈相位(CPHA):選擇訊號要在SCLK的上升緣或是下降緣進行採樣。 CPHA=0: 上升緣;CPHA = 1: 下降緣。
- 時脈極性(CPOL):當SPI在閒置狀態時,SCLK的電位狀態。 (註:閒置狀態指的是CS接腳在傳輸開始與傳輸結束時的狀態) CPOL = 0: 閒置時保持低電位;CPOL = 1: 閒置時保持高電位。
根據相位和極性的變化,SPI就會產生4種不同的傳輸模式:
模式 | Clock Polarity (CPOL) | Clock Phase (CPHA) |
---|---|---|
MODE0 | 0 | 0 |
MODE1 | 0 | 1 |
MODE2 | 1 | 0 |
MODE3 | 1 | 1 |
ESP32的SPI介面
ESP32總共有4個SPI介面,分別是SPI0~SPI3。SPI0 與 SPI1 已經被用來連接到晶片上的Flash Memory,我們一般可以自由使用的是SPI2與SPI3這兩個SPI控制器,其中SPI2又被稱作 HSPI(High-speed Serial Peripheral Interface),SPI3被稱作VSPI(Very High-speed Serial Peripheral Interface)。這兩個 SPI Bus 各自具有獨立的訊號,代表我們可以隨意將任意一項作為主設備或周邊設備,而不影響到另外一項。在把它們當作主設備的情況下,單一個SPI Bus最多可以驅動3個周邊設備。
HSPI 和 VSPI Bus的預設接腳如下:
使用ESP32上面的SPI
VSPI(預設)
在沒有特別設定的情況下,ESP32的SPI預設使用VSPI(SPI3),因此只要照著預設接腳連接即可。
我們可以執行下面的範例查看開發板的預設接腳:
/* 顯示ESP32 SPI預設接腳 */
void setup() {
Serial.begin(115200);
}
void loop() {
Serial.print("COPI / MOSI: ");
Serial.println(MOSI);
Serial.print("CIPO / MISO: ");
Serial.println(MISO);
Serial.print("SCK: ");
Serial.println(SCK);
Serial.print("CS / SS: ");
Serial.println(SS);
delay(1000);
Serial.println();
}
執行結果
HSPI
如果要使用HSPI,就要先引入<SPI.h>這個標頭檔,然後用SPIClass函式實例化物件,並指定使用HSPI。
例如:
# include <SPI.h>
SPIClass MySPI2(HSPI); // 指定MySPI2使用HSPI
void setup() {
MySPI2.begin(); // 初始化MySPI2,使用HSPI預設接腳 (SCLK = 14, CIPO= 12, COPI =13, CS = 15)
// 以下省略...
}
void loop() {
}
自訂義SPI接腳
SPIClass的begin函式也可以用來指定其他GPIO接腳:
MySPI2.begin(HSPI_SCLK, HSPI_CIPO, HSPI_COPI, HSPI_CS); //SCLK, CIPO, COPI, CS Pin
例如像是:
# include <SPI.h>
// 接腳定義
# define MySPI2_SCLK 14
# define MySPI2_CIPO 12
# define MySPI2_COPI 13
# define MySPI2_CS 15
# define SPI2CLK 1000000 // 1M Hz
SPIClass MySPI2(HSPI);
void setup() {
MySPI2.begin(MySPI2_SCLK, MySPI2_CIPO, MySPI2_COPI, MySPI2_CS); // 在begin()自定義SPI2的接腳
}
void loop() {
}
SPI傳輸流程
首先,設定好SPI的時脈頻率以及傳輸模式,然後使用beginTransaction初始化SPI介面。
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); // 時脈1MHz, MSBFIRST, MODE0
接著用CS接腳選擇要傳送的周邊設備,將電壓準位拉低,啟用設備。
digitalWrite(CS_pin, LOW);
然後呼叫transfer()方法傳輸資料。
SPI.transfer(data);
傳輸完成後,再將CS接腳的電壓準位拉回高電位。
digitalWrite(CS_pin, HIGH);
最後,呼叫endTransaction(),釋放目前使用的SPI Bus。
SPI.endTransaction();
SPI訊號的實際輸出
下圖是指定ESP32當作SPI Controller輸出 0b11001100
的訊號,用邏輯分析儀捕捉到的波形:
這段通訊的傳輸模式是MODE 0,所以從圖片中可以看到,在每個時脈的上升緣讀取,MOSI接腳就會接收到 0b11001100 的資料。但是在試了幾次後都沒能抓到CS接腳的變化狀態,且CS出現的幾個尖波,也讓筆者相當不解。如果有網友知道原因,還請不吝告知。
測試程式碼
(註: 這部分參考的是arduino-esp32 core SPI example的測試程式(Link))
從上面的範例中我們可以看到,在SPI資料傳輸階段,ESP32使用的程式語法與原生的Arduino相同。在傳輸資料前,會先將CS接腳接到低電位再送出資料。傳輸完成後再將CS接腳接回高電位,表示傳輸結束。
連接多個SPI設備
假如ESP32同時與周邊設備1以及設備2連接,要向周邊設備2傳送資料時,也是用同樣的方式設定。只是要先將周邊設備1的CS接腳設為高電位,再去設定設備2的CS接腳。接線部分需要注意Controller用了兩根不同的CS接腳。
void loop() {
MySPI2.beginTransaction(SPISettings(SPI2CLK, MSBFIRST, SPI_MODE0)); // 初始化SPI bus。與Arduino SPI用法相同
digitalWrite(MySPI2_CS1, HIGH); // 將設備1的晶片選擇腳(CS)拉高,禁用設備1
digitalWrite(MySPI2_CS2, LOW); // 將設備2的晶片選擇腳(CS)拉低,準備傳輸資料
MySPI2.transfer(byte(0b11001100)); // 傳輸二進位資料
// 以下省略....
}
註:若將ESP32當作主設備讀取具有SPI介面的感測器資料,通常還會另外再搭配感測器的函式庫使用,但是此部分的程式碼與原生SPI的關聯性較低,本篇先不討論。
進階範例: 使用SPI在ESP32之間傳訊
接下來的範例會說明如何在兩片不同的ESP32開發板上面使用SPI通訊。我們把右邊的開發板當作主設備,左邊的開發板當作周邊設備,兩邊使用HSPI的預設接腳通訊,由主設備發送二進位訊息到周邊設備。當週邊設備接收到0x01訊號時,就會點亮開發板上的LED。
材料清單
腳位對照表
SPI Bus | 主設備 | 周邊設備 |
---|---|---|
COPI | GPIO 13 | GPIO 13 |
CIPO | GPIO 12 | GPIO 12 |
SCK | GPIO 14 | GPIO 14 |
CS | GPIO 15 | GPIO 15 |
主控端
下面的程式功能是讓主控端使用HSPI控制器固定間隔大約1秒鐘的時間,傳送訊號給周邊設備。
程式碼
周邊設備端
由於<SPI.h>函式庫沒有支援將ESP32當作周邊設備的功能,所以我們需要先到函式庫管理員中下載ESP32SPISlave這個函式庫。
將下面的程式碼複製到IDE後上傳,打開序列埠監控視窗,就可以看到目前接收到的資料。
程式碼
(註:由於此函式庫使用Slave代稱周邊設備,因此程式碼中就沿用Slave的稱呼)
執行結果
小結
我們可以使用ESP32上面的VSPI和HSPI這兩個獨立的SPI控制器進行SPI通訊,單一個SPI控制器可以驅動3個周邊裝置,所以最多可以控制到6個周邊裝置。在連接到多個周邊設備時,需要注意每個設備都要由不同的CS接腳控制。在這篇文章中,除了介紹預設的SPI接腳,也說明了如何用軟體重新定義接腳,以及使用SPI傳訊時實際的輸出訊號。最後透過範例說明如何在兩片ESP32間用SPI通訊。下一篇,我們將會說明如何在ESP32上面驅動SPI介面的電子紙。