嵌入式软件分层框架设计,举了个例子

2023-05-16

文章目录

    • 前言
    • 一、框架分层是什么?
    • 二、框架分层的优劣势
      • 1.优势
      • 2.劣势
    • 三、一个简单的例子
    • 四、总结

已剪辑自: https://mp.weixin.qq.com/s/-m_Q-PaC0XlPSeoWrxIstg

前言

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

一、框架分层是什么?

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

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

  • 每个模块提供的接口要统一,只能增加,不能改。在设计的时候得考虑好兼容性,使用起来麻烦不麻烦等等。
  • 同一级模块与模块之间相互独立,互不影响,不能相互调用,只能调用它下一层的接口。
  • 不同模块构成不同的层,层与层之间不能跨级调用。
  • 模块中又可以继续分层,可以增减分层,这个需要根据自己的项目需求来进行设置。

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

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

图片

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

二、框架分层的优劣势

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);
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 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 */
  }

}
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这个简单的例子,已经大概了解到这个设计的复杂了,如果是大型项目,运用起来会很爽,小型的话完全没必要这样分层,太麻烦了,严重减慢开发效率,时间都用在思考如何进行分层才能符合框架分层的原则。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌入式软件分层框架设计,举了个例子 的相关文章

随机推荐

  • 功能安全软件架构

    已剪辑自 https mp weixin qq com s pCenGTqg2Xi t7b8ebNHMA 1 E GAS 安全架构思想 汽车功能安全旨在把电子电气系统失效而导致的人身危害风险控制在合理范围内 下图是常见的电子电气系统硬件构成
  • 代码是如何控制硬件的?

    简单来说 xff0c 就是软件指令通过操作寄存器 xff0c 控制与 或 非门搭建的芯片电路 xff0c 产生 保存高低电平信号 xff0c 实现相应的逻辑 xff0c 最终通过IO 串口等输出 要想更清楚的了解软件控制硬件的原理 xff0
  • Windows下的TCP/UDP网络调试工具-NetAssist以及Linux下的nc网络调试工具

    已剪辑自 https blog csdn net ccf19881030 article details 109370384 一 Windows下的网络调试工具 NetAssist 1 TCP服务端和客户端测试2 UDP服务端和客户端测试
  • 软件产品化

    1 产品化定义 xff1a 软件产品化是指客户无需为软件添加或调整代码和语句即能完成软件的安装配置 应用初始化 系统管理 用户使用的全过程 xff0c 并且软件至少能满足80 以上的用户某一组应用需求 微软Office或杀毒软件就是产品化软
  • 使用Dev C++进行Windows socket网络编程,需链接lws2_32库

    背景 在我们使用Dev C 43 43 进行C语言编程时 xff0c 如果我们引入的库是C语言标准库 xff0c 那我们是不要在编译器选项中进行额外的设置的 xff0c 但是如果我们使用的是一些不是C语言标准库 xff0c 那我们可能就需要
  • Windows下C语言程序和网络调试助手通信

    网络调试助手之间进行UDP通信 前面一篇文章介绍了Windows下的网络调试助手 xff0c 文章链接如下 xff1a Windows下的TCP UDP网络调试工具 NetAssist以及Linux下的nc网络调试工具 下面我们简单介绍一下
  • 为什么char a[]的a不能用a=“hello”来赋值?

    问题 char b里面的b是一个指向char的指针 xff0c 而b可以用b 61 hello 来复制 同样 xff0c a代表char数组的第一个元素的指针 xff0c 类型应该也是char xff0c 为什么b可以直接用赋值符号而a不可
  • Windows下使用C语言创建定时器并周期和网络调试助手通信

    在Windows C下采用timeSetEvent函数来设置定时器 关于timeSetEvent的函数原型及注释如下所示 xff1a MMRESULT span class token function timeSetEvent span
  • GCC编译程序如何减少堆栈空间的大小?

    1 静态堆空间的大小是编译期可以根据你的全局变量 静态变量尺寸算出来的 因此减少后者的长度可以减少你使用堆空间 2 MCU裸片程序 xff0c 很多环境是不方便 xff0c 不支持 xff0c 不鼓励使用malloc函数的 xff0c 裸环
  • 腾讯毕业十多年了

    已剪辑自 https mp weixin qq com s rb5aeIDQ5 qtifeoeIrqZw 昨天一个腾讯前同事加了我的微信找我聊天 xff0c 说是在网上看见我写的文章想起我了 这个同事印象中很沉默寡言的 xff0c 在腾讯除
  • ARM9和STM32什么关系?

    已剪辑自 https mp weixin qq com s QHARY D2SwFoQbFsJoCNlg 有小伙伴问 xff1a ARM9和STM32什么关系 xff1f 如果时间倒退10年 xff0c ARM9 s3c2410还算是比较流
  • 汽车ECU通信相关验证项有哪些?

    已剪辑自 https mp weixin qq com s fIAXkS37r6jvnuA7yIQDA 汽车电子的高速发展决定了基础软件所面临的要求将会更加严格 xff0c 其要求会覆盖软件的安全性 稳定性 可扩展性等方方面面 为了提高软件
  • 不可忽视的 C 语言陷阱!

    已剪辑自 https mp weixin qq com s bdGuOjz1Cg3YXHwlVh5ZlA 语言是编程的基石 xff0c C语言诡异且有种种陷阱和缺陷 xff0c 需要程序员多年历练才能达到较为完善的地步 虽然有众多书籍 杂志
  • 程序员能纯靠技术渡过中年危机吗?

    首先说答案 xff0c 能 xff01 程序员可以靠技术渡过中年危机 xff0c 但是要转变线性思维 先说说个人情况 xff0c 80后 xff0c 从事电机控制软件开发十余年 xff0c 属于制造业 xff0c 算嵌入式的一个小分支 xf
  • STM32学习(一)

    单片机基础 CISC和RISC举例 冯 诺依曼结构VS哈佛结构 冯 诺依曼结构数据和程序存储在同一存储器中 xff0c 访问数据时不能访问程序 xff0c 访问程序时不能访问数据 xff0c 属于分时复用 xff0c 同一时间只能读取其中一
  • C语言既然可以自动为变量分配内存,为什么还要用动态分配内存呢?

    已剪辑自 https mp weixin qq com s NRyM5KAm jrBImmkA2WYSA 不知道大家在学习C语言动态分配内存的时候 xff0c 有没有过这样的疑问 xff1a 既然系统可以自动帮我们分配内存 xff0c 为什
  • 分享一种通信协议的应用编程原理和思路

    已剪辑自 https mp weixin qq com s wy flva6pCNqHV3ObeLPCQ 嵌入式开发过程中 xff0c UART CAN USB等通信基本离不开通信协议 下面给大家分享一种通信协议 xff08 MAVLink
  • 一名智能驾驶产品经理的自我修养

    已剪辑自 https mp weixin qq com s fCSO7hmP3FP Xat NyFG0A 随着智能驾驶浪潮的兴起 xff0c 市场上产生了对相关人才的大量需求 xff0c 也诞生了一些传统汽车行业所没有的新兴岗位 其中 xf
  • 7个硬核嵌入式C进阶要点!

    已剪辑自 https mp weixin qq com s 2MLAK9JB oyLhc6HiXH2ww void表示的是无类型 xff0c 不可以采用这个类型声明变量或常量 xff0c 但是可以把指针定义为void类型 xff0c 如vo
  • 嵌入式软件分层框架设计,举了个例子

    文章目录 前言一 框架分层是什么 xff1f 二 框架分层的优劣势1 优势2 劣势 三 一个简单的例子四 总结 已剪辑自 https mp weixin qq com s m Q PaC0XlPSeoWrxIstg 前言 为了能够使得产品得