9種單片機常用的軟件架構(gòu)
1.線性架構(gòu)
這是最簡單的一種程序設計方法,也就是我們在入門時寫的,下面是一個使用C語言編寫的線性架構(gòu)示例:
本文引用地址:http://www.ex-cimer.com/article/202405/458995.htm#include <reg51.h> // 包含51系列單片機的寄存器定義// 延時函數(shù),用于產(chǎn)生一定的延遲void delay(unsigned int count) {unsigned int i;while(count--) {for(i = 0; i < 120; i++) {} // 空循環(huán),用于產(chǎn)生延遲}}void main() {// 初始設置P1端口為輸出模式,用于控制LEDP1 = 0xFF; // 將P1端口設置為高電平,關(guān)閉所有LEDwhile(1) { // 無限循環(huán)P1 = 0x00; // 將P1端口設置為低電平,點亮所有LEDdelay(500000); // 調(diào)用延時函數(shù),延遲一段時間P1 = 0xFF; // 將P1端口設置為高電平,關(guān)閉所有LEDdelay(500000); // 再次調(diào)用延時函數(shù),延遲相同的時間}}
2.模塊化架構(gòu)
模塊化架構(gòu)是一種將程序分解為獨立模塊的設計方法,每個模塊執(zhí)行特定的任務。
這種架構(gòu)有助于代碼的重用、維護和測試。
下面是一個使用C語言編寫的模塊化架構(gòu)示例,該程序模擬了一個簡單的交通信號燈控制系統(tǒng)。
#include <reg51.h> // 包含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),用于產(chǎn)生延時}}}
3.層次化架構(gòu)
層次化架構(gòu)是一種將系統(tǒng)分解為多個層次的設計方法,每個層次負責不同的功能。
著以下是一個使用C語言編寫的層次化架構(gòu)示例,模擬了一個具有不同權(quán)限級別的嵌入式系統(tǒng)。
#include <reg51.h> // 包含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)消息的顯示}
4.事件驅(qū)動架構(gòu)
事件驅(qū)動架構(gòu)是一種編程范式,其中程序的執(zhí)行流程由事件(如用戶輸入、傳感器變化、定時器到期等)觸發(fā)。
在單片機開發(fā)中,事件驅(qū)動架構(gòu)通常用于響應外部硬件中斷或軟件中斷。
以下是一個使用C語言編寫的事件驅(qū)動架構(gòu)示例,模擬了一個基于按鍵輸入的LED控制。
#include <reg51.h> // 包含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; // 設置定時器初值,用于產(chǎn)生定時中斷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),用于產(chǎ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(); // 調(diào)用按鍵中斷服務程序}return keyState; // 返回按鍵狀態(tài)}
事實上,真正的事件型驅(qū)動架構(gòu),是非常復雜的,我職業(yè)生涯的巔峰之作,就是用的事件型驅(qū)動架構(gòu)。
5.狀態(tài)機架構(gòu)
在單片機開發(fā)中,狀態(tài)機常用于處理復雜的邏輯和事件序列,如用戶界面管理、協(xié)議解析等。
以下是一個使用C語言編寫的有限狀態(tài)機(FSM)的示例,模擬了一個簡單的自動售貨機的狀態(tài)轉(zhuǎn)換。
#include <reg51.h> // 包含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)且檢測到硬幣投入事件,則轉(zhuǎn)換到硬幣投入狀態(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; // 關(guān)閉電機}// 返回找零的函數(shù)void returnChange(void){// 這里添加找零邏輯,例如激活機械臂放置零錢// 假設P2端口連接了找零機械臂P2 = 0x00; // 激活機械臂// ... 找零邏輯P2 = 0xFF; // 關(guān)閉機械臂}
6.面向?qū)ο蠹軜?gòu)
STM32的庫,就是一種面向?qū)ο蟮募軜?gòu)。
不過在單片機由于資源限制,OOP并不像在高級語言中那樣常見,但是一些基本概念如封裝和抽象仍然可以被應用。
雖然C語言本身并不直接支持面向?qū)ο缶幊蹋梢酝ㄟ^結(jié)構(gòu)體和函數(shù)指針模擬一些面向?qū)ο蟮奶匦浴?/p>
下面是一個簡化的示例,展示如何在C語言中模擬面向?qū)ο蟮木幊田L格,以51單片機為背景,創(chuàng)建一個簡單的LED類。
#include <reg51.h>// 定義一個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類的構(gòu)造函數(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)點亮LEDif(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)熄滅LEDif(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;// 使用面向?qū)ο蟮娘L格控制LEDwhile(1) {myLed.turnOn(&myLed); // 點亮LED// 延時myLed.turnOff(&myLed); // 熄滅LED// 延時}}
這段代碼定義了一個結(jié)構(gòu)體LED,模擬面向?qū)ο笾械摹邦悺?/p>
這個示例僅用于展示如何在C語言中模擬面向?qū)ο蟮娘L格,并沒有使用真正的面向?qū)ο缶幊陶Z言的特性,如繼承和多態(tài),不過對于單片機的應用,足以。
7.基于任務的架構(gòu)
這種我最喜歡用,結(jié)構(gòu),邏輯清晰,每個任務都能靈活調(diào)度。
基于任務的架構(gòu)是將程序分解為獨立的任務,每個任務執(zhí)行特定的工作。
在單片機開發(fā)中,如果沒有使用實時操作系統(tǒng),我們可以通過編寫一個簡單的輪詢調(diào)度器來模擬基于任務的架構(gòu)。
以下是一個使用C語言編寫的基于任務的架構(gòu)的示例,該程序在51單片機上實現(xiàn)。
為了簡化,我們將使用一個簡單的輪詢調(diào)度器來在兩個任務之間切換:一個是按鍵掃描任務,另一個是LED閃爍任務。
#include <reg51.h>// 假設P1.0是LED輸出sbit LED = P1^0;// 全局變量,用于記錄系統(tǒng)Tickunsigned int systemTick = 0;// 任務函數(shù)聲明void taskLEDBlink(void);void taskKeyScan(void);// 定時器0中斷服務程序,用于產(chǎn)生Tickvoid timer0_ISR() interrupt 1 using 1 {// 定時器溢出后自動重新加載初值,無需手動重置systemTick++; // 更新系統(tǒng)Tick計數(shù)器}// 任務調(diào)度器,主函數(shù)中調(diào)用,負責任務輪詢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; // 設置定時器初值,產(chǎn)生定時中斷(定時周期取決于系統(tǒng)時鐘頻率)TL0 = 0x00;ET0 = 1; // 允許定時器0中斷EA = 1; // 允許中斷TR0 = 1; // 啟動定時器0while(1){taskScheduler(); // 調(diào)用任務調(diào)度器}}
這里只是舉個簡單的例子,這個代碼示例,比較適合51和stm8這種資源非常少的單片機。
8.代理架構(gòu)
這個大家或許比較少聽到過,但在稍微復雜的項目中,是非常常用的。
在代理架構(gòu)中,每個代理(Agent)都是一個獨立的實體,它封裝了特定的決策邏輯和數(shù)據(jù),并與其他代理進行交互。
在實際項目中,需要創(chuàng)建多個獨立的任務或模塊,每個模塊負責特定的功能,并通過某種機制(如消息隊列、事件觸發(fā)等)進行通信。
這種方式可以大大提高程序可擴展性和可移植性。
以下是一個LED和按鍵代理的簡化模型。
#include <reg51.h> // 包含51系列單片機的寄存器定義// 假設P3.5是按鍵輸入,P1.0是LED輸出sbit KEY = P3^5;sbit LED = P1^0;typedef struct {unsigned char pin; // 代理關(guān)聯(lián)的引腳void (*action)(void); // 代理的行為函數(shù)} Agent;// 按鍵代理的行為函數(shù)聲明void keyAction(void);// LED代理的行為函數(shù)聲明void ledAction(void);// 代理數(shù)組,存儲所有代理的行為和關(guān)聯(lián)的引腳Agent agents[] ={{5, keyAction}, // 按鍵代理,關(guān)聯(lián)P3.5{0, ledAction} // LED代理,關(guān)聯(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++){// 調(diào)用每個代理的行為函數(shù)(*agents[agentIndex].action)(); // 注意函數(shù)指針的調(diào)用方式}}}
9.組件化架構(gòu)
組件化架構(gòu)是一種將軟件系統(tǒng)分解為獨立、可重用組件的方法。
將程序分割成負責特定任務的模塊,如LED控制、按鍵處理、傳感器讀數(shù)等。
每個組件可以獨立開發(fā)和測試,然后被組合在一起形成完整的系統(tǒng)。
以下是一個簡化的組件化架構(gòu)示例,模擬了一個單片機系統(tǒng)中的LED控制和按鍵輸入處理兩個組件。
為了簡化,組件間的通信將通過直接函數(shù)調(diào)用來模擬。
#include <reg51.h> // 包含51系列單片機的寄存器定義// 定義組件結(jié)構(gòu)體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)為關(guān)閉}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ù),調(diào)用所有組件的初始化函數(shù)void System_Init(void) {unsigned char componentIndex;for (componentIndex = 0; componentIndex < sizeof(components) / sizeof(components[0]); componentIndex++){components[componentIndex].init();}}// 主循環(huán),調(diào)用所有組件的任務函數(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(); // 調(diào)用組件任務}}}
評論