前言:軟體架構是系統的靈魂
結構混亂的程式碼不僅難讀,更導致除錯、測試、維護、升級成本指數級上升。本文從嵌入式系統實戰角度,系統闡述軟體架構設計的核心原則與落地技巧。
一、模組劃分:高內聚、低耦合的 C 語言實踐
C 語言屬結構化程式設計,模組劃分以「功能」為主軸(註:面向物件中以資料為主軸)。
模組實體定義
- 一個
.c 檔 + 一個 .h 檔
.h 僅含介面宣告(extern 函數/變數)
.c 內部函數與全域變數加 static 隱藏
鐵律:.h 檔絕不定義變數
錯誤範例:
/* module1.h */ int a = 5; // 定義,會配置記憶體
/* module1.c */ #include "module1.h"
/* module2.c */ #include "module1.h" // 每個 .c 都產生一份 a!
正確做法:
/* module1.h */ extern int a; // 僅宣告
/* module1.c */ #include "module1.h"; int a = 5; // 定義在 .c
/* module2.c */ #include "module1.h" // 共用同一個 a
嵌入式系統兩大模組類別
- 硬體驅動模組:一顆晶片對應一個模組
- 軟體功能模組:業務邏輯封裝,滿足高內聚、低耦合
二、多任務 vs. 單任務:架構選型決策
核心差異
| 特性 | 單任務系統 | 多任務系統 |
| 執行模式 | 巨觀串列 | 巨觀並行(微觀可能串列) |
| 核心依賴 | 無 OS 或簡易調度器 | 多任務 OS (VxWorks, μC/OS, Linux...) |
| 任務管理 | 主迴圈輪詢 | TCB + 排程器 + 上下文切換 |
| 適用場景 | 簡單控制、資源極受限 | 複雜系統、即時性要求高 |
決策指南
- 手機協定棧:多任務
- 小靈通協定棧:單任務(主迴圈輪詢模擬並發)
- 原則:軟體規模、即時性、硬體資源三者權衡
三、單任務系統典型架構
// 啟動流程
_reset -> startup.asm -> main()
int main() {
// 1. 硬體初始化
hw_init_all();
// 2. 軟體模組初始化
sw_module_init_all();
// 3. 進入死迴圈(超級迴圈)
while (1) { // 推薦 while(1),語意明確
module1_process();
module2_process();
// ...
}
}
死迴圈的哲學
- 作業系統是死迴圈
- Win32 訊息迴圈是死迴圈
- 嵌入式主程式是死迴圈
- 執行緒處理函數是死迴圈
系統需運行到世界末日,除非看門狗重啟。過度追求「優雅退出」反而增加複雜度。
常見反模式
for (;;) { } // 語意不明確,需知 C 語言規則才懂是無限迴圈
四、中斷服務程式 (ISR) 設計鐵律
標準 C 不支援中斷,編譯器擴充關鍵字(如 __interrupt、#pragma interrupt)。
ISR 四大原則
- 無回傳值
- 無參數傳遞
- 極短極快:只做必要硬體操作與資料緩衝
- 嚴禁呼叫
printf 等非重入/耗時函數
最佳實踐:中斷佇列 + 主迴圈分發
typedef struct IntQueue {
int intType;
struct IntQueue *next;
} IntQueue;
IntQueue *gpIntQueueHead = NULL;
__interrupt void UART_Isr() {
int type = GetInterruptType();
QueueAddTail(&gpIntQueueHead, type); // 僅入佇列
}
// 主迴圈
while (1) {
if (!IsIntQueueEmpty()) {
int type = GetFirstInt();
switch (type) { // 類訊息驅動機制
case INT_UART_RX: handle_uart_rx(); break;
case INT_TIMER: handle_timer(); break;
// ...
}
}
// 其他模組輪詢...
}
ISR 極精簡,實際處理移至主迴圈,避免中斷巢狀與阻塞問題。
五、硬體驅動模組標準化介面
每個驅動模組應包含:
- ISR
- 硬體初始化
- 設定暫存器參數(鮑率、採樣率...)
- 登記中斷向量表
- 設定 CPU 控制線(PIO/中斷觸發模式/屏蔽位)
- 操作介面函數群(如 LCD:畫點、畫線、顯示字元;RTC:讀/寫時間)
中斷向量表登記範例 (80186)
void far *m_myPtr = make_far_pointer(0L); // 0x0000:0x0000
m_myPtr += ITYPE_UART; // 偏移至 UART 向量
*m_myPtr = &UART_Isr; // 填入 ISR 位址
六、C 語言模擬物件導向:封裝資料與行為
利用結構體 + 函數指標模擬「類別」:
#define C_Class struct
C_Class A {
C_Class A *A_this; // this 指標
void (*Foo)(C_Class A *A_this); // 方法:函數指標
int a; // 資料成員
int b;
};
可進一步實現封裝、繼承、多型,但嵌入式開發中,核心價值在於解決資料與函數脫節、架構分散的問題,而非追求純粹的 OO 模擬。
總結
軟體架構決定系統成敗。模組劃分遵循高內聚低耦合,任務模型依複雜度選型,單任務採超級迴圈,中斷採佇列分離,驅動模組標準化介面,必要時以 C 模擬 OO 封裝。記住:結構清晰的程式碼,是對未來維護者最大的善意。
- 上一篇文章: C 語言指標完全掌握:從核心概念到進階運算與陣列關係
- 下一篇文章: 嵌入式 C 程式設計記憶體操作進階:指標、動態配置、關鍵字與位寬處理
Copyright ©
程式碼之家 All Rights Reserved