文章目錄
- 一、前后臺系統(Foreground-Background System)
- 二、時間片輪詢架構(Time-Slicing Polling)
- 三、狀態機架構(State Machine)
- 四、事件驅動架構(Event-Driven)
- 五、架構設計原則
- 總結
單片機裸機程序(無操作系統的單片機程序)的架構設計直接影響程序的
可靠性、可維護性和擴展性。由于單片機資源有限(內存、算力、外設等),裸機架構需遵循“簡潔高效、任務可控”的原則,常見架構主要有以下幾種:
一、前后臺系統(Foreground-Background System)
最基礎、最常用的架構,適合任務簡單、實時性要求不高的場景。
- 前臺(Foreground):由中斷服務程序(ISR)組成,負責處理緊急事件(如外部觸發、定時器溢出、數據接收等),優先級最高,執行時間需盡可能短(避免阻塞其他中斷)。
- 后臺(Background):即主循環(main loop),負責處理非緊急的常規任務(如數據處理、狀態刷新、外設控制等),按順序循環執行。
- 協作方式:前臺通過設置標志位(flag)通知后臺處理任務,后臺在主循環中輪詢標志位,觸發對應處理邏輯。
示例流程:
// 全局標志位(前臺通知后臺的“信號”)
uint8_t uart_rx_flag = 0; // 串口接收完成標志
uint8_t key_press_flag = 0; // 按鍵按下標志int main(void) {// 初始化:外設、中斷、全局變量等init_uart();init_key();init_led();while(1) { // 后臺主循環if(uart_rx_flag) {uart_rx_flag = 0;process_uart_data(); // 處理串口數據(非緊急)}if(key_press_flag) {key_press_flag = 0;handle_key_event(); // 處理按鍵事件(非緊急)}update_led_status(); // 周期性刷新LED(常規任務)}
}// 串口接收中斷服務程序(前臺)
void UART_IRQHandler(void) {if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {uart_rx_buf = USART_ReceiveData(USART1);uart_rx_flag = 1; // 設置標志位,通知后臺處理USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}
優點:結構簡單,資源占用少,易于實現。
缺點:后臺任務按順序執行,若某個任務耗時過長,會阻塞后續任務,實時性差。
二、時間片輪詢架構(Time-Slicing Polling)
適合多任務周期性執行的場景(如工業控制、傳感器采集),通過定時器劃分“時間片”,按固定周期輪流執行任務。
- 核心思想:用定時器(如SysTick)產生固定間隔的中斷(如1ms),在中斷中更新“系統時基”,并為每個任務分配“時間片”(執行周期)。
- 任務調度:主循環中輪詢所有任務,若任務的“時間片到期”(當前時基達到任務周期),則執行該任務,執行完成后等待下一個周期。
提示:下面代碼中有Bug: ------- sys_tick 溢出就不準拉
示例流程:
// 任務結構體:包含周期、上次執行時間、處理函數
typedef struct {uint32_t period; // 任務周期(ms)uint32_t last_run; // 上次執行時間(時基)void (*func)(void); // 任務處理函數
} TaskType;// 系統時基(由定時器中斷更新)
uint32_t sys_tick = 0;// 任務列表
TaskType tasks[] = {{10, 0, task_sensor_read}, // 10ms讀取一次傳感器{100, 0, task_data_report}, // 100ms上報一次數據{500, 0, task_led_flash}, // 500ms閃爍一次LED
};
#define TASK_NUM (sizeof(tasks)/sizeof(TaskType))// 定時器中斷(1ms觸發一次,更新時基)
void SysTick_Handler(void) {sys_tick++;
}int main(void) {init_sys();SysTick_Config(SystemCoreClock / 1000); // 配置1ms時基while(1) {for(int i=0; i<TASK_NUM; i++) {// 檢查任務是否到執行時間if(sys_tick - tasks[i].last_run >= tasks[i].period) {tasks[i].last_run = sys_tick; // 更新上次執行時間tasks[i].func(); // 執行任務}}}
}// 任務實現示例
void task_sensor_read(void) {// 讀取溫濕度傳感器...
}
優點:任務執行周期可控,避免單任務阻塞,適合周期性任務。
缺點:任務仍在主循環中執行,若某任務執行時間超過其周期,會影響后續任務;無優先級區分,實時性中等。
三、狀態機架構(State Machine)
適合邏輯復雜、狀態多變的單任務場景(如設備啟停流程、人機交互),將任務分解為“狀態”,通過條件判斷切換狀態,避免嵌套邏輯混亂。
- 核心思想:將任務劃分為多個“狀態”(如初始化、運行、暫停、故障),每個狀態對應特定的處理邏輯,通過“狀態變量”記錄當前狀態,在主循環中根據輸入(事件)切換狀態。
- 分類:
- 單狀態機:單個任務的狀態管理(如一個傳感器的工作流程);
- 層次狀態機(HSM):復雜任務的狀態嵌套(如主狀態包含子狀態)。
示例流程:
// 定義狀態枚舉
typedef enum {DEVICE_INIT, // 初始化狀態DEVICE_RUN, // 運行狀態DEVICE_PAUSE, // 暫停狀態DEVICE_ERROR // 錯誤狀態
} DeviceState;DeviceState current_state = DEVICE_INIT; // 當前狀態
uint8_t error_flag = 0; // 錯誤標志(觸發狀態切換)int main(void) {while(1) {switch(current_state) {case DEVICE_INIT:if(init_success()) { // 初始化成功current_state = DEVICE_RUN;start_device();} else {current_state = DEVICE_ERROR; // 初始化失敗}break;case DEVICE_RUN:if(error_flag) {current_state = DEVICE_ERROR; // 運行中出錯error_flag = 0;} else if(key_pause_pressed()) {current_state = DEVICE_PAUSE; // 暫停按鍵觸發} else {run_task(); // 執行運行狀態的邏輯}break;case DEVICE_PAUSE:if(key_resume_pressed()) {current_state = DEVICE_RUN; // 恢復運行}break;case DEVICE_ERROR:handle_error(); // 錯誤處理(如報警、重啟)break;}}
}
優點:邏輯清晰,避免深層嵌套(if-else/switch-case),易于調試和擴展。
缺點:主要適用于單任務,多任務需結合其他架構(如時間片)。
四、事件驅動架構(Event-Driven)
適合多外部事件響應的場景(如按鍵、通信、傳感器觸發),基于“事件”觸發任務,而非輪詢,提高CPU效率。
- 核心思想:定義“事件”(如按鍵按下、數據接收、超時等),每個事件關聯對應的“回調函數”;通過中斷或輪詢檢測事件,觸發回調執行。
- 實現方式:用事件隊列(FIFO)存儲事件,主循環不斷從隊列中取出事件并分發處理,支持事件優先級。
示例流程:
// 事件類型定義
typedef enum {EVENT_KEY_PRESS, // 按鍵事件EVENT_UART_RX, // 串口接收事件EVENT_TIMEOUT // 超時事件
} EventType;// 事件結構體
typedef struct {EventType type; // 事件類型void* data; // 事件附加數據(如按鍵值、接收數據)
} Event;// 事件隊列(FIFO)
#define EVENT_QUEUE_SIZE 10
Event event_queue[EVENT_QUEUE_SIZE];
uint8_t queue_head = 0, queue_tail = 0;// 入隊事件
void event_enqueue(Event event) {uint8_t next_tail = (queue_tail + 1) % EVENT_QUEUE_SIZE;if(next_tail != queue_head) { // 隊列未滿event_queue[queue_tail] = event;queue_tail = next_tail;}
}// 主循環:事件分發
int main(void) {init_periph(); // 初始化外設和中斷while(1) {if(queue_head != queue_tail) { // 隊列非空Event event = event_queue[queue_head];queue_head = (queue_head + 1) % EVENT_QUEUE_SIZE; // 出隊// 分發事件到對應處理函數switch(event.type) {case EVENT_KEY_PRESS:handle_key((uint8_t*)event.data);break;case EVENT_UART_RX:handle_uart((uint8_t*)event.data);break;case EVENT_TIMEOUT:handle_timeout();break;}}}
}// 按鍵中斷:觸發事件入隊
void KEY_IRQHandler(void) {Event event;event.type = EVENT_KEY_PRESS;event.data = (void*)get_key_value(); // 獲取按鍵值event_enqueue(event); // 事件入隊
}
優點:CPU利用率高(無輪詢空耗),事件響應靈活,支持多事件并行處理。
缺點:需實現事件隊列和分發邏輯,復雜度高于前后臺系統。
五、架構設計原則
- 模塊化:按功能拆分模塊(如uart.c、key.c、sensor.c),模塊間通過接口通信,降低耦合。
- 資源管理:明確外設(GPIO、定時器、DMA)的歸屬,避免沖突(如用“資源鎖”保護共享外設)。
- 中斷管理:中斷僅做“最短處理”(如置標志、存數據),復雜邏輯放后臺;合理設置中斷優先級,避免嵌套過深。
- 可擴展性:預留接口(如任務注冊、事件類型),方便后期增加功能。
- 健壯性:處理邊界情況(如隊列滿、數據溢出、超時),避免程序崩潰。
總結
- 簡單場景(如LED控制、單傳感器采集):優先用前后臺系統;
- 多周期性任務(如定時采集+上報):用時間片輪詢;
- 復雜邏輯任務(如設備狀態切換):用狀態機;
- 多事件響應(如多按鍵+多通信):用事件驅動。
實際開發中,常結合多種架構(如“時間片+狀態機”“事件驅動+前后臺”),以適應具體需求。