举例说明嵌入式框架该如何分层

article/2025/9/14 14:50:29

    关注、星标公众号,直达精彩内容

85407f5beb751275ad8d17d0a3d7aae1.png

素材来源:https://blog.csdn.net/weixin_46185705/article/details/122536374

整理:技术让梦想更伟大 | 李肖遥

前言

为了能够使得产品得到更好的开发速度与以后更好的迭代和移植,框架分层是很有必要的。但如对于中小型项目严格遵循这些原则,势必会消耗过多精力去思考怎么设计系统,这是一个抉择的过程。

一、框架分层是什么?

在嵌入式架构中:一般分为硬件架构与软件架构。这里是嵌入式软件设计,也是大多数人接触的设计。

所谓的分层,也可以理解为模块化的设计,但是框架分层的设计一般会遵循以下几点原则

  • 每个模块提供的接口要统一,只能增加,不能改。在设计的时候得考虑好兼容性,使用起来麻烦不麻烦等等。

  • 同一级模块与模块之间相互独立,互不影响,不能相互调用,只能调用它下一层的接口。

  • 不同模块构成不同的层,层与层之间不能跨级调用。

  • 模块中又可以继续分层,可以增减分层,这个需要根据自己的项目需求来进行设置。

一般可以分为:硬件驱动层–>功能模块层–>应用接口层–>业务逻辑层–>应用层

让我们看看这个经典的图,简单了解一下框架分层。

48fa455a6514608f8033b2a07eeb49e7.png

从图中不难观察出,设计都是遵循设计的原则的,层与层之间不能相互调用。

二、框架分层的优劣势

1.优势

  • 单一职责:每一层只负责一个职责,职责边界清晰,不会造成跨级调用,在大型项目中,每个人负责的部分不一样,加快整个项目的开发进度。

  • 高内聚:分层是把相同的职责放在同一个层中,所有业务逻辑内聚在领域层。在测试的时候,只需要测试该领域的层即可,一般不需要考虑其他层的问题。

  • 低耦合:依赖关系非常简单,上层只能依赖于下层,没有循环依赖。

  • 易维护:面对变更容易修改。在平台更改后,如果只是改了驱动,其他层都不需要动,只需要把驱动层给更改,其他层的功能不需要更改。

  • 易复用:如果功能模块变动了,只需升级相应的功能模块,其他的模块不受影响,应用层也不受影响。

如果想要更好地利用这些优势,那得严格遵循设计的原则。

2.劣势

  • 开发成本高:因为多层分别承担各自的职责,增加功能需要在多个层增加代码,这样难免会增加开发成本。但是合理的抽象,根据自己的项目设置合理的层级是能降低开发成本的。

  • 性能略低:业务流需要经过多层代码的处理,性能会有所消耗。

  • 可扩展性低:因为上下层之间存在耦合度,有些功能变化可能涉及到多层的修改。

有优势也有劣势,需要根据自己的项目需要,进行部分的取舍,如果是中小型项目,可以不需要分层(如果不考虑到以后会迭代的话),或者部分分层就够了,既能利用框架分层的部分优势,也能降低开发成本。

三、一个简单的例子

由于主要讨论的是软件框架的分层设计,这里使用STM32cubemx来进行硬件的初始化,尽可能少考虑到硬件驱动的部分。

以一个智能小灯的作为例子:

功能

  • 按键控制小灯的亮度,等级为:0,1,2,3

  • 串口可以观察当前小灯亮度等级

  • OLED也可以观察当前小灯亮度等级

下面就是这个例子的一个简单的图示。

这和例子比较简单,业务逻辑层完全可以去除,直接从应用层调用功能模块层,加快开发进度。

0f405acb66e0ad3f0ee8292d8bb0ada3.png

最后附上一点点代码,就是关于LED如何进行在不同层进行封装

硬件层

首先看HAL库生成提供的代码,这个就是LED硬件层,也就是GPIO层,cubemx已经生成了,在stm32f4xx_hal_gpio.c(我用的是F4),以及有相应的GPIO的驱动了,这里不需要我们进行处理。

7ca49190c1cea9b035d632ae49f59df5.png

硬件层驱动层

看LED部分的驱动,也就是下面的这两个函数

void MX_TIM1_Init(void);
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle);
12
/* 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 ConfigurationPE11     ------> 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 */}}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798

对其进行封装,就是我们想要的Led小灯的驱动了,到时候如果需要,改驱动直接改底层就行了。

void Led_init()
{MX_TIM1_Init();HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);//启动PWM
}
12345

功能模块层

根据上面的需求要求划分为四个不同等级,同时也需要对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();
}
12345678910111213141516171819202122232425

业务逻辑层

这里仅仅以启动层为例:

void Start_app()
{Led_Start();
}
1234

应用层

基本流程是:启动业务逻辑->读取业务逻辑->处理业务逻辑->显示业务逻辑。

四、总结

到这里,一个简单的例子也解释完毕了,通过LED这个简单的例子,已经大概了解到这个设计的复杂了,如果是大型项目,运用起来会很爽,小型的话完全没必要这样分层,太麻烦了,严重减慢开发效率,时间都用在思考如何进行分层才能符合框架分层的原则。下一篇文章将会结合框架分层与MVC进行一个小综合。

版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注我的微信公众号,回复“加群”按规则加入技术交流群。
点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

http://chatgpt.dhexx.cn/article/rGwc78PU.shtml

相关文章

嵌入式软件架构设计

如何设计一个好的软件架构,如何提高软件的扩展性,移植性,复用性和可读性? 很多做嵌入式开发的朋友经常会遇到这种情况:一个项目软件设计完成了,客户提出了一些新的功能需求。这时侯如果客户新需求不多&…

《嵌入式软件设计方法》--设计原则

设计原则(SOLID) 使用各个原则的首字母组成了一个单词:SOLID。 SRP:Single Responsibility Principle,单一职责原则;OCP:Open Closed Principle,开闭原则;LSP:Liskov Substitution Principle,…

嵌入式软件设计之设计模式

文章目录 前言1.设计模式之适配器模式2.设计模式之单例模式3.设计模式之命令模式4.设计模式之门面模式 前言 在嵌入式软件设计过程中,也会用到一些设计模式,所以说设计模式并不是面向对象语言的专利,下面我通过查资料以及自己的思考总结的几…

2、【已解决】Oracle错误问题(ORA-03113)

在执行一条sql时出现了如下问题: 处理方案网上找了一下日志,说是备份日志满了的原因; 通过以下方法看了下错误日志信息: 1、export TIME_STYLE%Y-%m-%d %H:%M:%S #格式化文件日期,便于查找错误文件 2、sqlplus &qu…

搭建ADG过程中复制报错 RMAN-03009 ORA-03113

搭建ADG过程中复制报错 RMAN-03009 ORA-03113 猜测主备之间网络路由过多导致。。。 开启mrp进程报错 发现数据文件是主库ASM的路径,备库是单机的 switch database to copy; 报错RMAN-6571 report schema;switch database to copy; select name from v$datafile;s…

oracle 启动报错03113,oracle数据库无法启动,总报ora-03113错误

虚机上装的oracle,由于系统突然宕机,导致了以下问题: 问题: 1、监听无法启动: $ lsnrctl start LSNRCTL for Linux: Version 11.2.0.4.0 - Production on 22-NOV-2018 15:27:11 Copyright (c) 1991, 2013, Oracle. Al…

oracle 启动报错03113,Oracle 入门之Oracle启动报错“ORA-03113”

Oracle 入门之Oracle启动报错“ORA-03113” [日期:2010-09-25] 来源:Linux社区 作者:naruto6006 [字体:大 中 小] 早上连接Oracle,发现oracle无法正常工作,无法shutdown immediate方式关闭,shutdown abort方…

linux oracle 03113,Oracle数据库关闭时出现ORA-03113错误

Oracle数据库关闭时,出现ORA-03113错误: SQL> shutdown immediate ORA-03113: end-of-file on communication channel Process ID: 3437 Session ID: 125 Serial number: 5 SQL> startup ORA-24324: service handle not initialized ORA-01041: in…

oracle startup open ora 03113,oracle宕机,startup报错ora03113

一、故障情况 应用无法连接数据库,检查oracle发现已经宕机。 startup 后报错 ORA-03113: end-of-file on communication channel 二、查找原因 查看alter日志 tail -500 /oracle/database/oracle/diag/rdbms/udb/udb1/trace/alter_UDB1.log Unable to create archiv…

ora03113通信通道的文件结尾 会话id 149 序列号 3

ORA-19815: 警告: db_recovery_file_dest_size 字节 已使用100.00%, 尚有 0 字节可用。” 是db_recovery_file_dest_size也叫归档日志空间不足导致 解决方法 第一步,将空间设置大点,另一个就是将多余的文件删除掉即可,那么我们就将这两个办法…

ORA-03113:通信通道的文件结尾

转自--------------http://blog.csdn.net/zwk626542417/article/details/39667999 由来 今天跟往常一样,登陆PL/SQL,确登陆失败,出现一个错误“ORA-01034”和“ORA-27101”如图: 然后就就通过命令提示符去登陆Oracle,去…

ORA-03113:通信通道的文件结尾-完美解决方案

今天发现系统登录和查询数据特别慢,orcl进程占用内存也特别高。打开程序调试用,存储过程调用时报错【ORA-03113:通信通道的文件结尾】 解决方案: oracle 文档中对这个错误这样解释: ORA-03113 错误就是说连接到数据…

ORA-03113: 通信通道的文件结尾

一、报错及错误原因 启动oracle数据库事报错:ORA-03113: 通信通道的文件结尾,报错内容如下图 出现问题后,去查看告警日志文件(D:\app\diag\rdbms\cjyorcl\cjyorcl\trace\alert_cjyorcl.log),日志报错内容如…

TinyPng:在线PNG图片压缩工具

本资源由 伯乐在线 - 卢伟 整理 TinyPng:在线PNG图片压缩工具是一款可以帮助网页设计师们优化图片的工具,只需要简单的两步就可以完成对PNG图片的高压缩而且还不会影响PNG图片的质量,这样就可以解决网页设计师因图片太大而影响网站加载网页速度的难题。 …

在线PNG图片压缩工具推荐——TinyPng

日常开发工作中时常需要对PNG图片压缩,今天推荐大家一个在线的图片压缩工具,ThinyPng 在线地址:TinyPNG – Compress WebP, PNG and JPEG images intelligently TinyPng:在线PNG图片压缩工具是一款可以帮助网页设计师们优化图片的工具&#x…

Image 图片

Image 图片 随机矩阵画图 这一节我们讲解怎样在matplotlib中打印出图像。这里打印出的是纯粹的数字,而非自然图像。下面用 3x3 的 2D-array 来表示点的颜色,每一个点就是一个pixel。 import matplotlib.pyplot as plt import numpy as npa np.array([0.…

libpng 处理png图片

尝试 最近遇到一个需求需要处理png 图片,由于处理过bmp 图片,所以想跟bmp 图片的处理过程一样直接去掉头信息,但是发现解开png之后发现头信息中数据长度和实际的的,data 数据量对不上,发现数据是压缩,跟bm…

Image Manipulation with StyleGAN

StyleGAN 特征插值算法复现 本教程通过对 StyleGanV2 网络上的部分修改,实现图片的拼接。 参考论文 StyleGAN of All Trades: Image Manipulation with Only Pretrained StyleGAN 一、StyleGAN 简介 在GAN出现之前,图形学已经出现了一个重要分支&…

GIMP( GNU IMAGE MANIPULATION PROGRAM)

openGL系列文章目录 文章目录 openGL系列文章目录前言一、GIMP获取?二、使用步骤1.安装2.GIMP使用 总结 前言 我们在使用openGL处理纹理时,为了达到某种特效需要对三维模型添加纹理,而有些纹理在网上是下不到的,而且在一些商用项…