| ESP32 教學 | MicroPython | SPI Function | 205 |

SPI 是在 MCU 系統中常用的通訊協定,跟 I2C使用相比,雖然所需線路較多,但傳輸速度快,架構上可同時接收與傳送訊息,所以常出現在 EEPROM、SD記憶體等元件上。MicroPython 在 ESP32平台的 SPI 介面,可以使用硬體 SPI 或 IO腳位模擬的SPI,本文就來分享如何使用 SPI Function,並透過 SPI 介面的 MAX7219 為例,操作 8×8 矩陣的LED。

1. SPI 原理

SPI (Serial Peripheral Interface)採用的是 Master 與 Slave 架構,通常在硬體上會有一個主要 Master,搭配著多個 Slave,資料傳輸介面基本上會有 4 條,CS、MOSI、MISO、CLK,分別解釋如下:

  • CS:Chip Select,也就是當 Master 要與目標裝置溝通時,會將將該腳位拉成 LOW,訊息開始傳輸,結束後便會將 CS 腳位拉成 HIGH,結束通訊。也就因 SPI 是透過 CS 來『確定』與那個裝置溝通,所以當系統上的裝置越多,CS 線路也就越多(MOSI、MISO、CLK仍共用)。
  • MOSI:英文為 Master Output Slave Input,也就是控制器發出訊息與 Slave 接收訊息的資料線。
  • MISO:英文為 Master Input Slave Output,此條線路為控制器接收來自 Slave 裝置訊息用。
  • CLK:Clock 線路,MOSI 或 MISO 資料線的參考時脈。

完整的SPI線路架構可以參考下圖:

spi function
spi function

2. ESP32 SPI Function 用法

在 ESP32 平台上可以使用純硬體的 SPI 介面或是軟體的 SPI 介面,可能有些朋友不懂這兩者的差別,這邊解釋一下,一般在 MCU 內部設計來說,如果有提供 SPI 介面的話,MCU 內就會包含所謂的 SPI 控制器,也就是所謂硬體的 SPI,一旦設定完成後,它是可以獨立運作與外界通訊,CPU 僅需對內部 SPI 控制暫存器讀取與寫入即可,那軟體 SPI 是什麼?其實就是利用一般的 GPIO『模擬』出 SPI 所需的通訊協定,特色就是腳位選擇彈性(因為只要一般GPIO即可),缺點就是佔用 CPU 資源與 SPI 傳輸的速度無法拉太高,畢竟軟體在控制IO的速度上還是沒有專門獨立運作 SPI 控制器快,所以這部分就是看開發者自己去選擇。JIMI哥自己以前在進行 Embedded 系統開發時,如有 SPI 介面需求時,支援 SPI 腳位還是會優先規劃出來,實做出軟體 SPI 通訊協定還是需要花費一些時間。

> 硬體 SPI

我們接著來看一下 MicroPython SPI 如何使用,下面為一個創造出硬體 SPI 物件的範例:(hw_sp1為自訂的物件名)

from machine import SPI,Pin
hw_spi1=SPI(1, 100000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))

與 PWM、ADC 等應用類似,SPI 類別都需要同時 import Pin,而在建立 SPI 的物件第一個參數填的就是 id,硬體 SPI 可以填入1或2;第2個參數就是 baud rate,硬體 SPI 的上限是 80Mhz,通常不會使用到上限,而且要看後端的 SPI 裝置可以允許的速度,範例程式參數是設定為100K;在資料線腳位選擇就關係到傳輸速度,為了要有穩定的高速傳輸介面,當使用硬體 SPI 時,建議可以採用預設的 SPI 腳位,如下表所示:

SPI ID 1 2
MISO GPIO12 GPIO19
MOSI GPIO13 GPIO23
CLK GPIO14 GPIO18

也就是說,如果我們是使用 SPI 預設腳位來進行傳輸時,建構 SPI 物件時可以簡化寫法 hw_spi1=SPI(1,100000),決定那一組 SPI 跟設定 BaudRate 就可。

有注意到少設定一隻腳嗎?沒錯!就是 CS 腳位,因為這隻腳僅需在傳輸開始前與後動作,所以只要選定一支通用的 GPIO 設定成 OUTPUT 即可。

> 軟體 SPI

如果我們要想要用一般的 GPIO 來模擬 SPI 的通訊協定,寫法與上述略有不同,使用上的彈性也較多,下面為一個軟體 spi的範例:(sw_spi 為自訂的物件名)

from machine import Pin,SoftSPI
sw_spi=SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(14), mosi=Pin(27), miso=Pin(26))

除了要改成 import SoftSpi 這個類別外,可以增加設定 CLK 的 polarity 和 phase 選擇,主要為因為不同的 SPI裝置對於CLK這個脈波的規格有時會有些不同,比如閒置狀態 CLK 需保持在 HIGH 或 LOW,跟資料線在 Latch時是用第 1 個 EDGE 或第 2 個,都可以進行改變以符合規格要求,腳位選擇因為一般的 GPIO 即可,所以就必須先指定。

各位朋友若對於要使用 Hardware 或 Software SPI,沒有概念的話,就可以先試著用硬體的方式試試,只要將其對應的腳位連接到 SPI 介面的腳位,就可以開始進行操作。

> Master 傳送訊息

最頻繁使用的的 SPI 控制方法就是 Master (控制器)下指令給 Slave 裝置,所使用的方法如:spi.write(buf)

buf 為 Bytes 型態的物件,因為 SPI 訊息的資料結構會通常以 8bit 為單位,以此類推成 16bit 或 24bit 更高,MSB 通常是暫存器位置,其他則為指令內容,所以我們如果想要對 Slave 的暫存器位址 0x01 下達 0x08 內容,在搭配我們先前指定的 CS 腳位(需事前定義),程式碼就會變成:

spi_cs.value(0)  #low , 開始進行spi傳輸
spi.write(b'18') #傳送 1 8 訊息 
spi_cs.value(1) # high, 結束spi通訊

3. SPI 實作 – MAX7219通訊

> 電路連接

知道 Micropython SPI 建立與使用方法後,就可以利用 MAX7219 的 SPI 介面進行與 ESP32 進行通訊測試,MAX7219 是一顆用來驅動 LED 的 IC,可以用在像是 7 段顯示器或 8×8 LED 的矩陣都可,提供的 SPI 介面速度可以達到 10MHz,在 shutdown 模式下,可以保持低電流省電,如需要的話也可以使用 BCD 解碼訊息格式的來顯示需要的樣式。我們先來使用一塊 MAX7219 的模組連結 8X8 的 LED 矩陣,電路的接法如下:

模組的 VCC 連接 5V,SPI 的設定採用第一組的硬體 SPI,所以對應的腳位為:

  • MOSI:GPIO13
  • CLK:GPIO12
  • MISO:沒有連接,因為無需從7219讀出資料
  • CS:GPIO27

> MAX7219 訊息格式

查閱 max7219的 Datasheet,其 SPI 訊息格式為 16bit,完整的格式定義為:

https://jimirobot.com.tw/image/esp32/205/ps_df.jpg

D15-D12為未定義的內容(也就是可以不用管它),D11-D8 代表暫存器的 address,D7-D0就是要傳的資料內容,所以我們只要知道在查閱每個暫存器位置代表的意思就可以到該傳什麼資料進行測試,而暫存器位置定義為:

暫存器位址有點多,不過JIMI哥快速的幫大家整理一下,各位朋友應該就會清楚了。

  1. Digit 0-7 在 8×8 LED矩陣應用中,代表實際LED陣列中第1行-第8行的LED顯示。
  2. Decode Mode: 解碼模式選擇。
  3. Intensity: 亮度。
  4. Scan Limit: 掃瞄模式設定。
  5. Shutdown:是否要進入關機模式。
  6. Display Test: 是否要進入顯示測試模式。

所以我們要初始化這個max7219,就只要將後5個暫存器填入對應的資料就可以開始使用,後五個暫存器在初始化的設定如下:(如果要瞭解為什麼要填入該數字,看 datasheet 內說明就可以知道)

  1. Decode mode (9)=>填入0
  2. Intensity(10)=>填入6
  3. ScanLimit(11)=>填入7,掃描8行
  4. ShutDown(12)=>填入1
  5. DisplayTest(13)=>填入0

> 完整程式碼

現在將上面的功能直接用程式碼建立,並在LED的陣列中,顯示一個方塊□的圖案。

cs_7219= Pin(27,Pin.OUT)
cs_7219.value(1)
spi7219=SPI(1, 100000)

## 初始化步驟 
cs_7219.value(0)        #chip_cs=low
buf=[9,0]
spi7219.write(bytes(buf))
cs_7219.value(1)

cs_7219.value(0)        #chip_cs=low
buf=[10,6]
spi7219.write(bytes(buf))
cs_7219.value(1)

cs_7219.value(0)        #chip_cs=low
buf=[11,7]
spi7219.write(bytes(buf))
cs_7219.value(1)

cs_7219.value(0)        #chip_cs=low
buf=[12,1]
spi7219.write(bytes(buf))
cs_7219.value(1)

cs_7219.value(0)        #chip_cs=low
buf=[13,0]
spi7219.write(bytes(buf))
cs_7219.value(1)

# 開始鍵入圖形□的資料
for i in range(1,9):
    cs_7219.value(0)        #chip_cs=low
    if ((i==1)|(i==8)):
        buf=[i,255]
    else :
        buf=[i,129]
    spi7219.write(bytes(buf))
    cs_7219.value(1)
#設定鍵盤中斷的處理    
try:
    while 1:
        pass
except KeyboardInterrupt :
    for i in range(1,9):
        cs_7219.value(0)        #chip_cs=low
        buf=[i,0]
        spi7219.write(bytes(buf))
        cs_7219.value(1)
    spi7219.deinit()

> 程式碼說明

cs_7219= Pin(27,Pin.OUT)
cs_7219.value(1)
spi7219=SPI(1, 100000)

一開始指定 GPIO27 為 ChipSelect 腳位,並預設為為 HIGH(避免誤動作),接者初始化 SPI 物件,指定使用第一組硬體 SPI 腳位。

# 初始化步驟 
cs_7219.value(0)        #chip_cs=low
buf=[9,0]
spi7219.write(bytes(buf))
cs_7219.value(1)

cs_7219.value(0)        #chip_cs=low
buf=[10,6]
spi7219.write(bytes(buf))
cs_7219.value(1)

cs_7219.value(0)        #chip_cs=low
buf=[11,7]
spi7219.write(bytes(buf))
cs_7219.value(1)

cs_7219.value(0)        #chip_cs=low
buf=[12,1]
spi7219.write(bytes(buf))
cs_7219.value(1)

cs_7219.value(0)        #chip_cs=low
buf=[13,0]
spi7219.write(bytes(buf))
cs_7219.value(1)

這邊JIMI哥沒有將這步驟寫法簡化,是想讓各位朋友可以清楚看到完整初始化流程,依序將我們指定的暫存器位址填入指定的資料,即可開始操作後面LED的資料。

# 開始鍵入圖形1的資料
for i in range(1,9):
    cs_7219.value(0)        #chip_cs=low
    if ((i==1)|(i==8)):
        buf=[i,255]
    else :
        buf=[i,129]
    spi7219.write(bytes(buf))
    cs_7219.value(1)

此段程式碼就是將暫存器 Digit0-Digit7 依序點亮LED,對應的 address 就是從 0x01~0x08,所以 buf 這個Bytes 物件的第一個元素就會填入1~8,至於 buf 第二個元素就是填入要點亮的LED,每個LED位置規則如下圖:

如果我們想要D0這行8顆LED全點亮時,對應的Bit位置就要填入1,也就是『11111111』,將這個二進位的數字換成10進位,就為變成255;那 D1 這行的 Bit7 和 Bit0 點亮,其餘關閉,那在D1就要填入『10000001』,十進位數值就填入129,其他位置就依此類推,所以如果我們想要點亮LED成一個方框的話, buf這個物件的第2個元素就依序填入255-129-129-129-129-129-129-255(分別代表D0-D7的值),整理這個迴圈共傳遞8次的spi訊息(也就是buf內容)就會變成:

  • 1-255 #填入D0暫存器(位置為0x01), 內容數值為255
  • 2-129 #填入D1暫存器, 內容數值為129
  • 3-129 #填入D2暫存器, 內容數值為129
  • 4-129 #填入D3暫存器, 內容數值為129
  • 5-129 #填入D4暫存器, 內容數值為129
  • 6-129 #填入D5暫存器, 內容數值為129
  • 7-129 #填入D6暫存器, 內容數值為129
  • 8-255 #填入D7暫存器, 內容數值為255
#設定鍵盤中斷的處理    
try:
    while 1:
        pass
except KeyboardInterrupt :
    for i in range(1,9):
        cs_7219.value(0)        #chip_cs=low
        buf=[i,0]
        spi7219.write(bytes(buf))
        cs_7219.value(1)
    spi7219.deinit()

最後這個部分就是當按下鍵盤中斷時的處理,第 45-49 行,將 8X8 LED 的燈全部關閉(傳入 D0-D7內容全部都是0);第 50 行,關閉 SPI 模組。

4. 結語

本篇的內文較長,主要因為分享較多關於 SPI 底層的原理,並透過 Max7219 這個模組搭配 8x8LED 來實際的瞭解 SPI 訊息如何傳送與溝通,相信各位朋友了解後,如果在應用其他的 SPI 介面的元件時,就不會有苦手的感覺。而關於 Micropython SPI用法的下一篇文章, JIMI哥就來預告一下,內容將是分享網路上 max7219 函式庫使用方法,如果在使用SPI 通訊上如果有遇到什麼問題,歡迎在下方留言討論,感謝囉!

↓↓↓↓↓↓賣場連結↓↓↓↓↓↓

歡迎大家有需要的話,可以多多支持一下我們的蝦皮賣場喔! 😀 

吉米家官方店-創客機器人材料專賣 https://shopee.tw/jimirobot.tw

Follow JIMI哥 Twitter : https://twitter.com/jimirobot  <–得到最新文章通知

發佈留言

Close Menu