嵌入式软件架构设计-建立抽象层

一起学嵌入式 2023-09-26 07:50

扫描关注一起学嵌入式,一起学习,一起成长


大家好,今天分享一篇嵌入式软件架构设计相关的文章。

软件架构这东西,众说纷纭,各有观点。什么是软件架构,我们能在网上找到无数种定义。

比如,我们可以这样定义:软件架构是软件系统的基本结构,体现在其组件、组件之间的关系、组件设计与演进的规则,以及体现这些规则的基础设施。怎么定义一般来说,基本上不重要,我们不是在写学术书籍,工程人员嘛,只关心软件架构能解决什么问题。

软件架构不是制定出来的,而是产品和业务需求所决定的,架构师所做的,只是忠于需求,并合理的表达了需求。软件架构也从来都不是一成不变的。在产品或者产品线的整个生命周期中,随着业务和需求的变化,软件架构不断发展和变化,以适应新的需要。

软件架构,也不是一个简单的项目问题,而是产品或产品线的技术战略问题。一个良好设计并推广的软件架构,能带来如下好处。

  • • 最大限度地减少不必要的返工
  • • 使嵌入式软件在宏观层面建立规划
  • • 增强复用性,降低开发成本
  • • 便于团队内部的技术培训
  • • 使技术积累更加容易

我经常看到的一个常见问题是,新手工程师,由于经历与知识不足,往往看不到项目全貌,很难深刻理解软件架构,他们往往要经过多年的专业训练,才能逐渐建立架构意识。

但软件架构真的只是资深工程师和架构师的专利吗?这个也不见得。古人作文,讲究立意为先。

今天工程师做项目和产品,也应该先立意。这个意,就是指要有高度。工程师入门能从软件架构的高度出发,看待软件问题,相信对软件的理解,会更加深刻一些。因此,我总结了软件架构的六个步骤,供嵌入式工程师参考。

  1. 1. 隔离硬件相关代码,建立抽象层
  2. 2. 建立统一的软件基础设施
  3. 3. 妥善识别和处理产品数据
  4. 4. 功能分层与分解
  5. 5. 组件及其接口设计
  6. 6. 测试、调试与跨平台开发的支持

需要注意的是,看完这六篇文章,并不足以保证嵌入式工程师学会软件架构。嵌入式软件架构师,是不可培养的。但至少,嵌入式工程师们,可以了解到什么是正确的努力方向,很多时候,选择比努力更加重要。

因此,在未来的几篇文章中,我们会一起探讨一下设计嵌入式软件架构,可以采取的六个步骤。

嵌入式软件架构之一 抽象层与硬件隔离

许多新手乃至老手嵌入式工程师,在未了解软件架构之前,把应用层功能和硬件相关的代码,不由自主的搅和在一起写。这种做法非常普遍。比如下面的代码:

void modbus_rtu_write_reply(uint8_t add, uint8_t func_code, uint16_t reg, uint16_t data)
{
    rs485.buff_tx[0] = add;
    rs485.buff_tx[1] = func_code;
    rs485.buff_tx[2] = (uint8_t)(reg >> 8);
    rs485.buff_tx[3] = (uint8_t)(reg);
    rs485.buff_tx[4] = (uint8_t)(data >> 8);
    rs485.buff_tx[5] = (uint8_t)(data);

    uint16_t crc16 = mb_crc16(rs485.buff_tx, 6);

    rs485.buff_tx[6] = (uint8_t)(crc16);
    rs485.buff_tx[7] = (uint8_t)(crc16 >> 8);

    rs485.tx_total = 8;
    rs485.tx_num = 0;

    /* Send data from the uart port. The hardware related program. */
    LL_USART_ClearFlag_TC(USART1);
    LL_USART_EnableIT_TC(USART1);
    USART1->DR = rs485.buff_tx[rs485.tx_num ++];
}

上面的这一段代码,不是一个好例子。从函数LL_USART_ClearFlag_TC开始的一句,也就意味着,这个Modbus的代码,和MCU提供出的固件库耦合在一起写了。

著名的SOLID原则中,有个依赖倒置原则,高层模块不应该依赖于底层模块,它们应该共同依赖于抽象。此处的代码,显然违反了这一原则。Modbus作为高层模块,此处对MCU固件库的API进行了依赖。

对于这种将硬件相关的代码与功能耦合在一起的软件架构,在本文中,我们姑且称之为“耦合架构”;而我们要追求的,是将隔离硬件相关的软件架构,我们称之为“隔离架构”。接下来,我们将详细对比,耦合架构和隔离架构各自的特征。

耦合架构的问题

虽然从原则上来说,耦合架构是不对的,但我个人对这种软件写法,还是能理解的。为什么?万事皆有因,存在即合理。一般而言,大部分嵌入式软件工程师,都出自硬件相关的专业(比如电子、自动化等),来自于软件工程和计算机专业的嵌入式工程师不多(他们都去互联网行业了),因此从他们的知识结构和习惯思维出发,一般从硬件视角看待嵌入式系统,而不是站在软件抽象的视角。

我个人也是电子工程专业毕业的,对此有感受。但理解归理解,道理归道理,既然已经从事嵌入式软件,哪怕是硬件专业出身的,我也建议他一定抛弃既有思维,学会抽象这一强大的软件思维工具,否则他的职业天花板将非常低。

耦合架构带来的问题,也是显而易见的,那就是,实实在在的难以移植。因为一旦硬件发生变化,比如MCU停产,芯片短缺等等(在当前形势下太过常见),嵌入式软件就要大把修改。如果软件规模较大,尝试移植耦合架构的代码到在新MCU上,是一项艰巨的工作,没人愿意干这事。因此产品开发完成,更新架构并推倒重来,几乎是不可能。

别说工程师不愿意,你问问老板答应吗?于是工程师们只能检查所有代码,把与硬件交互的每一行代码改掉,遇到硬件交互方式大不相同的,就更糟心,还要大篇幅的改,边改边骂娘。比如上面的代码,如果换一片芯片,可能要改为以下代码。

void modbus_rtu_write_reply(uint8_t add, uint8_t func_code, uint16_t reg, uint16_t data)
{
    rs485.buff_tx[0] = add;
    rs485.buff_tx[1] = func_code;
    rs485.buff_tx[2] = (uint8_t)(reg >> 8);
    rs485.buff_tx[3] = (uint8_t)(reg);
    rs485.buff_tx[4] = (uint8_t)(data >> 8);
    rs485.buff_tx[5] = (uint8_t)(data);

    uint16_t crc16 = mb_crc16(rs485.buff_tx, 6);

    rs485.buff_tx[6] = (uint8_t)(crc16);
    rs485.buff_tx[7] = (uint8_t)(crc16 >> 8);

    rs485.tx_total = 8;
    rs485.tx_num = 0;

    /* Send data from the uart port. The hardware related program. */
    MCU_NEW_USART_ClearFlag_TC(NEW_USART1);
    MCU_NEW_USART_EnableIT_TC(NEW_USART1);
    NEW_USART1->DR = rs485.buff_tx[rs485.tx_num ++];
}

其次,耦合架构会导致,在开发环境中(如Windows或者Linux,非目标硬件),很难对应用程序进行单元测试。脱离目标硬件,跨平台开发嵌入式程序,是提升开发效率的重要措施。

对耦合架构来说,应用程序代码直接调用硬件,如果要进行完整的测试工作,就要花费大量工作,因为测试程序也要去操作硬件,才能验证正确与错误。或者,需要工程师在硬件上完成手动测试(实际上现在大家就这么干的,哈哈)。

手动测试很繁琐,往往让人烦躁,工程师的主观感受,会影响测试质量。很多时候,为了赶进度,或者规避繁琐的测试工作,软件并没有经过很好的测试,整体系统质量受到影响。另外,手动测试,交付软件可能需要更长的时间。而自动测试,往往只需要一瞬间,清楚明了。

第三,耦合架构将存在不易扩展的问题。耦合架构,往往是共享数据的,也就是所谓的全局变量满天飞。随着软件系统的扩大,每个新功能的添加,变得更加困难,而且是越来越困难,出现BUG的机会急剧增加。屎山就是这么炼成的。

但需要说明的是,数据问题,不是说隔离了硬件,就能完全解决掉。数据问题,是嵌入式软件乃至任何软件的核心问题,它需要在架构六部曲之二和之三中,通过软件基础设施的合理构建,和数据机制的合理制定,共同得到解决。

隔离架构如何解决问题?

到这里,我们架构的第一步,呼之欲出,那就是:将软件架构分离为硬件相关和硬件无关两个部分。这就要引入抽象层这个概念。何为抽象层?抽象层有很多种,比如硬件抽象层(HAL)、设备抽象层(DAL),操作系统抽象层(OSAL),网络抽象层,文件系统抽象层,Flash抽象层(RT-Thread里就有这个)等等。

对谁进行抽象,就会建立这个东西的抽象层,无一定之规。本文中的抽象层,特指硬件抽象层,或者设备抽象层,或者二者兼备。具体是谁,取决于产品特性,可参考后续文章《嵌入式软件中的抽象层》。

在硬件相关代码和硬件独立代码之间创建抽象层,这是软件移植的要求,实际上也是依赖倒置原则需求。在这里,我们有必要对依赖倒置原则进行强调:高层模块不应该依赖于底层模块,它们应该共同依赖于抽象。也就是说,应用层代码(硬件无关),不应该依赖于硬件相关的代码(驱动代码),他们应该依赖于抽象层代码。

抽象层的创建,将允许将应用代码从一个微控制器移动到下一个微控制器,或者一套硬件迁移到另一套硬件,应用层代码不必更换。抽象层打破了硬件依赖关系;换句话说,应用程序根本不必知道,也不必关心,当前运行的是什么硬件,应用程序只需要关心抽象层的API是什么样的。

新的硬件驱动程序要做的,仅仅是满足接口的要求而已。这意味着如果我们更改硬件,则只会更改硬件相关的模块,而不是整个代码库。

void modbus_rtu_write_reply(uint8_t add, uint8_t func_code, uint16_t reg, uint16_t data)
{
    rs485.buff_tx[0] = add;
    rs485.buff_tx[1] = func_code;
    rs485.buff_tx[2] = (uint8_t)(reg >> 8);
    rs485.buff_tx[3] = (uint8_t)(reg);
    rs485.buff_tx[4] = (uint8_t)(data >> 8);
    rs485.buff_tx[5] = (uint8_t)(data);

    uint16_t crc16 = mb_crc16(rs485.buff_tx, 6);

    rs485.buff_tx[6] = (uint8_t)(crc16);
    rs485.buff_tx[7] = (uint8_t)(crc16 >> 8);

    rs485.tx_total = 8;
    rs485.tx_num = 0;

    /* Send data from the uart port. The hardware related program. */
    hal_uart_send(HAL_UART_ID_1, rs485.buff_tx, rs485.tx_total);
}

void hal_uart_send

硬件相关的代码,应该改为如下的样子。这尚且算不上真正的抽象层,只是抽象层最简陋的替代实现方法,实际工程应用中,抽象层还有很多细节需要阐述。限于篇幅,在本文中,我们不进行探讨,请关注后续的《抽象层》系列文章。

void hal_uart_send(uint8_t uart_id, void *buffer, uint32_t size)
{
    /* Start the uart sending process, the remaning data will be send in UART ISR 
       function. */

    MCU_NEW_USART_ClearFlag_TC(NEW_USART1);
    MCU_NEW_USART_EnableIT_TC(NEW_USART1);
    NEW_USART1->DR = rs485.buff_tx[rs485.tx_num ++];
}

抽象层还可以解决单元测试的许多问题。有了抽象层,我们可以在Windows或者Linux上创建硬件的替身程序(mock),也可以称为假硬件。我们可以在假硬件上给出输入数据,并通过检查假硬件给出的输出数据会否符合预期,来对软件进行单元测试。在没有硬件的情况,也可以对应用层程序进行开发。很多嵌入式程序员觉得不可能,但这时很多大公司开发软件的方式。

抽象层的建立,还有一个好处。软件不必等着硬件就绪才开始开发,而在硬件可用之前,就开始专注于开发和交付应用程序。

这样做的好处是,可以在项目早期就对客户提供试用服务,并根据客户反馈进行功能调整。如今,太多的团队专注于首先准备好硬件,而核心应用程序是事后才想到的。这样并不利于对嵌入式软件进行良好的设计和实现。

那么如何建立抽象层呢?抽象层的建立,涉及到几个关键的因素:抽象的程度、抽象的手段以及抽象的对象。这些问题,非常复杂,非三言两语就能说清。

结论

嵌入式软件与其他软件领域都不一样,因为没有一个软件领域,和嵌入式软件一样,会和硬件进行直接交互(请注意此处直接二字)。

为了应对可能出现的硬件变化(无论是MCU,PCBA,还是连接PCBA的设备),嵌入式软件架构师应该将硬件相关的代码独立出去,并压缩在一个最小的范围内。否则,一旦使用耦合架构,不对硬件相关代码进行剥离,屎山式的代码,几乎是注定的结局。

一个成功的软件架构,从来不是一蹴而就,通常是通过迭代和演进创建的。这需要技术负责人,或者架构师,主动去推动软件架构的迭代,不断推动软件的优化重构。这就有点像明星的好身材,从来不是天生,都是后天自律的结果。

但在嵌入式领域,无论搞什么产品,搞什么复杂的软件架构,剥离硬件相关,是第一步,也是最为关键的一步。连硬件相关代码都剥不干净,软件架构就犹如浮沙筑高台,无从谈起。

合抱之木,生于毫末,有志于提升技术水平的工程师们,先从隔离硬件开始吧。我在此先预祝成功!

原文:https://zhuanlan.zhihu.com/p/600061712

文章来源于网络,版权归原作者所有,如有侵权,请联系删除。



扫码,拉你进高质量嵌入式交流群


关注我【一起学嵌入式】,一起学习,一起成长。


觉得文章不错,点击“分享”、“”、“在看” 呗!

一起学嵌入式 公众号【一起学嵌入式】,RTOS、Linux编程、C/C++,以及经验分享、行业资讯、物联网等技术知
评论
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 65浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 66浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 86浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 70浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 51浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 37浏览
  • 艾迈斯欧司朗全新“样片申请”小程序,逾160种LED、传感器、多芯片组合等产品样片一触即达。轻松3步完成申请,境内免费包邮到家!本期热荐性能显著提升的OSLON® Optimal,GF CSSRML.24ams OSRAM 基于最新芯片技术推出全新LED产品OSLON® Optimal系列,实现了显著的性能升级。该系列提供五种不同颜色的光源选项,包括Hyper Red(660 nm,PDN)、Red(640 nm)、Deep Blue(450 nm,PDN)、Far Red(730 nm)及Ho
    艾迈斯欧司朗 2024-11-29 16:55 167浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 98浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 70浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 106浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 102浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 83浏览
  • 国产光耦合器正以其创新性和多样性引领行业发展。凭借强大的研发能力,国内制造商推出了适应汽车、电信等领域独特需求的专业化光耦合器,为各行业的技术进步提供了重要支持。本文将重点探讨国产光耦合器的技术创新与产品多样性,以及它们在推动产业升级中的重要作用。国产光耦合器创新的作用满足现代需求的创新模式新设计正在满足不断变化的市场需求。例如,高速光耦合器满足了电信和数据处理系统中快速信号传输的需求。同时,栅极驱动光耦合器支持电动汽车(EV)和工业电机驱动器等大功率应用中的精确高效控制。先进材料和设计将碳化硅
    克里雅半导体科技 2024-11-29 16:18 168浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 41浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦