Learning Objectives
- 說明為什麼樹莓派要透過 ADC 晶片才能讀類比電壓
- 用位元數算出 ADC 的階數、每一階的電壓與量化誤差
- 把 MCP3008 接到樹莓派的 SPI 腳位,用 gpiozero 讀出讀值、比例與電壓
- 用可變電阻與光敏電阻分壓電路,做出旋鈕調光與自動夜燈
樹莓派看不懂「一半」
上一課用 PWM 讓腳位「輸出」中間值。這一課反過來:要「讀進」中間值。
旋鈕轉到哪裡、房間有多亮、土壤有多濕,這些感測器給的都是類比訊號:電壓可以是 0V 到 3.3V 之間的任何值。 樹莓派的 GPIO 只能判斷「高電位」或「低電位」,它沒有內建類比輸入,1.2V 和 2.1V 對它來說只是「低」和「高」。
這一課要回答:
- 怎麼把一個電壓變成程式拿得到的數字?
- 為什麼 10 位元的 ADC 讀值是 0~1023?每差 1 代表多少電壓?
- MCP3008 怎麼接到樹莓派,gpiozero 怎麼讀?
類比數位轉換(ADC)
類比數位轉換器(Analog-to-Digital Converter,ADC)把電壓切成固定數量的「階」,回報電壓落在第幾階。 階數由位元數決定:n 位元有 2ⁿ 階。
| 位元數 | 階數 | 讀值範圍 | 參考電壓 3.3V 時,每一階 |
|---|---|---|---|
| 8 | 256 | 0~255 | 約 12.9 mV |
| 10 | 1024 | 0~1023 | 約 3.2 mV |
| 12 | 4096 | 0~4095 | 約 0.8 mV |
MCP3008 是常用來搭配樹莓派的 ADC 晶片:10 位元、8 個輸入通道(CH0~CH7)、用 SPI 和樹莓派溝通, 工作電壓 2.7V~5.5V。資料手冊給的讀值公式是:
讀值 = 1024 × 輸入電壓 ÷ 參考電壓(VREF) 取整數,最大 1023
例:VREF = 3.3V、輸入 1.65V → 1024 × 1.65 ÷ 3.3 = 512
切成階就一定有誤差:真實的電壓是 1.6512V 還是 1.6530V,10 位元的 ADC 可能都讀成 512。 這個落差叫量化誤差,最多差不到一階。位元數越多,一階越小,誤差也越小。
SPI 接線與啟用
MCP3008 用 SPI(Serial Peripheral Interface)和樹莓派溝通。SPI 有四條訊號線:
| 訊號 | 意思 | 樹莓派腳位(BCM) | MCP3008 腳位 |
|---|---|---|---|
| SCLK | 時脈:樹莓派送出節拍 | GPIO11 | 13 CLK |
| MOSI | 樹莓派送給晶片(要讀哪個通道) | GPIO10 | 11 DIN |
| MISO | 晶片送回樹莓派(讀值) | GPIO9 | 12 DOUT |
| CE0 | 片選:選中這顆晶片 | GPIO8 | 10 CS/SHDN |
另外 MCP3008 的 16 VDD、15 VREF 接 3.3V,14 AGND、9 DGND 接 GND。可變電阻兩端接 3.3V 與 GND,中間腳接 CH0(1 號腳)。 同一組 SPI 可以接好幾顆晶片,它們共用時脈與資料線,各用自己的片選線(CE0 是 GPIO8,CE1 是 GPIO7)。
輸入電壓不要超過 VREF 與晶片的工作電壓;參考電壓接 3.3V 時,讀到 1023 就代表大約 3.3V。
樹莓派的 SPI 預設是關閉的。在終端機執行 sudo raspi-config,選 3 Interface Options → I4 SPI,回答要啟用,
或在桌面的 Control Centre 的 Interfaces 分頁打開 SPI。之後 gpiozero 就能讀:
from gpiozero import MCP3008
pot = MCP3008(channel=0) # 預設使用 SPI 0、片選 CE0
pot.raw_value # 讀值 0~1023
pot.value # 換成 0~1 的比例
pot.voltage # value × 3.3(max_voltage 預設 3.3)
逐步看:讀可變電阻
模擬的可變電阻在 2.5 秒內從 10% 轉到 90%(看電路上的數值)。每一輪讀一次讀值,再自己算出比例與電壓,對照上面的公式。
分壓:把電阻的變化變成電壓的變化
ADC 量的是電壓,可是可變電阻、光敏電阻改變的是電阻。中間的橋樑是分壓電路: 兩個電阻串聯接在 3.3V 與 GND 之間,中間點的電壓依兩個電阻的比例決定(「自造者入門」軌第 04 課會詳細算)。
- 可變電阻本身就是一個分壓器:兩端接 3.3V 與 GND,中間腳(滑片)轉到哪裡,就分到多少電壓。
- 光敏電阻越亮阻值越小。把它接在 3.3V 這一側、下面串一顆固定電阻接地,中間點接 ADC:越亮,中間點電壓越高。
讀感測器時有一個好習慣:讀一次、存進變數,再拿變數去用。pot.value 每讀一次都是一次新的轉換,
同一個 if 判斷裡讀兩次,兩次的值可能不同。
逐步看:旋鈕調光
把 ADC 的比例(0~1)直接交給 PWMLED 的 value(也是 0~1),旋鈕就成了調光器。這次模擬的旋鈕先轉到底,再轉回來一些。
逐步看:天黑自動開燈
光敏電阻分壓接在 CH1。模擬的光線先變暗再變亮。比例低於 DARK(0.3)就開燈,看程式在第 10 行往哪邊走。
小測驗:ADC 的數字
MCP3008 是 10 位元的 ADC,讀值的範圍是多少?
下面的程式會印出什麼?
raw = 512
volt = raw / 1023 * 3.3
print(round(volt, 2))
同樣參考電壓 3.3V,8 位元的 ADC 和 10 位元的 ADC 比,哪一個說法正確?
小測驗:接線與程式
MCP3008(channel=0) 預設使用哪一支腳位當作這顆晶片的片選(CE0)?
光敏電阻接 3.3V、固定電阻接 GND,中間點接 CH1。用手把光敏電阻遮住,MCP3008(channel=1).value 會怎樣?
把讀到的比例存進 level,下面的程式會印出什麼?
level = 0.25
DARK = 0.3
if level < DARK:
print("開燈")
else:
print("關燈")
重點整理
- 樹莓派沒有類比輸入,要靠 ADC 晶片把電壓變成數字;n 位元的 ADC 有 2ⁿ 階。
- MCP3008:10 位元(0~1023)、8 個通道、SPI 介面;讀值=1024 × 輸入電壓 ÷ 參考電壓。
- SPI 四條線:SCLK(GPIO11)、MOSI(GPIO10)、MISO(GPIO9)、片選 CE0(GPIO8);先用 raspi-config 啟用 SPI。
- gpiozero:
raw_value是整數讀值,value是 0~1 的比例,voltage是電壓;讀一次存進變數再用。 - 可變電阻、光敏電阻透過分壓電路把電阻的變化變成電壓的變化;比例 0~1 可以直接接到 PWMLED 的亮度。
下一課換另一種匯流排 I2C:只用兩條線就能接好幾個感測器,直接讀出已經換算好的溫度。