談談關於嵌入式軟件分層框架
爲了能夠使得產品得到更好的開發速度與以後更好的迭代和移植,框架分層是很有必要的。但如對於中小型項目嚴格遵循這些原則,勢必會消耗過多精力去思考怎麼設計系統,這是一個抉擇的過程。
一、框架分層是什麼?
在嵌入式架構中:一般分爲硬件架構與軟件架構。這裏是嵌入式軟件設計,也是大多數人接觸的設計。
所謂的分層,也可以理解爲模塊化的設計,但是框架分層的設計一般會遵循以下幾點原則:
-
每個模塊提供的接口要統一,只能增加,不能改。在設計的時候得考慮好兼容性,使用起來麻煩不麻煩等等。
-
同一級模塊與模塊之間相互獨立,互不影響,不能相互調用,只能調用它下一層的接口。
-
不同模塊構成不同的層,層與層之間不能跨級調用。
-
模塊中又可以繼續分層,可以增減分層,這個需要根據自己的項目需求來進行設置。
一般可以分爲:硬件驅動層–> 功能模塊層–> 應用接口層–> 業務邏輯層–> 應用層
讓我們看看這個經典的圖,簡單瞭解一下框架分層。
從圖中不難觀察出,設計都是遵循設計的原則的,層與層之間不能相互調用。
二、框架分層的優劣勢
1. 優勢
-
單一職責:每一層只負責一個職責,職責邊界清晰,不會造成跨級調用,在大型項目中,每個人負責的部分不一樣,加快整個項目的開發進度。
-
高內聚:分層是把相同的職責放在同一個層中,所有業務邏輯內聚在領域層。在測試的時候,只需要測試該領域的層即可,一般不需要考慮其他層的問題。
-
低耦合:依賴關係非常簡單,上層只能依賴於下層,沒有循環依賴。
-
易維護:面對變更容易修改。在平臺更改後,如果只是改了驅動,其他層都不需要動,只需要把驅動層給更改,其他層的功能不需要更改。
-
易複用:如果功能模塊變動了,只需升級相應的功能模塊,其他的模塊不受影響,應用層也不受影響。
如果想要更好地利用這些優勢,那得嚴格遵循設計的原則。
2. 劣勢
-
開發成本高:因爲多層分別承擔各自的職責,增加功能需要在多個層增加代碼,這樣難免會增加開發成本。但是合理的抽象,根據自己的項目設置合理的層級是能降低開發成本的。
-
性能略低:業務流需要經過多層代碼的處理,性能會有所消耗。
-
可擴展性低:因爲上下層之間存在耦合度,有些功能變化可能涉及到多層的修改。
有優勢也有劣勢,需要根據自己的項目需要,進行部分的取捨,如果是中小型項目,可以不需要分層(如果不考慮到以後會迭代的話),或者部分分層就夠了,既能利用框架分層的部分優勢,也能降低開發成本。
三、一個簡單的例子
由於主要討論的是軟件框架的分層設計,這裏使用 STM32cubemx 來進行硬件的初始化,儘可能少考慮到硬件驅動的部分。
以一個智能小燈的作爲例子:
功能
-
按鍵控制小燈的亮度,等級爲:0,1,2,3
-
串口可以觀察當前小燈亮度等級
-
OLED 也可以觀察當前小燈亮度等級
下面就是這個例子的一個簡單的圖示。
這和例子比較簡單,業務邏輯層完全可以去除,直接從應用層調用功能模塊層,加快開發進度。
最後附上一點點代碼,就是關於 LED 如何進行在不同層進行封裝
硬件層
首先看 HAL 庫生成提供的代碼,這個就是 LED
硬件層,也就是 GPIO 層,cubemx 已經生成了,在 stm32f4xx_hal_gpio.c(我用的是 F4),以及有相應的 GPIO 的驅動了,這裏不需要我們進行處理。
硬件層驅動層
看 LED 部分的驅動:
也就是下面的這兩個函數
void MX_TIM1_Init(void);
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle);
/* TIM1 init function */
void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
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();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
HAL_TIM_MspPostInit(&htim1);
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(timHandle->Instance==TIM1)
{
/* USER CODE BEGIN TIM1_MspPostInit 0 */
/* USER CODE END TIM1_MspPostInit 0 */
__HAL_RCC_GPIOE_CLK_ENABLE();
/**TIM1 GPIO Configuration
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);
/* USER CODE BEGIN TIM1_MspPostInit 1 */
/* USER CODE END TIM1_MspPostInit 1 */
}
}
對其進行封裝,就是我們想要的 Led 小燈的驅動了,到時候如果需要,改驅動直接改底層就行了。
void Led_init()
{
MX_TIM1_Init();
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);//啓動PWM
}
功能模塊層
根據上面的需求要求劃分爲四個不同等級,同時也需要對 LED 驅動進行進一步封裝,以便滿足層與層之間不能跨級調用的原則(到這裏是不是發現很麻煩!小項目就不要用啦!)
//ARR計數器設置值爲0~10000
#define LED_GRADE_0 0
#define LED_GRADE_1 3000
#define LED_GRADE_2 6000
#define LED_GRADE_3 10000
//設置LED亮度功能
void Led_Set_brightness(int Grade)
{
if(Grade==LED_GRADE_0)
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, Grade);
HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_2);//關閉PWM輸出
}
else
{
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2, Grade);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, Grade);
}
}
//啓動LED功能
void Led_Start()
{
Led_init();
}
業務邏輯層
這裏僅僅以啓動層爲例:
void Start_app()
{
Led_Start();
}
應用層
基本流程是:啓動業務邏輯 -> 讀取業務邏輯 -> 處理業務邏輯 -> 顯示業務邏輯。
四、總結
到這裏,一個簡單的例子也解釋完畢了,通過 LED 這個簡單的例子,已經大概瞭解到這個設計的複雜了,如果是大型項目,運用起來會很爽,小型的話完全沒必要這樣分層,太麻煩了,嚴重減慢開發效率,時間都用在思考如何進行分層才能符合框架分層的原則。
原文:http://t.csdn.cn/OWG8f
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8G6ThSuccr-t6UcHWyMaVg