整潔的嵌入式架構 (Clean Embedded Architecture)#

在嵌入式開發領域,開發者常陷入一個陷阱:「軟體磨損(Software wears out)。」 因為程式碼與硬體綁定得太緊,導致硬體一換,程式碼就得重寫。這樣的程式碼不叫軟體(Software),而叫韌體(Firmware)

Uncle Bob 強調:「雖然軟體依存在韌體之上,但韌體依存在硬體之上,軟體本身不該成為韌體。」

一、軟體建置的三個活動#

引用 Kent Beck 的觀點,軟體建置應包含三個階段,但大多數嵌入式工程師只停留在第一階段:

  1. 讓它工作 (Make it work): 這是最難的。確保程式碼能跑在目標硬體上。如果只停在這裡,你得到的是一堆亂七八糟的程式碼。
  2. 把它做對 (Make it right): 重構程式碼,讓它結構良好、易於維護。這需要架構思維。
  3. 讓它變快 (Make it fast): 優化效能。

整潔的嵌入式架構,目標是讓我們能順利進行到第二階段,從「讓程式碼工作」轉向「建構長壽有用的程式碼」。

二、目標硬體瓶頸 (The Target-Hardware Bottleneck)#

嵌入式開發最大的痛點是**「只能在目標硬體上測試」**。

  • 流程慢: 修改 $\rightarrow$ 編譯 $\rightarrow$ 燒錄 $\rightarrow$ 重啟 $\rightarrow$ 測試。這個迴圈太長了。
  • 解法: 如果架構分層得當,大部分的業務邏輯應該能在**主機(PC)**上進行測試,而不需要硬體參與。

三、分層策略:隔離硬體與韌體#

為了避免軟體被韌體汙染,我們必須明確界定三層結構:

  1. 軟體 (Software): 純粹的業務邏輯。完全不知道硬體的存在。
  2. 韌體 (Firmware): 負責與硬體溝通的低層程式碼。
  3. 硬體 (Hardware): 物理裝置。

1. 硬體抽象層 (HAL)#

(Hardware Abstraction Layer)

不要讓軟體層直接控制 GPIO 或讀取暫存器。

  • 做法: 建立一個 HAL。
  • 軟體看到的是: Led.TurnOn() (高層介面)。
  • HAL 實作的是: GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_SET) (韌體細節)。
  • 效益: 當硬體從 STM32 換成 ESP32 時,軟體層完全不用改,只要重寫 HAL 即可。

2. 處理器抽象層 (PAL)#

(Processor Abstraction Layer)

不要讓軟體依賴處理器廠商提供的 Header files。

  • 問題: 如果你的程式碼裡充滿了 vendor_cpu.h,你就被這顆 CPU 綁架了。
  • 解法: 將處理器特定的功能(如中斷控制、暫存器定義)隔離在 PAL 中。

3. 作業系統抽象層 (OSAL)#

(Operating System Abstraction Layer)

不要讓軟體直接依賴 OS(如 FreeRTOS, Linux pthreads)。

  • 問題: 如果直接使用 OS 的 API,當你需要更換 OS,或者想在 Windows 上跑單元測試時,你會遇到巨大的障礙。
  • 解法: 定義一個 OSAL 介面(如 OS_TaskCreate, OS_MutexLock)。
  • 效益: 你可以輕易地將 FreeRTOS 換成 Linux,甚至在測試環境中換成一個「假的」測試用 OS。

總結: 硬體是細節,處理器是細節,作業系統也是細節。 一個整潔的嵌入式架構,能透過 HAL 和 OSAL 將這些細節隔離。 這不僅能讓程式碼在不同硬體間移植,更重要的是,它讓你能脫離硬體進行離線測試(Off-target Testing),大幅提升開發速度與品質。