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

單片機學習和開發(fā)常用的軟件合集免費下載

單片機開發(fā)會用到的常用軟件合集

評論