本專案以 micro:bit 為核心,結合無線電技術開發遙控機器人系統。系統由控制器和被控機器人組成,透過無線電訊號傳輸控制指令。控制器端負責讀取加速度感測器數值和按鈕狀態,並將這些資訊編碼成控制指令,透過無線電傳送給被控機器人。被控端則負責接收並解碼這些指令,進而控制機器人的馬達、燈光和喇叭等元件。文章探討了 MicroPython 在嵌入式系統中的記憶體管理與效能最佳化技巧,涵蓋編譯和執行階段的最佳化策略、記憶體碎片化問題的解決方案、垃圾回收機制的運用,以及實用的除錯技巧。這些技巧旨在協助開發者在資源受限的嵌入式平台上,開發出高效且穩定的 MicroPython 應用程式。
未來改進方向
- 增強避障能力:加入更複雜的演算法來最佳化避障路徑。
- 增加互動功能:使用無線電模組實作遙控功能,並結合傾斜感測器進行方向和速度控制。
- 硬體升級:考慮增加更多感測器或升級驅動系統,以提升機器人的功能性和穩定性。
無線遙控機器人:技術實作與程式解析
專案概述
本專案利用micro:bit開發板,結合無線電通訊技術,實作了一個可遙控的機器人系統。系統包含一個控制器和一個被控機器人,透過無線電訊號傳輸控制指令,實作對機器人的運動控制。
硬體組態
- micro:bit開發板(控制器與被控端各一)
- 加速度感測器(內建於micro:bit)
- 按鈕A與B(用於控制喇叭與燈光)
- 馬達控制電路
- NeoPixels燈條
軟體實作:控制器端
程式邏輯
控制器端的程式碼負責讀取加速度感測器資料和按鈕狀態,將這些資訊轉換為控制指令並透過無線電傳送給被控機器人。
from microbit import *
import radio
radio.config(channel=44)
radio.on()
# 定義有效的傾斜範圍
max_tilt = 1000
min_tilt = 199
while True:
# 讀取輸入
y = accelerometer.get_y() # 前後方向
x = accelerometer.get_x() # 左右方向
a = button_a.was_pressed() # 喇叭控制
b = button_b.was_pressed() # 燈光控制
# 初始化控制資料
control_data = [0, 0, 0, 0]
# 根據傾斜方向設定控制資料
if x < -min_tilt and y < -min_tilt:
display.show(Image.ARROW_NW)
control_data[0] = max(y, -max_tilt)
control_data[1] = max(x, -max_tilt)
# 其他方向判斷省略...
# 處理按鈕事件
if a:
control_data[2] = 1 # 發出喇叭訊號
if b:
control_data[3] = 1 # 切換燈光狀態
# 傳送控制指令
if any(control_data):
msg = '{}:{}:{}:{}'.format(*control_data)
radio.send(msg)
else:
display.clear()
sleep(20)
內容解密:
- 初始化設定:設定無線電通道並啟用無線電功能。
- 讀取輸入資料:從加速度感測器讀取x和y軸的傾斜值,並檢查按鈕A和B是否被按下。
- 轉換為控制指令:根據傾斜方向和按鈕狀態生成控制資料陣列。
- 傳送控制指令:將控制資料格式化為字串並透過無線電傳送。
軟體實作:被控端
程式邏輯
被控端的程式碼負責接收來自控制器的無線電訊號,解析控制指令,並驅動機器人的馬達和燈光。
from microbit import *
import radio
import neopixel
display.show(Image.SKULL) # 顯示標誌
colour = (244, 0, 244) # NeoPixel顏色
np = neopixel.NeoPixel(pin13, 12)
lights = False
radio.config(channel=44)
radio.on()
def move(speed, steer):
# 設定預設值(停止)
forward = 0
left = 0
right = 0
if speed > 0:
forward = 1
left = 1000 - speed
right = 1000 - speed
# 省略其他運動控制邏輯...
# 主迴圈
while True:
pin14.write_digital(0) # 關閉喇叭
try:
msg = radio.receive()
except:
msg = None
if msg is not None:
speed, steer, horn, light = [int(val) for val in msg.split(':')]
move(speed, steer) # 控制機器人運動
if horn:
pin14.write_digital(1) # 發出喇叭聲
if light:
# 切換燈光狀態
if lights:
np.clear()
lights = False
else:
lights = True
for i in range(12):
np[i] = colour
np.show()
else:
move(0, 0) # 無訊號則停止
sleep(20)
內容解密:
move函式:根據接收到的速度和轉向值,計算並設定馬達的轉速和方向。- 主迴圈邏輯:監聽無線電訊號,接收到訊號後解析並執行對應的動作。
- 燈光與喇叭控制:根據接收到的指令切換燈光狀態或發出喇叭聲。
第13章:道地的MicroPython
前幾章討論了許多與MicroPython相關的技術層面:可用的模組、它們如何與硬體互動,以及如何使用各種協定來實作有趣且實用的功能。然而,尚未討論如何利用這些知識來建立良好的程式碼:道地的MicroPython程式碼,或者如社群中某些人所說的,Pythonic程式碼。要了解這意味著什麼,我們需要退一步,從語言設計和程式設計文化的角度來考慮Python,然後再研究如何在高度受限的嵌入式裝置上編寫Pythonic的MicroPython。
為什麼Python會成為如此流行的語言?是什麼激勵了這麼多人為Python社群做出貢獻?為什麼Python被廣泛用作教學語言?
在1990年代,Python的創造者Guido van Rossum以及專案的仁慈獨裁者(BDFL),將Python作為一個名為“每個人的電腦程式設計:未來程式設計師的偵察遠徵”的專案基礎。該專案提案的開篇段落為Python的受歡迎程度提供了線索:
在七十年代,Xerox PARC問道:“我們能否在每個桌上都有電腦?”現在我們知道這是可能的,但那些電腦不一定能賦予使用者權力。今天的電腦往往缺乏彈性:一般的電腦使用者通常只能透過“嚮導”(一個對封閉式對話方塊的讚美之詞)更改有限的選項,並且除了專家程式設計師之外,其他人對其他一切都依賴。
我們提出了一個後續問題:“如果使用者可以編寫自己的電腦程式,會發生什麼?”我們期待著一個未來,每個電腦使用者都能“開啟他們電腦的引擎蓋”並改進內部的應用程式。我們相信,這最終將從根本上改變軟體和軟體開發工具的性質。
我們將大眾能夠閱讀和編寫軟體與大眾掃盲相比較,並預測對社會同樣普遍的變化。硬體現在足夠快速和廉價,使得大眾電腦教育成為可能:下一個重大變化將發生在大多數電腦使用者具備建立和修改軟體的知識和能力之時。
Python的前景核心是一種賦權精神。難怪人們喜歡它。但Python也有一個美麗、富有表現力和樂趣的語言聲譽。為什麼?
我相信答案在於一個稱為“Python之禪”的文化產物。要閱讀它,請開啟標準的Python REPL並輸入import this:
import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren’t special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one– and preferably only one –obvious way to do it. Although that way may not be obvious at first unless you’re Dutch. Now is better than never. Although never is often better than right now. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea–let’s do more of those!
簡而言之,Python對簡單性、清晰度、實用性和樂趣的關注很有吸引力,這種態度也被帶到了MicroPython的嵌入式開發中。
MicroPython之禪
在micro:bit版本的MicroPython中有一個MicroPython之禪作為彩蛋。要閱讀它,請連線到REPL並匯入this:
import this The Zen of MicroPython, by Nicholas H. Tollervey Code, Hack it, Less is more, Keep it simple, Small is beautiful, Be brave! Break things! Learn and have fun! Express yourself with MicroPython. Happy hacking! :-)
它的篇幅適當短小,是一個隱喻:雖然其中有幽默、智慧和韻律,但它最重要的屬性是簡潔。如果你看看它是如何寫的,還有一些語言技巧暗示了MicroPython在如此受限的環境中工作的微妙和非常聰明的技巧。
MicroPython之禪是要用盡可能少的東西來表達盡可能多的東西,這應該反映在你的程式碼中。為什麼?因為在一些平台上,比如micro:bit,你只有16k RAM和不超過5層的呼叫堆積疊深度。然而,儘管有如此極度受限的條件,仍然可以創造出相當多的功能。為了做到這一點,你需要了解一些與MicroPython相關的行內秘技。本章的其餘部分將探討這些內容。
記憶體
記憶體可能是新的MicroPython程式設計師面臨的最大問題之一。你應該瞭解兩種型別的記憶體:快閃記憶體(即持久記憶體,在你將MicroPython“燒錄”到裝置上時寫入)和RAM(揮發性記憶體)。快閃記憶體的大小通常以百千位元組為單位。
內容解密:
本文討論了MicroPython與記憶體管理的相關問題。首先,我們需要了解兩種主要的記憶體型別:快閃記憶體和RAM。快閃記憶體是一種非揮發性儲存器,用於儲存程式碼和資料,即使斷電也不會丟失。RAM則是一種揮發性儲存器,用於執行期間臨時儲存資料。接下來,我們將探討如何在MicroPython中有效地管理這兩種記憶體,以確保程式碼的高效執行。
進一步解析:
在嵌入式系統中,記憶體資源是非常寶貴的。有效管理記憶體不僅可以提高系統的穩定性和效能,還可以減少能耗。因此,瞭解MicroPython如何管理記憶體對於開發高效、穩定的嵌入式應用至關重要。
記憶體管理與最佳化
在嵌入式系統中,記憶體資源極為有限。MicroPython 為這些系統提供了 Python 程式設計環境,但開發者仍需關注記憶體的使用效率。瞭解 MicroPython 如何管理記憶體以及如何最佳化程式碼以節省記憶體,是開發高效能嵌入式應用的關鍵。
記憶體限制
許多嵌入式裝置的 RAM 極為有限,例如某些裝置的 RAM 只有 8 kilobytes。即使裝置的 RAM 較大,快閃記憶體(flash memory)的容量也可能受到限制。例如,在 micro:bit 上,直接嵌入到 MicroPython hex 檔案中的指令碼大小可能不超過 2 kilobytes。若需儲存更大的指令碼,可以使用檔案系統(約 20 kilobytes)。透過減少註解、縮短縮排字元數或刪除空白行等方法,可以有效減少程式碼佔用的快閃記憶體空間。
MicroPython 的運作機制
MicroPython 的運作方式與 CPython 類別似:解析 Python 指令碼,將其編譯為位元組碼(bytecode),然後在虛擬機器(VM)中執行。這些步驟會在兩個主要方面佔用 RAM:編譯 Python 為位元組碼時,以及執行位元組碼時。此外,堆積碎片(heap fragmentation)問題也會對記憶體使用產生不利影響。
編譯階段的記憶體最佳化
在匯入模組時,MicroPython 編譯器會將 Python 程式碼轉換為位元組碼。編譯完成後,這些記憶體會被釋放,但產生的位元組碼會儲存在 RAM 中。如果多個模組已經被轉換,它們的位元組碼會佔用 RAM,可能導致匯入陳述式(import statement)因記憶體不足而產生異常。
最佳實踐
- 避免在匯入時執行程式碼:盡量將初始化程式碼放在所有必要模組匯入之後執行,以最大限度地利用可用 RAM 進行編譯。
- 使用凍結模組或凍結位元組碼:將程式碼「烘焙」到燒錄在裝置上的韌體映像中。這需要根據所使用的 MicroPython 埠(port)進行相應操作。
- 手動預編譯位元組碼:在 PC 上使用 MicroPython 交叉編譯器(mpy-cross)將 Python 指令碼編譯為
.mpy檔案,並將其複製到裝置的檔案系統中。
執行階段的記憶體最佳化
執行位元組碼時,也會佔用 RAM。以下是一些減少執行階段 RAM 使用的方法:
使用
const關鍵字:某些 MicroPython 埠支援const關鍵字,類別似於 C 語言中的#define。它可以在編譯時將常數的字面值替換到程式碼中,從而節省位元組碼和 RAM。from micropython import const ROWS = const(33) _COLS = const(0x10) a = ROWS b = _COLS使用不可變資料結構:對於不會改變的常數資料結構,使用
bytes物件可以節省 RAM,因為它們是不可變的,會被儲存在快閃記憶體中而非 RAM 中。import ustruct # 使用 bytes 物件儲存資料 data = b'\x01\x02\x03' # 使用 ustruct 將 bytes 轉換為其他 Python 型別 value = ustruct.unpack('BBB', data)利用不可變物件的特性:對於其他不可變物件(如字串、浮點數、整數和複數),它們的值會儲存在快閃記憶體中,只有一份參考會佔用 RAM。
效能最佳化與記憶體管理
在 MicroPython 的開發環境中,效能最佳化與記憶體管理是至關重要的議題。由於微控制器的運算能力與記憶體資源相對有限,如何有效地利用這些資源,直接影響到程式的執行效率與穩定性。
記憶體使用最佳化
在 MicroPython 中,記憶體管理是一項重要的任務。開發者需要重新思考他們的程式設計風格,以更有效地利用 RAM。例如,在字串拼接的過程中,應盡量在編譯時期完成,以減少執行時的記憶體分配。
不良範例
foobar1 = "foo" + "bar" # 不推薦
此範例會建立兩個字串物件,並在拼接過程中分配新的記憶體來存放結果。
推薦範例
foobar2 = "foo" "bar" # 推薦
此範例在編譯時期直接拼接字串,避免了執行時的額外記憶體分配。
處理串流資料
當需要將字串寫入或從串流(如檔案)中讀取時,應採用懶惰(lazy)的方式進行處理。不是建立一個佔用大量 RAM 的大字串物件,而是以較小的區塊進行處理。
不良範例
while True:
var = spi.read(100)
# 處理資料
此範例在每次迴圈中都建立了一個新的緩衝區。
推薦範例
buf = bytearray(100)
while True:
spi.readinto(buf)
# 處理 buf 中的資料
內容解密:
此推薦範例透過重複使用一個預先分配的緩衝區 buf,避免了在每次迴圈中建立新的物件,從而減少了記憶體分配的次數,提高了效率並減少了記憶體碎片化的風險。
記憶體碎片化問題
記憶體碎片化是一個值得關注的問題。當物件被建立和回收後,如果剩餘的記憶體空間不連續,可能會導致即使總體可用記憶體足夠,也無法分配足夠大的連續記憶體空間來存放某些物件,從而引發記憶體錯誤。
使用固定大小的可重複使用緩衝區可以有效減少記憶體碎片化的發生。將這些大型的、永久性的緩衝區在程式早期階段例項化,可以在碎片化發生之前就確保它們的可用性。
垃圾回收機制
MicroPython 的垃圾回收器(GC)自動管理記憶體。當物件超出作用域後,GC會回收其佔用的堆積空間。開發者可以透過 gc 模組手動觸發垃圾回收,以改善效能。此外,gc.mem_free 和 gc.mem_alloc 可以用來檢查堆積空間的使用情況。
除錯技巧
緊急異常緩衝區
import micropython micropython.alloc_emergency_exception_buf(100)此設定可以在記憶體受限的情況下,於遇到異常時提供更詳細的回溯資訊,有助於除錯。
偽斷點
while True: pass在需要檢查程式狀態的位置插入無限迴圈,然後透過 REPL 連線並按 CTRL-C 中斷,可以用來檢查當前的程式上下文。
效能最佳化
除了選擇合適的演算法外,資源的有效利用也是效能最佳化的關鍵。開發者應根據 MicroPython 的特點,最佳化程式碼以提高執行效率。