凡亿教育-佳佳
凡事用心,一起进步
打开APP
公司名片
凡亿专栏 | 设计嵌入式软件框架时,如何避免过度分层和过度封装?
设计嵌入式软件框架时,如何避免过度分层和过度封装?

我是老温,一名热爱学习的嵌入式工程师
关注我,一起变得更加优秀!
工程师在进行嵌入式软件架构设计的时候,很多时候需要在灵活性与资源约束之间进行权衡。
分层与封装虽然能提升可维护性和移植性,但过度设计容易引发性能损耗与开发效率下降。
本文尝试讨论,在嵌入式软件框架设计时,应如何避免框架过度分层和封装设计,以便让开发者在资源、性能与维护成本之间找到最佳的平衡点。
6cbf17772a6fba15b447799b8ae9e6.jpg
一、嵌入式软件分层和封装接口的本质。
分层软件框架的工程学定义,单片机典型的四层框架设计示例。


Application Layer    // 业务逻辑实现//-------------------------------------Middleware Layer     // 协议栈/算法库//-------------------------------------Driver Layer         // 外设驱动封装//-------------------------------------Hardware Layer       // 寄存器级操作每个层级通过明确定义的接口进行通信,上层模块仅能调用下层提供的接口,禁止跨层访问。
这种设计将硬件操作与业务逻辑解耦,例如STM32的HAL库就将寄存器操作抽象为统一API。
函数接口封装的技术内涵是,接口封装通过信息隐藏(Information Hiding)实现模块隔离,其核心要素包括:接口的访问权限控制、参数校验机制、运行错误的处理策略、不同软件版本的兼容性设计。


标准的驱动接口封装示例如下:


// SPI控制器接口定义typedef struct {    int (*init)(uint32_t freq);    int (*transfer)(uint8_t *tx_buf, uint8_t *rx_buf, size_t len);    int (*deinit)(void);} spi_controller_t;二、软件分层和封装接口的设计必要性
在大型的嵌入式软件系统开发中,分层软件框架带来的工程价值,主要体现在:提升可维护性、增强复用性、便于协作开发、降低移植成本。
举个例子,通过封装Modbus协议接口,可以实现协议栈和物理层(RS485/CAN总线)的解耦,在通信介质改变时,能节省了80%的接口调试时间。


// Modbus接口抽象typedef struct {    int (*read_holding_reg)(uint8_t addr, uint16_t reg, uint16_t *data);    int (*write_single_reg)(uint8_t addr, uint16_t reg, uint16_t value);} modbus_iface_t;三、经典分层架构的设计范式
硬件抽象层(HAL)模式,以ARM架构的 mbed OS 架构为例,其HAL设计规范可以使同一应用代码运行在不同厂商的Cortex-M芯片上,设计如下:


┌─────────────┐│ Application │└──────┬──────┘       ▼┌─────────────┐│   mbed API  │└──────┬──────┘       ▼┌─────────────┐│ Chip Vendor ││   HAL Lib   │└──────┬──────┘       ▼┌─────────────┐│  寄存器操作   │└─────────────┘操作系统抽象层(OSAL)模式,采用适配器模式,可以使业务代码不依赖特定的RTOS内核,以便在不同的RTOS之间进行迁移。


// 线程接口抽象typedef struct {    void* (*create)(task_func_t func, const char *name, \                   uint32_t stack_size, void *param);    void (*delete)(void* handle);} os_thread_t;关于嵌入式软件的设计模式,可以回顾公众号以前的文章,点击->:嵌入式 C 语言设计模式
四、软件分层和接口封装的使用场景
一般情况下,必须采用软件分层设计的场景,有以下这些:需支持多硬件平台、复杂的协议栈集成、长期的维护工作、大型开发团队协作。
而无需严格进行软件分层的场景,主要有:资源受限型的单片机、原型功能验证、高实时性的应用、短周期交付的项目。
如果出现以下特征,则可能警示着接口过度设计,比如:内存占用超标、执行时间出现劣化、工程师开发效率下降,等等。
需要警惕如下的接口嵌套陷阱。


// 过度封装示例result = hal_spi->controller->channel[0]->transfer(...);五、如何避免过度分层与封装设计
采用分层粒度控制策略,使用环形依赖检测工具(比如Lattix DSM工具)分析模块的耦合度,让软件架构满足以下原则:--单向依赖原则:禁止出现循环依赖。--接口精简原则:每个模块暴露的API不超过7个。--层级深度约束原则:API封装调用不超过5层。
在实时数据采集系统里面,可以采用“条件编译分层”技术,利用编译器处理一些额外的工作,可以让嵌入式系统达到较优的实时性。


// 性能关键路径消除抽象#ifdef OPTIMIZE_PERFORMANCE    #define SPI_TRANSFER(data)     REG_SPI_DR = (data)#else    #define SPI_TRANSFER(data)     hal_spi_transfer(data)#endif良好的函数封装接口设计,应该满足以下原则:--参数正交:修改某一参数时,不影响其他功能。--耗时确定:接口调用的耗时可以基本确定。--异常隔离:分层间的异常不会导致其他层崩溃。
六、总结
嵌入式软件虽然没有架构师相关的岗位,但优秀的嵌入式软件工程师,在规划和设计整个嵌入式系统的功能的时候,应当在可维护性和性能之间、在抽象成本和开发效率之间,寻找一个最佳的平衡点。
结合以往的项目生命周期、团队经验规模、硬件平台约束等参数,通过建立一个量化的评估体系,以便可以动态调整嵌入式系统的分层与封装策略。
谨记一点:没有完美的分层架构,只有适合当前上下文的设计。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表凡亿课堂立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。
相关阅读
进入分区查看更多精彩内容>
精彩评论

暂无评论