商城网站建设多少钱,微信小程序怎么做店铺免费,wordpress重置秘密,网站建设销售客户开发大家好#xff0c;今天分享一篇嵌入式软件架构设计相关的文章。 软件架构这东西#xff0c;众说纷纭#xff0c;各有观点。什么是软件架构#xff0c;我们能在网上找到无数种定义。
比如#xff0c;我们可以这样定义#xff1a;软件架构是软件系统的基本结构#xff0c… 大家好今天分享一篇嵌入式软件架构设计相关的文章。 软件架构这东西众说纷纭各有观点。什么是软件架构我们能在网上找到无数种定义。
比如我们可以这样定义软件架构是软件系统的基本结构体现在其组件、组件之间的关系、组件设计与演进的规则以及体现这些规则的基础设施。怎么定义一般来说基本上不重要我们不是在写学术书籍工程人员嘛只关心软件架构能解决什么问题。
软件架构不是制定出来的而是产品和业务需求所决定的架构师所做的只是忠于需求并合理的表达了需求。软件架构也从来都不是一成不变的。在产品或者产品线的整个生命周期中随着业务和需求的变化软件架构不断发展和变化以适应新的需要。
软件架构也不是一个简单的项目问题而是产品或产品线的技术战略问题。一个良好设计并推广的软件架构能带来如下好处。 最大限度地减少不必要的返工 使嵌入式软件在宏观层面建立规划 增强复用性降低开发成本 便于团队内部的技术培训 使技术积累更加容易
我经常看到的一个常见问题是新手工程师由于经历与知识不足往往看不到项目全貌很难深刻理解软件架构他们往往要经过多年的专业训练才能逐渐建立架构意识。
但软件架构真的只是资深工程师和架构师的专利吗这个也不见得。古人作文讲究立意为先。
今天工程师做项目和产品也应该先立意。这个意就是指要有高度。工程师入门能从软件架构的高度出发看待软件问题相信对软件的理解会更加深刻一些。因此我总结了软件架构的六个步骤供嵌入式工程师参考。
1. 隔离硬件相关代码建立抽象层
2. 建立统一的软件基础设施
3. 妥善识别和处理产品数据
4. 功能分层与分解
5. 组件及其接口设计
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也可以称为假硬件。我们可以在假硬件上给出输入数据并通过检查假硬件给出的输出数据会否符合预期来对软件进行单元测试。在没有硬件的情况也可以对应用层程序进行开发。很多嵌入式程序员觉得不可能但这时很多大公司开发软件的方式。
抽象层的建立还有一个好处。软件不必等着硬件就绪才开始开发而在硬件可用之前就开始专注于开发和交付应用程序。
这样做的好处是可以在项目早期就对客户提供试用服务并根据客户反馈进行功能调整。如今太多的团队专注于首先准备好硬件而核心应用程序是事后才想到的。这样并不利于对嵌入式软件进行良好的设计和实现。
那么如何建立抽象层呢抽象层的建立涉及到几个关键的因素抽象的程度、抽象的手段以及抽象的对象。这些问题非常复杂非三言两语就能说清。
结论
嵌入式软件与其他软件领域都不一样因为没有一个软件领域和嵌入式软件一样会和硬件进行直接交互请注意此处直接二字。
为了应对可能出现的硬件变化无论是MCUPCBA还是连接PCBA的设备嵌入式软件架构师应该将硬件相关的代码独立出去并压缩在一个最小的范围内。否则一旦使用耦合架构不对硬件相关代码进行剥离屎山式的代码几乎是注定的结局。
一个成功的软件架构从来不是一蹴而就通常是通过迭代和演进创建的。这需要技术负责人或者架构师主动去推动软件架构的迭代不断推动软件的优化重构。这就有点像明星的好身材从来不是天生都是后天自律的结果。
但在嵌入式领域无论搞什么产品搞什么复杂的软件架构剥离硬件相关是第一步也是最为关键的一步。连硬件相关代码都剥不干净软件架构就犹如浮沙筑高台无从谈起。
合抱之木生于毫末有志于提升技术水平的工程师们先从隔离硬件开始吧。我在此先预祝成功