嵌入式软件分层框架的优劣

正文
前言
为了能够使得产品得到更好的开发速度与以后更好的迭代和移植,框架分层是很有必要的。但如对于中小型项目严格遵循这些原则,势必会消耗过多精力去思考怎么设计系统,这是一个抉择的过程。
一、框架分层是什么?
在嵌入式架构中:一般分为硬件架构与软件架构。这里是嵌入式软件设计,也是大多数人接触的设计。
所谓的分层,也可以理解为模块化的设计,但是框架分层的设计一般会遵循以下几点原则
每个模块提供的接口要统一,只能增加,不能改。在设计的时候得考虑好兼容性,使用起来麻烦不麻烦等等。
同一级模块与模块之间相互独立,互不影响,不能相互调用,只能调用它下一层的接口。
不同模块构成不同的层,层与层之间不能跨级调用。
模块中又可以继续分层,可以增减分层,这个需要根据自己的项目需求来进行设置。
一般可以分为:硬件驱动层–>功能模块层–>应用接口层–>业务逻辑层–>应用层
让我们看看这个经典的图,简单了解一下框架分层。
从图中不难观察出,设计都是遵循设计的原则的,层与层之间不能相互调用。
二、框架分层的优劣势
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这个简单的例子,已经大概了解到这个设计的复杂了,如果是大型项目,运用起来会很爽,小型的话完全没必要这样分层,太麻烦了,严重减慢开发效率,时间都用在思考如何进行分层才能符合框架分层的原则。


关于PLC的五大功能你知道多少
GPS会不会是自动驾驶里最危险的技术
六种识别方式概述
液压冲击形成的原因_预防液压冲击的方法
挑选高低温试验箱的五大关键因素:让你轻松选择最适合的设备
嵌入式软件分层框架的优劣
苹果13电量多少毫安
制造业面临的网络安全挑战及解决方案
广电5G建设还面临哪些问题
中国首个天基物联网“行云工程”完成第一阶段建设任
基于LM4730/4731的音频功率放大电路图
电阻器的应用
曝微软正开发全新Win10商店 4核8线程的英特尔i3-1125G4笔记本上市
英特尔:“网络转型”以及“新计算”为5G时代奠定基础
阿特曼回锅OpenAI首次专访 谈解雇经过和神秘的Q模型
关于电子设备的热散设计
联想商用IoT边缘计算解决方案,推进企业数字化转型
TI与Imprivata强强联合 打造健康医疗行业新形象
全球有808家工厂在给苹果供货,较去年增加31家
详解汽车防盗器的安装方法