前言
為了能夠使得產(chǎn)品得到更好的開發(fā)速度與以后更好的迭代和移植,框架分層是很有必要的。但如對于中小型項目嚴格遵循這些原則,勢必會消耗過多精力去思考怎么設(shè)計系統(tǒng),這是一個抉擇的過程。
一、框架分層是什么?
在嵌入式架構(gòu)中:一般分為硬件架構(gòu)與軟件架構(gòu)。這里是嵌入式軟件設(shè)計,也是大多數(shù)人接觸的設(shè)計。
所謂的分層,也可以理解為模塊化的設(shè)計,但是框架分層的設(shè)計一般會遵循以下幾點原則
每個模塊提供的接口要統(tǒng)一,只能增加,不能改。在設(shè)計的時候得考慮好兼容性,使用起來麻煩不麻煩等等。
同一級模塊與模塊之間相互獨立,互不影響,不能相互調(diào)用,只能調(diào)用它下一層的接口。
不同模塊構(gòu)成不同的層,層與層之間不能跨級調(diào)用。
模塊中又可以繼續(xù)分層,可以增減分層,這個需要根據(jù)自己的項目需求來進行設(shè)置。
一般可以分為:硬件驅(qū)動層–>功能模塊層–>應(yīng)用接口層–>業(yè)務(wù)邏輯層–>應(yīng)用層
讓我們看看這個經(jīng)典的圖,簡單了解一下框架分層。
從圖中不難觀察出,設(shè)計都是遵循設(shè)計的原則的,層與層之間不能相互調(diào)用。
二、框架分層的優(yōu)劣勢
1.優(yōu)勢
單一職責(zé):每一層只負責(zé)一個職責(zé),職責(zé)邊界清晰,不會造成跨級調(diào)用,在大型項目中,每個人負責(zé)的部分不一樣,加快整個項目的開發(fā)進度。
高內(nèi)聚:分層是把相同的職責(zé)放在同一個層中,所有業(yè)務(wù)邏輯內(nèi)聚在領(lǐng)域?qū)印T跍y試的時候,只需要測試該領(lǐng)域的層即可,一般不需要考慮其他層的問題。
低耦合:依賴關(guān)系非常簡單,上層只能依賴于下層,沒有循環(huán)依賴。
易維護:面對變更容易修改。在平臺更改后,如果只是改了驅(qū)動,其他層都不需要動,只需要把驅(qū)動層給更改,其他層的功能不需要更改。
易復(fù)用:如果功能模塊變動了,只需升級相應(yīng)的功能模塊,其他的模塊不受影響,應(yīng)用層也不受影響。
如果想要更好地利用這些優(yōu)勢,那得嚴格遵循設(shè)計的原則。
2.劣勢
開發(fā)成本高:因為多層分別承擔(dān)各自的職責(zé),增加功能需要在多個層增加代碼,這樣難免會增加開發(fā)成本。但是合理的抽象,根據(jù)自己的項目設(shè)置合理的層級是能降低開發(fā)成本的。
性能略低:業(yè)務(wù)流需要經(jīng)過多層代碼的處理,性能會有所消耗。
可擴展性低:因為上下層之間存在耦合度,有些功能變化可能涉及到多層的修改。
有優(yōu)勢也有劣勢,需要根據(jù)自己的項目需要,進行部分的取舍,如果是中小型項目,可以不需要分層(如果不考慮到以后會迭代的話),或者部分分層就夠了,既能利用框架分層的部分優(yōu)勢,也能降低開發(fā)成本。
三、一個簡單的例子
由于主要討論的是軟件框架的分層設(shè)計,這里使用STM32cubemx來進行硬件的初始化,盡可能少考慮到硬件驅(qū)動的部分。
以一個智能小燈的作為例子:
功能
按鍵控制小燈的亮度,等級為:0,1,2,3
串口可以觀察當(dāng)前小燈亮度等級
OLED也可以觀察當(dāng)前小燈亮度等級
下面就是這個例子的一個簡單的圖示。
這和例子比較簡單,業(yè)務(wù)邏輯層完全可以去除,直接從應(yīng)用層調(diào)用功能模塊層,加快開發(fā)進度。
最后附上一點點代碼,就是關(guān)于LED如何進行在不同層進行封裝
硬件層
首先看HAL庫生成提供的代碼,這個就是LED硬件層,也就是GPIO層,cubemx已經(jīng)生成了,在stm32f4xx_hal_gpio.c(我用的是F4),以及有相應(yīng)的GPIO的驅(qū)動了,這里不需要我們進行處理。
硬件層驅(qū)動層
看LED部分的驅(qū)動,也就是下面的這兩個函數(shù)
voidMX_TIM1_Init(void); voidHAL_TIM_MspPostInit(TIM_HandleTypeDef*timHandle); 12 /*TIM1initfunction*/ voidMX_TIM1_Init(void) { /*USERCODEBEGINTIM1_Init0*/ /*USERCODEENDTIM1_Init0*/ TIM_ClockConfigTypeDefsClockSourceConfig={0}; TIM_MasterConfigTypeDefsMasterConfig={0}; TIM_OC_InitTypeDefsConfigOC={0}; TIM_BreakDeadTimeConfigTypeDefsBreakDeadTimeConfig={0}; /*USERCODEBEGINTIM1_Init1*/ /*USERCODEENDTIM1_Init1*/ htim1.Instance=TIM1; htim1.Init.Prescaler=168-1; htim1.Init.CounterMode=TIM_COUNTERMODE_UP; htim1.Init.Period=10000; htim1.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter=0; htim1.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE; if(HAL_TIM_Base_Init(&htim1)!=HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource=TIM_CLOCKSOURCE_INTERNAL; if(HAL_TIM_ConfigClockSource(&htim1,&sClockSourceConfig)!=HAL_OK) { Error_Handler(); } if(HAL_TIM_PWM_Init(&htim1)!=HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger=TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode=TIM_MASTERSLAVEMODE_DISABLE; if(HAL_TIMEx_MasterConfigSynchronization(&htim1,&sMasterConfig)!=HAL_OK) { Error_Handler(); } sConfigOC.OCMode=TIM_OCMODE_PWM1; sConfigOC.Pulse=0; sConfigOC.OCPolarity=TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity=TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode=TIM_OCFAST_DISABLE; sConfigOC.OCIdleState=TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState=TIM_OCNIDLESTATE_RESET; if(HAL_TIM_PWM_ConfigChannel(&htim1,&sConfigOC,TIM_CHANNEL_2)!=HAL_OK) { Error_Handler(); } sBreakDeadTimeConfig.OffStateRunMode=TIM_OSSR_DISABLE; sBreakDeadTimeConfig.OffStateIDLEMode=TIM_OSSI_DISABLE; sBreakDeadTimeConfig.LockLevel=TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime=0; sBreakDeadTimeConfig.BreakState=TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity=TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.AutomaticOutput=TIM_AUTOMATICOUTPUT_DISABLE; if(HAL_TIMEx_ConfigBreakDeadTime(&htim1,&sBreakDeadTimeConfig)!=HAL_OK) { Error_Handler(); } /*USERCODEBEGINTIM1_Init2*/ /*USERCODEENDTIM1_Init2*/ HAL_TIM_MspPostInit(&htim1); } voidHAL_TIM_MspPostInit(TIM_HandleTypeDef*timHandle) { GPIO_InitTypeDefGPIO_InitStruct={0}; if(timHandle->Instance==TIM1) { /*USERCODEBEGINTIM1_MspPostInit0*/ /*USERCODEENDTIM1_MspPostInit0*/ __HAL_RCC_GPIOE_CLK_ENABLE(); /**TIM1GPIOConfiguration PE11------>TIM1_CH2 */ GPIO_InitStruct.Pin=GPIO_PIN_11; GPIO_InitStruct.Mode=GPIO_MODE_AF_PP; GPIO_InitStruct.Pull=GPIO_NOPULL; GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate=GPIO_AF1_TIM1; HAL_GPIO_Init(GPIOE,&GPIO_InitStruct); /*USERCODEBEGINTIM1_MspPostInit1*/ /*USERCODEENDTIM1_MspPostInit1*/ } } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
對其進行封裝,就是我們想要的Led小燈的驅(qū)動了,到時候如果需要,改驅(qū)動直接改底層就行了。
voidLed_init() { MX_TIM1_Init(); HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);//啟動PWM } 12345
功能模塊層
根據(jù)上面的需求要求劃分為四個不同等級,同時也需要對LED驅(qū)動進行進一步封裝,以便滿足層與層之間不能跨級調(diào)用的原則(到這里是不是發(fā)現(xiàn)很麻煩!小項目就不要用啦!)
//ARR計數(shù)器設(shè)置值為0~10000 #defineLED_GRADE_00 #defineLED_GRADE_13000 #defineLED_GRADE_26000 #defineLED_GRADE_310000 //設(shè)置LED亮度功能 voidLed_Set_brightness(intGrade) { if(Grade==LED_GRADE_0) { __HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,Grade); HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_2);//關(guān)閉PWM輸出 } else { HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2,Grade); __HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,Grade); } } //啟動LED功能 voidLed_Start() { Led_init(); } 12345678910111213141516171819202122232425
業(yè)務(wù)邏輯層
這里僅僅以啟動層為例:
voidStart_app() { Led_Start(); } 1234
應(yīng)用層
基本流程是:啟動業(yè)務(wù)邏輯->讀取業(yè)務(wù)邏輯->處理業(yè)務(wù)邏輯->顯示業(yè)務(wù)邏輯。
四、總結(jié)
到這里,一個簡單的例子也解釋完畢了,通過LED這個簡單的例子,已經(jīng)大概了解到這個設(shè)計的復(fù)雜了,如果是大型項目,運用起來會很爽,小型的話完全沒必要這樣分層,太麻煩了,嚴重減慢開發(fā)效率,時間都用在思考如何進行分層才能符合框架分層的原則。
審核編輯:湯梓紅
-
led
+關(guān)注
關(guān)注
242文章
23256瀏覽量
660612 -
嵌入式
+關(guān)注
關(guān)注
5082文章
19111瀏覽量
304847 -
OLED
+關(guān)注
關(guān)注
119文章
6198瀏覽量
224122 -
嵌入式軟件
+關(guān)注
關(guān)注
4文章
240瀏覽量
26642
原文標題:舉例說明嵌入式軟件如何做分層框架設(shè)計
文章出處:【微信號:工程師進階筆記,微信公眾號:工程師進階筆記】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論