第 06 章 - 認識 GPIO 與 LED 燈測試
上次更新日期 2022/10/30
了解一點點 python 的程式碼實作之後,再來理解一下怎麼操作樹莓派與外部 IoT 元件的互動! 因為 IoT 的元件並不一定透過網路,大部分的感測器都還是得要透過實體線路連接後,才有辦法取得感測器的資訊。 樹莓派主要透過 GPIO 的接腳界面來跟感測器互動,所以,我們得要知道 GPIO 代表的意義。 另外,也能夠過 python 的模組來跟這些元件互動!
學習目標:
- 認識 GPIO 各個腳位
- 使用 LED
- 透過 python 管理 LED
- 了解並使用 gpiozero 內的各個 LED 模組
6.1: 認識 GPIO 的腳位功能
看了樹莓派這許多時候,你應該會發現樹莓派上面有一些可以安插杜邦線的接腳,這個是所謂的 GPIO 通用界面 (General Purpose Input/Output)。 樹莓派透過讀/寫資料 (包括電力資源) 到這些針腳上面,就可以跟週邊的裝置進行互動與資料收集的行為了。所以說,這個 GPIO 是真的要學習的東西。 那這 GPIO 因為面板的大小與擴充性,而有不同的配置 (layout),我們使用的樹莓派 3B 則擁有標準的 40 隻 GPIO 針腳! 這些針腳與其功能的對應圖示如下圖所示:(圖片來源請參考文末的天花板隨記網站)
如上圖所示,在 6 個直行最中間:
- 『 Pin# 』的項目,對照的就是樹莓派的 40 隻腳位的號碼
- 『 2ndFunc 』則是大致的功能
- 『 GPIO# 』就是 GPIO 使用到的腳位號碼
那如何知道樹莓派針腳的 1 號在哪裡? 請注意樹莓派 GPIO 的針腳位置,注意看繪製在 GPIO 旁邊的白色實線的四個角落,會有一個角落並非直角, 那個角落就是上圖右側左上角 1 號針腳的位置了。你都可以透過這樣的判斷來了解 1 號腳位的位置所在。上圖更詳細的說明, 可以參考底下的連結,天花板隨記內容寫的非常好!請務必參考:
那麼樹莓派的哪些腳位號碼比較重要?底下這些腳位,你應該要稍微注意一下比較好:
- 電力: 1, 17 提供 3.3V 電力, 2, 4 提供 5V 電力
- 接地: 6, 9, 14, 20, 25, 30, 34, 39 提供接地
- I2C: 3(SDA), 5(SDC) ,大量用於感測器的資料傳輸
- UART: 8(TX), 10(RX),有點類似 RS232,也有點類似 arduino 的 RX/TX 腳位
至於 GPIO 對應的腳位,就可以留待後續的應用。
- 樹莓派本身提供的 pinout 指令
如果想要知道屬於你這塊樹莓派的 GPIO 與樹莓派針腳對應,現在樹莓派提供一個 pinout 指令方便你查詢! 指令相當簡單,就輸入 pinout 即可!輸出的結果是有『顏色』的,不過底下鳥哥輸出純文字檔,畢竟文字顯示比較簡單!
$ pinout
,--------------------------------.
| oooooooooooooooooooo J8 +====
| 1ooooooooooooooooooo | USB
| +====
| Pi Model 3B V1.2 |
| +----+ +====
| |D| |SoC | | USB
| |S| | | +====
| |I| +----+ |
| |C| +======
| |S| | Net
| pwr |HDMI| |I||A| +======
`-| |--------| |----|V|-------'
Revision : a22082
SoC : BCM2837
RAM : 1GB
Storage : MicroSD
USB ports : 4 (of which 0 USB3)
Ethernet ports : 1 (100Mbps max. speed)
Wi-fi : True
Bluetooth : True
Camera ports (CSI) : 1
Display ports (DSI): 1
J8:
3V3 (1) (2) 5V
GPIO2 (3) (4) 5V
GPIO3 (5) (6) GND
GPIO4 (7) (8) GPIO14
GND (9) (10) GPIO15
GPIO17 (11) (12) GPIO18
GPIO27 (13) (14) GND
GPIO22 (15) (16) GPIO23
3V3 (17) (18) GPIO24
GPIO10 (19) (20) GND
GPIO9 (21) (22) GPIO25
GPIO11 (23) (24) GPIO8
GND (25) (26) GPIO7
GPIO0 (27) (28) GPIO1
GPIO5 (29) (30) GND
GPIO6 (31) (32) GPIO12
GPIO13 (33) (34) GND
GPIO19 (35) (36) GPIO16
GPIO26 (37) (38) GPIO20
GND (39) (40) GPIO21
所有腳位對應都輸出在上面了,還提供樹莓派整個板子的 layout (布局) 示意圖呢!實在非常有趣!
- 關於 LED
發光二極體 (LED) 主要吃直流電,所以 LED 的供電需要分正負極。請拿出一顆兩隻腳的 LED 燈,你會發現到 LED 的腳有長短, 長腳是正極 (DC+),短腳是負極 (DC-)。讓我們來測試一下樹莓派 GPIO 與 LED 發光的關係。另外,要注意的是, LED 燈的供電也不能太大,否則可能會有燒毀的情況發生。雖然目前 LED 的品質都不錯,不過,為了避免燒毀, 建議在接上 5V 供電之前,還是加裝一顆電阻較佳。
- 預先找到樹莓派的 5V 與接地,這次分別以樹莓派 pin 2, pin 6 為例。注意,要有電阻支援!
- 連線方式處理:
- pi 5V --- 麵包板 --- 電阻的一腳
- 電阻的另一腳 --- 麵包板 --- LED 正極
- LED 負極 --- 麵包板 --- pi 接地
6.2: 使用 gpiozero 的 python 模組控制 LED
資傳系同學應該都有稍微摸過 arduino 這個 IoT 的小物件,樹莓派其實有個很類似的模組可以用,不過,我們這邊講一個更簡單的, 稱為 gpiozero 的 python 模組來處理!這個模組本身是由樹莓派基金會內的工程師開發的,使用上非常方便! 不過要使用之前,得要先安裝正確的軟體才行。(要特別注意 /dev/gpio* 檔案,權限要正確,否則僅有 root 可處理!)
$ sudo apt-get install python3-gpiozero $ sudo chmod 666 /dev/gpio* $ ls -l /dev/gpio*
基本上,我們使用 2022 年最新的樹莓派作業系統,已經將 gpiozero 建立好了!接下來,讓我們開始來處理一下 python 的 LED 控制吧。 首先,我們得要重新將 LED 燈接線~因為上一個例題的 5V 電力是持續供電的,我們現在的作法是想要手動來控制 LED 的開關。 因此,得要接在任何一個可用的 GPIO 接腳上面,例如網路範本長說的 GPIO18 這樣。我們也可以改變不同的接法,不要跟網路範例一樣, 在這裡,我們使用 GPIO27 與接地,分別是樹莓派的 13 與 14 針腳,用來作為 LED 的連線:
- 樹莓派 13 腳 --- 麵包板 --- 電阻一腳
- 電阻另一腳 --- 麵包板 --- LED 正極
- LED 負極 --- 麵包板 --- 樹莓派 14 腳
一般預設 GPIO 除了 5V 與 3.3V 之外,其他腳位是不會供電的!那麼我現在要強迫 GPIO 27 供電要怎麼處理?就使用 python 搭配 gpiozero 模組來處理即可!撰寫一隻名為 led-1-1.py 的腳本程式吧!
$ mkdir ~/led $ cd ~/led $ vim led-1-1.py # 從 gpiozero 模組當中載入 LED 功能 from gpiozero import LED from time import sleep led = LED(27) # led = LED("GPIO27") # 給 GPIO 腳位,跟上一行意義相同! # led = LED("J8:13") # 定義為樹莓派的腳位 while True: led.on() sleep(1) led.off() sleep(1) $ python led-1-1.py
這時 python 程式就會卡住,然後你看一下 LED 燈,就會每秒閃爍一次!如果你嫌棄閃爍的速度,那就將 sleep 裡面的數字改成 0.1 再次執行 python 看看,就可以看到閃爍的速度變化了。如果要停止 python 程式,按下 [ctrl]-c 中斷程式即可。 但是與 arduino 這種裝置不一樣的,因為 arduino 裝置的操作中,其內部的程式使用的是無限迴圈 (loop),所以 LED 會一直閃爍沒問題~但是樹莓派使用 python 控制而已,所以 python 停止後,所有腳位的電力就恢復預設值。 因此,停掉 python 程式後,你的 LED 燈不論最後程式的狀態為何,最終你的燈都會熄滅...
- 使用樹莓派腳位搭配 led.blink 處理
而除了使用 GPIO 的腳位 (預設也是使用 GPIO 腳位的數值) 之外,我們可以使用樹莓派的腳位定義,也就是 J8:13 這樣的腳位定義來說明。 此外,如果只是想要執行一秒一次的閃爍,不需要寫迴圈~透過 .blink() 的方法來處理即可!程式碼也相對簡單:
$ cp led-1-1.py led-1-2.py $ vim led-1-2.py from gpiozero import LED from signal import pause led = LED("J8:13") # 給樹莓派腳位 led.blink() pause() $ python led-1-2.py
另外,.blink 也有很多參數可以使用喔!包括延遲!一般延遲預設是 1 秒,但是你可以這樣規範不同的時間! 假設點亮 0.5 秒而熄滅僅 0.1 秒時,可以這樣做:
led.blink(on_time=0.5, off_time=0.1)
更多的 LED 函數參數,可以參考文末提供的 gpiozero 官網文件喔!至於那個 pause() 函數,則是要讓 python 程式不會停止! 這樣 LED 的功能才不會停止~
- 請再拿出一顆 LED 與電阻,根據之前的設計,安裝在麵包板上。請自行選擇需要的 GPIO 腳位處理
- 拿出 led-1-2.py 的程式碼另存為 led-1-3.py,修改為,當一顆 LED 亮,另一顆就暗。延遲 1 秒之後狀態互相改變。
- 程式碼會一直執行,直到用戶按下 [ctrl]-c 為止。
- 使用 input 進行互動
我們目前沒有其他可互動的元件 (按鈕),那是否可以先透過 python 的界面來處理互動呢?也是可以的。 使用 python 內建的 input 函數來處理,你就可以讓使用者鍵盤輸入所需要的資訊,搭配變數來處置,就能互動了。
$ vim led-1-4.py from gpiozero import LED led = LED(27) while True: print("\n0) 關閉 LED 燈") print("1) 打開 LED 燈") print("2) 離開程式") light = input("你的選擇 (0|1|2): ") if light == "0": led.off() if light == "1": led.on() if light == "2": break $ python led-1-4.py 0) 關閉 LED 燈 1) 打開 LED 燈 2) 離開程式 你的選擇 (0|1|2): 1
你可以發現,LED 燈可以讓你控制了!
- 可亮度變化的 LED
剛剛使用的 gpiozero LED 模組中,LED 的亮度是不可調整的,就是全亮跟全暗~那如果我需要從暗到亮呢?這時就得要使用可變電壓的情況! gpiozero 提供一個名為 PWMLED 的方法,你可以透過這個方法來調整亮度!這時就不是使用 on() 或 off() 了!直接給一個數值, 0 為最暗,1 為最亮!必須要是數值型態才行喔!讓我們來做一個可以從暗到亮的 LED 燈吧!
$ vim led-1-5.py from gpiozero import PWMLED from time import sleep led = PWMLED(27) while True: led.value = 0 sleep(0.5) led.value = 0.5 sleep(0.5) led.value = 1 sleep(0.5) $ python led-1-5.py
你就可以看到三階段的 LED 亮度變化。那如果你需要連續的變化,而不是三階段而已呢?那可以使用 pulse() 函數喔! 這個函數的功能有點像這樣:
led.pulse(fade_in_time=0.5, fade_out_time=0.5) # fade_in_time 指的是暗到亮 (亮度由 0~1) 變化的時間 # fade_out_time 指的是亮到暗 (亮度由 1~0) 變化的時間
這樣就可以產生連續的亮度變化喔!不用去寫 sleep 調整!相當方便!
- 先嘗試改變 led-1-5.py,使用 pulse() 函數,讓單顆 LED 燈可以產生亮度變化!
- 增加另外一顆 LED 燈,一顆變亮的時候一顆會變暗,反之亦同 (兩顆燈之間有 sleep 喔!)
6.3: 多顆 LED 燈號變化
單顆或兩顆 LED 還算好解決。如果想要多顆 LED 同時提供服務,製造 LED 燈號移動或者是霹靂燈或者是雨滴燈的情境時, 就得要有點邏輯思考了!不過,還好,我們有更簡單的 gpiozero 可以使用!這個模組用來控制 LED 真的好方便! 底下我們來測試一下如何處理這些特別的燈號變化。
這次我們就不用電阻了!在短時間內進行 LED 點亮的動作, 基本上,LED 應該是不會燒毀的!注意不要直接安裝在 5V 的電力上,而且不要點亮太久,應該是不會燒毀! 我們將四顆 LED 安裝成類似底下這樣:
為了方便安裝,建議杜邦線直接安插到 pin 31, 33, 35, 37, 39 上,注意, pin 39 是接地,其他自己注意。 這時,你的硬體連接應該就妥當了!剩下一些程式碼的處理。
- 讓 LED 燈號單方向移動 - 老派作法 (看看就好)
上個小節我們在控制兩顆 LED 燈的時候,可能會用到兩個變數,分別是 led1, led2 這樣來設計! 現在我們有 4 顆 LED 燈,如果要按照上面的方法來處理,可能得要設計四個變數!控制上面比較討厭!那我們可以使用陣列~ 例如底下的形式:
# 設定 led = [ "one", "two", "three", "four" ] # 使用 led[0] => "one" led[1] => "two" led[2] => "three" led[3] => "four"
現在,先讓我們來玩一下單純的點燈與關燈,讓燈號會朝向一個方向前進 (以 pin 腳來說,就是 31 --> 37 前進),那該如何處理? 老式的作法會有點像底下這樣,原理是:每次迴圈做四次,每個迴圈內有 sleep 大約 0.2 秒,每次的腳位都會是 31+i*2 。 程式碼會有點像底下這樣:
from gpiozero import PWMLED from time import sleep led = [ PWMLED("J8:31"), PWMLED("J8:33"), PWMLED("J8:35"), PWMLED("J8:37") ] while True: for i in range(4): for j in range(4): if j == i: led[j].value = 0.8 else: led[j].value = 0 sleep(0.2)
因為沒有加上電阻,所以,數值不要直接用到 1 了!使用 0.8 即可。只是,這種方式你得要自己去想一下迴圈的處理方式, 設計上面並不是很直觀!對於沒有程式底子的朋友來說,設計這種迴圈,會很有點頭疼!當然啦,如果你本身想要玩更高深的的東西, 上面這種設計,應該才是比較好的!因為可以學習程式的語法。
- 讓 LED 燈號單方向移動 - 使用 LEDBoard 模組
gpiozero 為了讓大家控制 LED 燈更簡易,也幫大家寫好了陣列式的 LED 群組~那就稱為 LEDBoard 的方式! 而 LEDBoard (LED 板子) 為了方便大家設計燈光緩慢增亮或變暗的方式,也提供了 pwm=True 的參數,讓使用者設計上更方便!
$ vim led-2-1.py from gpiozero import LEDBoard from time import sleep from signal import pause leds = LEDBoard("J8:31", "J8:33", "J8:35", "J8:37") dtime = 0.2 while True: leds.value = (1,0,0,0) sleep(dtime) leds.value = (0,1,0,0) sleep(dtime) leds.value = (0,0,1,0) sleep(dtime) leds.value = (0,0,0,1) sleep(dtime) $ python led-2-1.py
如果想要變成漸變的情況,那就更簡單啦!因為狀態都不變啊!我們讓燈號閃爍,大概 4 秒一個間隔, 其中 0.5 秒用在漸亮、0.5 秒用在漸暗、3 秒用在關閉的狀態,不要有全亮的狀態~那程式碼就簡單的變成這樣:
$ vim led-2-2.py from gpiozero import LEDBoard from time import sleep from signal import pause leds = LEDBoard("J8:31", "J8:33", "J8:35", "J8:37",pwm=True) for i in range(4): leds[i].blink(on_time=0, off_time=3, fade_in_time=0.5, fade_out_time=0.5) sleep(1) pause() $ python led-2-2.py
你可以讓 LED 有各種各樣的變化!當然啦,要搭配陣列的角度來思考囉!
- 讓 LED 排隊變化,有點類似跑馬燈 - 使用 LEDBarGraph 模組
除了 LEDBorad 模組之外,gpiozero 還提供了 LEDBarGraph 模組!這個模組也是挺有趣, 他使用了很直覺的連續排列順序來處理你的 LED 燈,設計的方式有點像這樣:
leds = LEDBarGraph("J8:31", "J8:33", "J8:35", "J8:37",pwm=True) leds.value = 1/4 # (1,0,0,0) leds.value = 3/4 # (1,1,1,0) leds.value =-3/4 # (0,1,1,1)
看出來了嘛?我共有 4 顆 LED 燈,如果是正值 (1,2,3...) 的話,代表從第 1 顆到該顆 LED 燈號要亮, 如果是負值,代表後面哪幾顆要亮,就這麼簡單!所以,如果你要讓四顆 LED 排隊全點亮,然後再從前面慢慢關掉~ 那就可以這樣簡單的設計了:
$ vim led-2-3.py from gpiozero import LEDBoard from gpiozero import LEDBarGraph from time import sleep from signal import pause leds = LEDBarGraph("J8:31", "J8:33", "J8:35", "J8:37",pwm=True) dtime = 0.2 while True: for i in range(1,5,1): leds.value = i/4 sleep(dtime) for i in range(-3,1,1): leds.value = i/4 sleep(dtime) $ python led-2-3.py
務必記得 python 的 range 用法,語法是:
range (start, stop+step, step)
因為 stop 數值不會出現喔!所以需要增加一次!否則你會老是少進行一次!透過上述的語法, 你的 LED 燈就可以開始排列整齊的出隊了!
- 讓 LED 變成霹靂燈
老人家可能會聽過霹靂車,以前這個『霹靂遊俠』很紅啊!裡面的霹靂車的車頭,就是有個閃爍的燈~ 這個燈會左右移動,後來就被稱為霹靂燈了!我們只有 4 顆 LED 燈,基本上,你可以使用陣列,反正只有底下這幾種情況:
1000 0100 0010 0001 0010 0100 1000
就只有這 6 種情況而已 (頭尾相同)~設計上面也挺簡單的!這樣處理即可:
$ vim led-2-4.py from gpiozero import LEDBoard from gpiozero import LEDBarGraph from time import sleep from signal import pause leds = LEDBoard("J8:31", "J8:33", "J8:35", "J8:37",pwm=True) dtime = 0.2 while True: leds.value = (1, 0, 0, 0) sleep(dtime) leds.value = (0, 1, 0, 0) sleep(dtime) leds.value = (0, 0, 1, 0) sleep(dtime) leds.value = (0, 0, 0, 1) sleep(dtime) leds.value = (0, 0, 1, 0) sleep(dtime) leds.value = (0, 1, 0, 0) sleep(dtime) $ python led-2-4.py
實際執行之後,你就可以發現這顆燈好像會碰撞來來去去!
6.4: 當週實做
- 讓 LED 變成雨滴燈:
雨滴燈的概念,則是一開始是亮的,但是後續會緩慢變暗。基本上,整體概念跟 led-2-2.py 差不多! 請使用 led-2-2.py 複製成為 led-2-5.py,然後修改一下,讓它變成雨滴燈的模樣來處理。 事實上,就是加入 on_time, off_time, fade_out_time 的參數,讓燈號變成突然亮起來,但是緩慢變暗而已。
- 透過雨滴燈功能,抓取會閃爍的霹靂燈:
基本上就是透過上面雨滴燈的概念來處理,但是,實際上燈號的現象要改變~大概就行了! 請建立 led-2-6.py,利用 led-2-5.py 結合 led-2-3.py 的概念來處理。另外要注意的是, on_time, off_time, fade_out_time 這些參數的總和時間,必須要是等待 (sleep) 時間的 6 倍! 這是因為全部的變化就僅有 6 種~(霹靂燈),所以得要結合起來才行!
- 參考資料
- IT 鐵人:https://ithelp.ithome.com.tw/articles/10215294
- 天花板隨記:https://atceiling.blogspot.com/2014/01/raspberry-pigpio.html
- The Pi4J Project: https://pi4j.com/1.2/pins/model-3b-rev1.html
- 有趣的 gpiozero 模組說明:https://www.ics.com/blog/control-raspberry-pi-gpio-pins-python
- gpiozero 官方文件:https://gpiozero.readthedocs.io/en/stable/
# led-2-5.py from gpiozero import LEDBoard from time import sleep from signal import pause leds = LEDBoard("J8:31", "J8:33", "J8:35", "J8:37",pwm=True) dtime = 0.1 for i in range(4): leds[i].blink(on_time=dtime*2, off_time=dtime*3, fade_in_time=0, fade_out_time=dtime*2) sleep(dtime) pause()
# led-2-6.py from gpiozero import LEDBoard from time import sleep from signal import pause leds = LEDBoard("J8:31", "J8:33", "J8:35", "J8:37",pwm=True) dtime = 0.5 while True: for i in range(4): leds[i].blink(on_time=dtime, off_time=dtime*3, fade_in_time=0, fade_out_time=dtime*2) sleep(dtime) for i in range(2,0,-1): leds[i].blink(on_time=dtime, off_time=dtime*3, fade_in_time=0, fade_out_time=dtime*2) sleep(dtime)