Learning Objectives
- 說明 UART、I2C、SPI 各需要幾條線、怎麼接,以及適合哪些模組
- 讀懂 UART 的訊框:起始位元、8 個資料位元(最低位元先)、停止位元
- 說明 I2C 的位址、讀寫位元與 ACK,用 i2c.scan() 找裝置、讀暫存器
- 說明 SPI 的片選與全雙工,依需求選擇協定
開發板和模組怎麼說話
LED、按鈕只要一支腳位就能表達「開或關」。溫溼度感測器、OLED 螢幕、GPS 模組要傳的是一串數字, 如果每個位元都用一支腳位,8 個位元就要 8 條線,腳位很快就不夠用。
所以模組大多用序列通訊(serial communication):在同一條線上,一個位元接著一個位元地傳。 雙方只要事先約好規則(通訊協定),就知道每個高低電位代表什麼。
最常見的三種協定:
| UART | I2C | SPI | |
|---|---|---|---|
| 訊號線 | TX、RX(加 GND) | SDA 資料、SCL 時脈(加 GND) | SCLK、MOSI、MISO、每個裝置一條 CS |
| 時脈 | 沒有,雙方事先約好速度 | 控制器提供 | 控制器提供 |
| 一次連幾個裝置 | 一對一 | 一條匯流排掛很多個,用位址區分 | 很多個,用 CS 選 |
| 速度(常見) | 9600~115200 bps | 100 kHz、400 kHz | 數 MHz 以上 |
| 常見的模組 | GPS、藍牙序列模組、USB 轉序列 | OLED、溫溼度與加速度感測器 | SD 卡、彩色顯示器 |
同步與非同步的差別在有沒有時脈線:I2C、SPI 由控制器送時脈,告訴對方「現在讀這一位」; UART 沒有時脈,雙方靠事先約好的速度各自計時。
接線前確認電壓:ESP32、Pico 是 3.3V,有些模組是 5V。5V 的訊號直接接到 3.3V 的腳位可能損壞開發板,需要電位轉換(level shifter)。
UART:約好速度,各自計時
UART 的接線是交叉的:我的 TX(傳送)接對方的 RX(接收),我的 RX 接對方的 TX,再把兩邊的 GND 接在一起。
雙方要先約好鮑率(baud rate,每秒幾個位元),常見 9600 或 115200。最常用的格式叫 8N1:
閒置(1) │起始(0)│D0│D1│D2│D3│D4│D5│D6│D7│停止(1)│ 閒置(1)
└ 拉低代表開始 └ 資料從最低位元開始送 ┘
- 閒置時線路是高電位;起始位元把線拉低,接收端從這一刻開始計時
- 接著 8 個資料位元,最低位元(D0)先送
- 最後一個停止位元回到高電位
一個位元組共 10 個位元。9600 bps 時每個位元約 104 微秒,一個位元組約 1.04 ms。 雙方鮑率不一樣,接收端會在錯的時間點讀,收到一堆亂碼。
你在電腦上看到 print() 的輸出,很多開發板也是經過一顆 USB 轉 UART 的晶片送過來的。
逐步看:用 UART 向感測模組要資料
ESP32 的 UART2 接到一個序列輸出的感測模組(示意):送出 READ 之後,模組回覆兩行文字。
注意 uart.readline() 讀到的是 bytes,要 decode() 才是字串。
I2C:一條匯流排,用位址點名
I2C 只要兩條線:SDA(資料)與 SCL(時脈),所有裝置並聯在同一組線上,形成一條匯流排(bus)。 每個裝置有一個 7 位元的位址,例如 OLED 常見 0x3C、TMP102 溫度感測器是 0x48。
兩條線都要接上拉電阻(很多模組已內建),閒置時是高電位;裝置只會把線拉低或放開,不會主動推高, 所以多個裝置接在一起也不會互相短路。
一次讀取的過程:
- 起始條件:SCL 是高電位時,控制器把 SDA 拉低
- 送 7 位元位址(最高位元先)+ 1 個讀寫位元(0 寫、1 讀)
- 第 9 個時脈:有這個位址的裝置把 SDA 拉低,回應 ACK;沒有人回應就是 NACK
- 傳資料,每 8 個位元之後都有一個 ACK/NACK
- 停止條件:SCL 是高電位時,SDA 由低變高
感測器裡的資料放在一格一格的暫存器(register)裡,i2c.readfrom_mem(位址, 暫存器, 位元組數) 就是「向某個裝置的某個暫存器讀幾個位元組」。
同一條匯流排上的位址不能重複;兩個相同位址的模組,要用模組上的跳線改位址,或接到另一組 I2C。
逐步看:掃描 I2C、讀溫度暫存器
匯流排上接了一個 OLED(0x3C)和一個 TMP102 類的溫度感測器(0x48)。 溫度暫存器有 2 個位元組:前 8 位元加上第二個位元組的前 4 位元,組成 12 位元的數值,每一單位是 0.0625°C。
SPI:多幾條線,換來速度
SPI 用四種訊號:
- SCLK:控制器送出的時脈
- MOSI:控制器送給裝置的資料(Master Out Slave In)
- MISO:裝置送回控制器的資料(Master In Slave Out)
- CS(片選):每個裝置一條,拉低代表選中,沒被選中的裝置不理會時脈,也不驅動 MISO
每一個時脈,MOSI 與 MISO 同時各傳一個位元,送 8 個時脈就同時「送出一個位元組、收到一個位元組」,這叫全雙工。 SPI 沒有位址與 ACK,時脈可以到數 MHz 以上,適合 SD 卡、彩色顯示器這類要傳大量資料的裝置;代價是線比較多,每多一個裝置就多一條 CS。
時脈閒置是高或低(CPOL)、在哪個邊緣取樣(CPHA)組合成模式 0~3,要和裝置的規格一致;最常見的是模式 0。
from machine import Pin, SPI
spi = SPI(1, baudrate=1000000, polarity=0, phase=0,
sck=Pin(18), mosi=Pin(23), miso=Pin(19))
cs = Pin(5, Pin.OUT, value=1) # 平常是高電位:沒選中
cs.value(0) # 選中裝置
rx = bytearray(1)
spi.write_readinto(b'\x9f', rx) # 送出一個位元組,同時收一個位元組
cs.value(1) # 結束
(Uedu 的模擬器目前沒有模擬 SPI,這段程式只看不執行;下面的邏輯分析儀可以看到 SPI 的波形。)
小測驗:UART
ESP32 用 UART 接一個 GPS 模組,下列哪一種接法正確?
下面的程式列出字母 A 從 D0 到 D7 的位元(UART 傳送的順序),會印出什麼?
v = ord("A")
bits = [(v >> k) & 1 for k in range(8)]
print(v, bits)
9600 bps、8N1 的 UART 傳一個位元組需要多久?
baud = 9600
print(round(10 / baud * 1000, 2), "ms")
小測驗:I2C 與 SPI
兩個 OLED 模組的位址都是 0x3C,接在同一組 I2C 上會怎樣?
溫度感測器回傳兩個位元組 0x19、0x40,下面的程式會印出什麼?
data = bytes([0x19, 0x40])
raw = (data[0] << 4) | (data[1] >> 4)
print(raw, raw * 0.0625)
要接一張 SD 卡存大量感測資料,通常用哪一種介面?為什麼?
重點整理
- 序列通訊在同一條線上一個位元接一個位元傳;有時脈線的是同步(I2C、SPI),沒有的是非同步(UART)。
- UART:TX、RX 交叉接,雙方鮑率相同;8N1 訊框是起始位元、8 個資料位元(最低位元先)、停止位元。
- I2C:SDA、SCL 兩條線加上拉電阻,用 7 位元位址點名,第 9 個時脈回應 ACK;
scan()找裝置、readfrom_mem()讀暫存器。 - SPI:SCLK、MOSI、MISO 加每個裝置一條 CS(低電位選中),全雙工、速度快、線比較多。
- 接線前確認電壓準位,5V 模組接 3.3V 開發板要電位轉換。
下一課看感測器怎麼把溫度、光線變成電壓與數字,以及讀值為什麼會跳動、怎麼校正。