引言 (Introduction)
在嵌入式開發與 Maker 的世界裡,微機電感測器(MEMS)的讀取通常是踏入硬體互動的第一步。然而,當我們試圖在底層硬體(ESP32-C3 SuperMini)上,將生硬的 MPU6050 角度數據轉化為流暢、具備擬真物理互動的 OLED 遊戲或模擬時,往往會踩入一連串教科書上沒寫的「硬體與演算法地雷」。
本篇文章將完整記錄如何從開機死鎖(Hang)的黑畫面一步步排查,導入精確的感測器融合(Sensor Fusion)與全零點校正,並最終在記憶體受限的單晶片上,徒手刻出一個支援 30 顆方塊實時碰撞、相互依靠堆疊的輕量級物理引擎。
第一章:幽靈的開機死鎖——解密 ESP32-C3 的 Strapping Pins 陷阱
很多初學者在將 MPU6050 連接上 ESP32-C3 SuperMini 並使用預設的 I2C 腳位(SDA=8, SCL=9)後,會遇到一個極其詭異的現象:程式燒錄成功(Upload OK),但序列埠監視器(Serial Monitor)永遠只印出第一行引導碼:
ESP-ROM:esp32c3-api1-20210207
隨後整個系統陷入無限期的死鎖,連 setup() 裡最頂端的 Serial.println() 都來不及執行。
核心原因分析:
問題不在軟體,而在硬體的「開機配置腳(Strapping Pins)」。ESP32-C3 晶片在硬體重置(Reset)的瞬間,會去偵測 GPIO 8 與 GPIO 9 的電位狀態,以決定晶片是要進入「SPI Boot 正常開機」還是「ROM Boot 燒錄模式」。
當 MPU6050 模組上的 4.7kΩ I2C 上拉電阻(Pull-up Resistors)直接把 GPIO 8 的電位拉高或拉低到異常狀態時,晶片在開機瞬間就會誤判啟動模式,進而硬體級鎖死。
工程解決方案:
徹底避開衝突腳位,在軟體中手動重新映射通訊軌道。我們將 I2C 匯流排改至安全且完全不影響開機配置的 GPIO 4 (SDA) 與 GPIO 5 (SCL)。
Wire.begin(4, 5); // 徹底避開 GPIO 8 開機地雷
Wire.setTimeOut(50); // 設置硬體超時防線
透過更換安全引腳與加入 setTimeOut 超時防線,即便硬體通訊中斷,底層函式庫也不會再讓 CPU 無限期死等硬體回應。
第二章:桌上的「鬧鬼」滑行——全感測器零點深度校正
當 I2C 通訊順利暢通後,我們嘗試在 OLED 上繪製一個基準框,並讓小球隨角度滑動。此時第二個經典盲點浮現:將板子平放在絕對靜止的桌面上,小球卻在幾秒鐘內自己緩緩「滑出平台」撞牆。
為什麼單純校正陀螺儀(Gyro)是不夠的?
一般的範例程式只會在開機時對陀螺儀進行取樣取平均,以扣除角速度的漂移(Drift)。然而,放在桌上靜止時,加速度計(Accelerometer)本身也帶有硬體偏置误差(Offset)。
受限於晶片封裝、洞洞板表面的銲接傾角、或是桌面本身的微小傾斜,加速度計讀到的 $X, Y$ 軸絕對不會是完美的 0.0。由於互補濾波器(Sensor Fusion)會不斷參考加速度計的絕對角度,這 $\pm 2^\circ \sim 3^\circ$ 的微小硬體誤差會被誤判為持續的傾斜,導致小球產生「自動滑行」的靈異現象。
深度水平校正演算法:
我們必須在開機時執行取樣(例如 200 次),不僅記錄陀螺儀偏置,更要把當前桌面狀態下的加速度計絕對三角角度(Roll/Pitch)直接記錄下來,並在 loop 運算中扎實地扣除它:
// 減去開機時的桌面偏置,將當前狀態定義為「絕對 0 度基準」
float accRoll = rawAccRoll - errorAccRoll;
float accPitch = rawAccPitch - errorAccPitch;
這樣一來,不論銲接再怎麼歪、桌面再怎麼斜,只要放在桌上不動,數值就會死死咬在 0 度,水晶球便能滑順地定格在正中央。
第三章:從冰面滑行到重力暴衝——非線性物理與 LERP 緩動
為了提升視覺的擬真度與互動的爽快感,系統的滾動速度不能是死板的線性移動,必須引入真正的加速度(Acceleration)與慣性阻尼(Friction)。
重力加速度累積:傾斜角度(Roll/Pitch)作為力(Force)施加在物體上,速度隨時間持續累積($V = V + a$)。手腕傾斜角度越大,施加的加速度越高,物體會呈現指數級的暴衝感。
LERP (線性插值) 濾波緩動:為了消除未過濾的手震雜訊,我們不讓方塊直接瞬移到目標物理座標,而是利用緩動公式:
$$\text{Current Position} += (\text{Target Position} - \text{Current Position}) \times \text{Factor}$$這讓物體在畫面上移動時,產生了一種如同在冰面上滑行的油潤液壓感與重力慣性。
第四章:極限壓榨效能——30 顆粒子實時碰撞堆疊引擎
專案的終極挑戰,是在單晶片上實施群體粒子模擬:在 OLED 畫面上繪製最大邊界框,生成 30 個 $4 \times 4$ 像素的獨立小方塊,當它們受到重力滑向特定牆角時,必須互相堆疊依靠、自然緊密排列,且彼此絕不產生重疊或穿透。
幽靈隱形牆的 Debug 經驗:
在初期測試中,主迴圈明明只命令 U8g2 繪製 30 個方塊,但當方塊滑落到底部時,卻會在距離底壁還有 10 像素的地方「憑空卡住」,死死地排成一列,彷彿撞上了隱形牆。
經查,元凶在於交叉碰撞檢查(Axis-Aligned Bounding Box, AABB)的內部迴圈上限被硬編碼寫死了常數:
for (int j = 0; j < 30; j++) { ... } // ❌ 越界讀取地雷
當這個常數與實際陣列大小產生偏差時,C++ 在執行碰撞判定時,會讀取到陣列邊界之外的未初始化記憶體垃圾數據(Uninitialized Memory)。這些垃圾數據在物理邏輯上被誤判為「已經死死躺在底部的方塊」,導致真實的 30 個方塊在移動時,提前撞上了這些看不到的記憶體幽靈。
工業級物理引擎解法:
內存徹底清空:開機時使用
memset(boxes, 0, sizeof(boxes));徹底洗淨陣列,抹除一切硬體隨機殘留值。多重鬆弛迭代(Relaxation Iterations):當多個方塊在牆角同時擠壓時,方塊 A 撞 B、B 又同時撞牆。我們在單次 loop 內加入 3 次迭代解算(Sweep),在極短的時間差 $dt$ 內反覆修正重疊座標,讓 30 顆方塊能夠展現出如同沙子般完美、扎實的依靠堆疊視覺特效。
// 核心多重鬆弛迭代碰撞檢查結構
for (int k = 0; k < 3; k++) {
for (int i = 0; i < NUM_BOXES; i++) {
// 預計移動的新座標計算與 AABB 矩形碰撞判定...
// 發生重疊時,精確倒推邊緣座標並將該軸向速度(vx/vy)安全歸零
}
}
結語 (Conclusion)
透過這一連串的硬體引腳更換、雙重零點校正、LERP 緩動機制,以及修正越界內存的碰撞演算法,原本生硬跳動的感測器訊號,在 ESP32-C3 上成功被賦予了真實的動態生命力。
完整的粒子堆疊物理引擎代碼已開源(詳見上文),不論你是想為你的機器人專案加入更滑順的角度追蹤,還是想在小螢幕上實作精緻的街機互動,這套整合了 Sytem Architecture (SA) 穩定防禦 與 UI/UX 視覺美學 的底層框架,都能為你的下一個 Maker 作品打下最扎實的技術基石!