可能很多工程師,工作了很多年,都不會有軟件架構的概念。 因為我在做研發工程師的第6年,才開始意識到這個東西,在此之前,都是做一些比較簡單的項目,一個main函數干到底,架構復雜了反而是累贅。 后面有幸,接觸了稍微復雜點的項目,感覺以前水平Hold不住,然后借著項目需求,學習了很多優秀的代碼架構,比如以前同事的,一些模組廠的SDK,還有市面上成熟的系統。 說出來可能有點夸張,一個好項目帶來的成長,頂你做幾年小項目。 在一個工程師從入門到成為高級工程師,都會經歷哪些軟件架構?
下面給大家盤點一下,每個都提供了簡易的架構模型代碼,難度循環漸進。
1.線性架構
這是最簡單的一種程序設計方法,也就是我們在入門時寫的,下面是一個使用C語言編寫的線性架構示例:
#include? 2.模塊化架構 模塊化架構是一種將程序分解為獨立模塊的設計方法,每個模塊執行特定的任務。 這種架構有助于代碼的重用、維護和測試。 下面是一個使用C語言編寫的模塊化架構示例,該程序模擬了一個簡單的交通信號燈控制系統。// 包含51系列單片機的寄存器定義 // 延時函數,用于產生一定的延遲 void delay(unsigned int count) { unsigned int i; while(count--) { for(i = 0; i < 120; i++) {} // 空循環,用于產生延遲 } } void main() { // 初始設置P1端口為輸出模式,用于控制LED P1 = 0xFF; // 將P1端口設置為高電平,關閉所有LED while(1) { // 無限循環 P1 = 0x00; // 將P1端口設置為低電平,點亮所有LED delay(500000); // 調用延時函數,延遲一段時間 P1 = 0xFF; // 將P1端口設置為高電平,關閉所有LED delay(500000); // 再次調用延時函數,延遲相同的時間 } }
#include// 包含51系列單片機的寄存器定義 // 定義信號燈的狀態 typedef enum { RED_LIGHT, YELLOW_LIGHT, GREEN_LIGHT } TrafficLightState; // 函數聲明 void initializeTrafficLight(void); void setTrafficLight(TrafficLightState state); void delay(unsigned int milliseconds); // 信號燈控制主函數 void main(void) { initializeTrafficLight(); // 初始化交通信號燈 while(1) { setTrafficLight(RED_LIGHT); delay(5000); // 紅燈亮5秒 setTrafficLight(YELLOW_LIGHT); delay(2000); // 黃燈亮2秒 setTrafficLight(GREEN_LIGHT); delay(5000); // 綠燈亮5秒 } } // 初始化交通信號燈的函數 void initializeTrafficLight(void) { // 這里可以添加初始化代碼,比如設置端口方向、默認狀態等 // 假設P1端口連接了信號燈,初始狀態為熄滅(高電平) P1 = 0xFF; } // 設置交通信號燈狀態的函數 void setTrafficLight(TrafficLightState state) { switch(state) { case RED_LIGHT: // 設置紅燈亮,其他燈滅 P1 = 0b11100000; // 假設低電平有效,這里設置P1.0為低電平,其余為高電平 break; case YELLOW_LIGHT: // 設置黃燈亮,其他燈滅 P1 = 0b11011000; // 設置P1.1為低電平,其余為高電平 break; case GREEN_LIGHT: // 設置綠燈亮,其他燈滅 P1 = 0b11000111; // 設置P1.2為低電平,其余為高電平 break; default: // 默認為熄滅所有燈 P1 = 0xFF; break; } } // 延時函數,參數是毫秒數 void delay(unsigned int milliseconds) { unsigned int delayCount = 0; while(milliseconds--) { for(delayCount = 0; delayCount < 120; delayCount++) { // 空循環,用于產生延時 } } }
3.層次化架構 層次化架構是一種將系統分解為多個層次的設計方法,每個層次負責不同的功能。
著以下是一個使用C語言編寫的層次化架構示例,模擬了一個具有不同權限級別的嵌入式系統。
#include4.事件驅動架構// 包含51系列單片機的寄存器定義 // 定義不同的操作級別 typedef enum { LEVEL_USER, LEVEL_ADMIN, LEVEL_SUPERUSER } OperationLevel; // 函數聲明 void systemInit(void); void performOperation(OperationLevel level); void displayMessage(char* message); // 系統初始化后的主循環 void main(void) { systemInit(); // 系統初始化 // 模擬用戶操作 performOperation(LEVEL_USER); // 模擬管理員操作 performOperation(LEVEL_ADMIN); // 模擬超級用戶操作 performOperation(LEVEL_SUPERUSER); while(1) { // 主循環可以是空閑循環或者處理其他低優先級任務 } } // 系統初始化函數 void systemInit(void) { // 初始化系統資源,如設置端口、中斷等 // 這里省略具體的初始化代碼 } // 執行不同級別操作的函數 void performOperation(OperationLevel level) { switch(level) { case LEVEL_USER: //用戶操作具體代碼 break; case LEVEL_ADMIN: //管理員操作具體代碼 break; case LEVEL_SUPERUSER: //超級用戶操作具體代碼 break; } } // 顯示消息的函數 void displayMessage(char* message) { // 這里省略了實際的顯示代碼,因為單片機通常沒有直接的屏幕輸出 // 消息可以通過LED閃爍、串口輸出或其他方式展示 // 假設通過P1端口的LED展示,每個字符對應一個LED閃爍模式 // 實際應用中,需要根據硬件設計來實現消息的顯示 }
事件驅動架構是一種編程范式,其中程序的執行流程由事件(如用戶輸入、傳感器變化、定時器到期等)觸發。 在單片機開發中,事件驅動架構通常用于響應外部硬件中斷或軟件中斷。 以下是一個使用C語言編寫的事件驅動架構示例,模擬了一個基于按鍵輸入的LED控制。
#include事實上,真正的事件型驅動架構,是非常復雜的,我職業生涯的巔峰之作,就是用的事件型驅動架構。// 包含51系列單片機的寄存器定義 // 定義按鍵和LED的狀態 #define KEY_PORT P3 // 假設按鍵連接在P3端口 #define LED_PORT P2 // 假設LED連接在P2端口 // 函數聲明 void delay(unsigned int milliseconds); bit checkKeyPress(void); // 返回按鍵是否被按下的狀態(1表示按下,0表示未按下) // 定時器初始化函數 void timer0Init(void) { TMOD = 0x01; // 設置定時器模式寄存器,使用模式1(16位定時器) TH0 = 0xFC; // 設置定時器初值,用于產生定時中斷 TL0 = 0x18; ET0 = 1; // 開啟定時器0中斷 EA = 1; // 開啟總中斷 TR0 = 1; // 啟動定時器 } // 定時器中斷服務程序 void timer0_ISR() interrupt 1 { // 定時器溢出后自動重新加載初值,無需手動重置 // 這里可以放置定時器溢出后需要執行的代碼 } // 按鍵中斷服務程序 bit keyPress_ISR(void) interrupt 2 using 1 { if(KEY_PORT != 0xFF) // 檢測是否有按鍵按下 { LED_PORT = ~LED_PORT; // 如果有按鍵按下,切換LED狀態 delay(20); // 去抖動延時 while(KEY_PORT != 0xFF); // 等待按鍵釋放 return 1; // 返回按鍵已按下 } return 0; // 如果沒有按鍵按下,返回0 } // 延時函數,參數是毫秒數 void delay(unsigned int milliseconds) { unsigned int i, j; for(i = 0; i < milliseconds; i++) for(j = 0; j < 1200; j++); // 空循環,用于產生延時 } // 主函數 void main(void) { timer0Init(); // 初始化定時器 LED_PORT = 0xFF; // 初始LED熄滅(假設低電平點亮LED) while(1) { if(checkKeyPress()) { // 檢查是否有按鍵按下事件 // 如果有按鍵按下,這里可以添加額外的處理代碼 } } } // 檢查按鍵是否被按下的函數 bit checkKeyPress(void) { bit keyState = 0; // 模擬按鍵中斷觸發,實際應用中需要連接硬件中斷 if(1) // 假設按鍵中斷觸發 { keyState = keyPress_ISR(); // 調用按鍵中斷服務程序 } return keyState; // 返回按鍵狀態 }
5.狀態機架構 在單片機開發中,狀態機常用于處理復雜的邏輯和事件序列,如用戶界面管理、協議解析等。 以下是一個使用C語言編寫的有限狀態機(FSM)的示例,模擬了一個簡單的自動售貨機的狀態轉換。
#include// 包含51系列單片機的寄存器定義 // 定義自動售貨機的狀態 typedef enum { IDLE, COIN_INSERTED, PRODUCT_SELECTED, DISPENSE, CHANGE_RETURNED } VendingMachineState; // 定義事件 typedef enum { COIN_EVENT, PRODUCT_EVENT, DISPENSE_EVENT, REFUND_EVENT } VendingMachineEvent; // 函數聲明 void processEvent(VendingMachineEvent event); void dispenseProduct(void); void returnChange(void); // 當前狀態 VendingMachineState currentState = IDLE; // 主函數 void main(void) { // 初始化代碼(如果有) // ... while(1) { // 假設事件由外部觸發,這里使用一個模擬事件 VendingMachineEvent currentEvent = COIN_EVENT; // 模擬投入硬幣事件 processEvent(currentEvent); // 處理當前事件 } } // 處理事件的函數 void processEvent(VendingMachineEvent event) { switch(currentState) { case IDLE: if(event == COIN_EVENT) { // 如果在空閑狀態且檢測到硬幣投入事件,則轉換到硬幣投入狀態 currentState = COIN_INSERTED; } break; case COIN_INSERTED: if(event == PRODUCT_EVENT) { // 如果在硬幣投入狀態且用戶選擇商品,則請求出貨 currentState = PRODUCT_SELECTED; } break; case PRODUCT_SELECTED: if(event == DISPENSE_EVENT) { dispenseProduct(); // 出貨商品 currentState = DISPENSE; } break; case DISPENSE: if(event == REFUND_EVENT) { returnChange(); // 返回找零 currentState = CHANGE_RETURNED; } break; case CHANGE_RETURNED: // 等待下一個循環,返回到IDLE狀態 currentState = IDLE; break; default: // 如果狀態非法,重置為IDLE狀態 currentState = IDLE; break; } } // 出貨商品的函數 void dispenseProduct(void) { // 這里添加出貨邏輯,例如激活電機推出商品 // 假設P1端口連接了出貨電機 P1 = 0x00; // 激活電機 // ... 出貨邏輯 P1 = 0xFF; // 關閉電機 } // 返回找零的函數 void returnChange(void) { // 這里添加找零邏輯,例如激活機械臂放置零錢 // 假設P2端口連接了找零機械臂 P2 = 0x00; // 激活機械臂 // ... 找零邏輯 P2 = 0xFF; // 關閉機械臂 }
6.面向對象架構 STM32的庫,就是一種面向對象的架構。 不過在單片機由于資源限制,OOP并不像在高級語言中那樣常見,但是一些基本概念如封裝和抽象仍然可以被應用。
雖然C語言本身并不直接支持面向對象編程,但可以通過結構體和函數指針模擬一些面向對象的特性。 下面是一個簡化的示例,展示如何在C語言中模擬面向對象的編程風格,以51單片機為背景,創建一個簡單的LED類。
#include這段代碼定義了一個結構體LED,模擬面向對象中的“類。 這個示例僅用于展示如何在C語言中模擬面向對象的風格,并沒有使用真正的面向對象編程語言的特性,如繼承和多態,不過對于單片機的應用,足以。 7.基于任務的架構 這種我最喜歡用,結構,邏輯清晰,每個任務都能靈活調度。// 定義一個LED類 typedef struct { unsigned char state; // LED的狀態 unsigned char pin; // LED連接的引腳 void (*turnOn)(struct LED*); // 點亮LED的方法 void (*turnOff)(struct LED*); // 熄滅LED的方法 } LED; // LED類的構造函數 void LED_Init(LED* led, unsigned char pin) { led->state = 0; // 默認狀態為熄滅 led->pin = pin; // 設置LED連接的引腳 } // 點亮LED的方法 void LED_TurnOn(LED* led) { // 根據引腳狀態點亮LED if(led->pin < 8) { P0 |= (1 << led->pin); // 假設P0.0到P0.7連接了8個LED } else { P1 &= ~(1 << (led->pin - 8)); // 假設P1.0到P1.7連接了另外8個LED } led->state = 1; // 更新狀態為點亮 } // 熄滅LED的方法 void LED_TurnOff(LED* led) { // 根據引腳狀態熄滅LED if(led->pin < 8) { P0 &= ~(1 << led->pin); // 熄滅P0上的LED } else { P1 |= (1 << (led->pin - 8)); // 熄滅P1上的LED } led->state = 0; // 更新狀態為熄滅 } // 主函數 void main(void) { LED myLed; // 創建一個LED對象 LED_Init(&myLed, 3); // 初始化LED對象,連接在P0.3 // 給LED對象綁定方法 myLed.turnOn = LED_TurnOn; myLed.turnOff = LED_TurnOff; // 使用面向對象的風格控制LED while(1) { myLed.turnOn(&myLed); // 點亮LED // 延時 myLed.turnOff(&myLed); // 熄滅LED // 延時 } }
基于任務的架構是將程序分解為獨立的任務,每個任務執行特定的工作。
在單片機開發中,如果沒有使用實時操作系統,我們可以通過編寫一個簡單的輪詢調度器來模擬基于任務的架構。
以下是一個使用C語言編寫的基于任務的架構的示例,該程序在51單片機上實現。
為了簡化,我們將使用一個簡單的輪詢調度器來在兩個任務之間切換:一個是按鍵掃描任務,另一個是LED閃爍任務。
#include這里只是舉個簡單的例子,這個代碼示例,比較適合51和stm8這種資源非常少的單片機。// 假設P1.0是LED輸出 sbit LED = P1^0; // 全局變量,用于記錄系統Tick unsigned int systemTick = 0; // 任務函數聲明 void taskLEDBlink(void); void taskKeyScan(void); // 定時器0中斷服務程序,用于產生Tick void timer0_ISR() interrupt 1 using 1 { // 定時器溢出后自動重新加載初值,無需手動重置 systemTick++; // 更新系統Tick計數器 } // 任務調度器,主函數中調用,負責任務輪詢 void taskScheduler(void) { // 檢查系統Tick,決定是否執行任務 // 例如,如果我們需要每1000個Tick執行一次LED閃爍任務 if (systemTick % 1000 == 0) { taskLEDBlink(); } // 如果有按鍵任務,可以類似地檢查Tick并執行 if (systemTick % 10 == 0) { taskKeyScan(); } } // LED閃爍任務 void taskLEDBlink(void) { static bit ledState = 0; // 用于記錄LED的當前狀態 ledState = !ledState; // 切換LED狀態 LED = ledState; // 更新LED硬件狀態 } // 按鍵掃描任務(示例中省略具體實現) void taskKeyScan(void) { // 按鍵掃描邏輯 } // 主函數 void main(void) { // 初始化LED狀態 LED = 0; // 定時器0初始化設置 TMOD &= 0xF0; // 設置定時器模式寄存器,使用模式1(16位定時器/計數器) TH0 = 0x4C; // 設置定時器初值,產生定時中斷(定時周期取決于系統時鐘頻率) TL0 = 0x00; ET0 = 1; // 允許定時器0中斷 EA = 1; // 允許中斷 TR0 = 1; // 啟動定時器0 while(1) { taskScheduler(); // 調用任務調度器 } }
8.代理架構 這個大家或許比較少聽到過,但在稍微復雜的項目中,是非常常用的。
在代理架構中,每個代理(Agent)都是一個獨立的實體,它封裝了特定的決策邏輯和數據,并與其他代理進行交互。
在實際項目中,需要創建多個獨立的任務或模塊,每個模塊負責特定的功能,并通過某種機制(如消息隊列、事件觸發等)進行通信。
這種方式可以大大提高程序可擴展性和可移植性。
以下是一個LED和按鍵代理的簡化模型。
#include// 包含51系列單片機的寄存器定義 // 假設P3.5是按鍵輸入,P1.0是LED輸出 sbit KEY = P3^5; sbit LED = P1^0; typedef struct { unsigned char pin; // 代理關聯的引腳 void (*action)(void); // 代理的行為函數 } Agent; // 按鍵代理的行為函數聲明 void keyAction(void); // LED代理的行為函數聲明 void ledAction(void); // 代理數組,存儲所有代理的行為和關聯的引腳 Agent agents[] = { {5, keyAction}, // 按鍵代理,關聯P3.5 {0, ledAction} // LED代理,關聯P1.0 }; // 按鍵代理的行為函數 void keyAction(void) { if(KEY == 0) // 檢測按鍵是否被按下 { LED = !LED; // 如果按鍵被按下,切換LED狀態 while(KEY == 0); // 等待按鍵釋放 } } // LED代理的行為函數 void ledAction(void) { static unsigned int toggleCounter = 0; toggleCounter++; if(toggleCounter == 500) // 假設每500個時鐘周期切換一次LED { LED = !LED; // 切換LED狀態 toggleCounter = 0; // 重置計數器 } } // 主函數 void main(void) { unsigned char agentIndex; // 主循環 while(1) { for(agentIndex = 0; agentIndex < sizeof(agents) / sizeof(agents[0]); agentIndex++) { // 調用每個代理的行為函數 (*agents[agentIndex].action)(); // 注意函數指針的調用方式 } } }
9.組件化架構 組件化架構是一種將軟件系統分解為獨立、可重用組件的方法。
將程序分割成負責特定任務的模塊,如LED控制、按鍵處理、傳感器讀數等。
每個組件可以獨立開發和測試,然后被組合在一起形成完整的系統。
以下是一個簡化的組件化架構示例,模擬了一個單片機系統中的LED控制和按鍵輸入處理兩個組件。
為了簡化,組件間的通信將通過直接函數調用來模擬。
#include審核編輯:黃飛// 包含51系列單片機的寄存器定義 // 定義組件結構體 typedef struct { void (*init)(void); // 組件初始化函數 void (*task)(void); // 組件任務函數 } Component; // 假設P3.5是按鍵輸入,P1.0是LED輸出 sbit KEY = P3^5; sbit LED = P1^0; // LED組件 void LED_Init(void) { LED = 0; // 初始化LED狀態為關閉 } void LED_Task(void) { static unsigned int toggleCounter = 0; toggleCounter++; if (toggleCounter >= 1000) // 假設每1000個時鐘周期切換一次LED { LED = !LED; // 切換LED狀態 toggleCounter = 0; // 重置計數器 } } // 按鍵組件 void KEY_Init(void) { // 按鍵初始化代碼 } void KEY_Task(void) { if (KEY == 0) // 檢測按鍵是否被按下 { LED = !LED; // 如果按鍵被按下,切換LED狀態 while(KEY == 0); // 等待按鍵釋放 } } // 組件數組,存儲系統中所有組件的初始化和任務函數 Component components[] = { {LED_Init, LED_Task}, {KEY_Init, KEY_Task} }; // 系統初始化函數,調用所有組件的初始化函數 void System_Init(void) { unsigned char componentIndex; for (componentIndex = 0; componentIndex < sizeof(components) / sizeof(components[0]); componentIndex++) { components[componentIndex].init(); } } // 主循環,調用所有組件的任務函數 void main(void) { System_Init(); // 系統初始化 while(1) { unsigned char componentIndex; for (componentIndex = 0; componentIndex < sizeof(components) / sizeof(components[0]); componentIndex++) { components[componentIndex].task(); // 調用組件任務 } } }
-
單片機
+關注
關注
6032文章
44521瀏覽量
633086 -
寄存器
+關注
關注
31文章
5322瀏覽量
120018 -
嵌入式系統
+關注
關注
41文章
3568瀏覽量
129234 -
C語言
+關注
關注
180文章
7599瀏覽量
136213
原文標題:長文干貨預警 | 單片機常用的9種軟件架構!
文章出處:【微信號:nanshuqg,微信公眾號:無際單片機編程】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論