本文探讨了在嵌入式开发中设计嵌入式软件架构的第四步——接口和组件设计。
在上一篇文章中,我们探讨了如何将一个系统分解成域和任务,我们已经确定了该系统的数据资产。你可能还记得,我们创建的上一张图将我们的应用程序分为安全和非安全应用程序域、硬件独立和依赖域(由抽象层分隔),最后分为任务。我们的系统分解如图1所示。
由于blog格式的空间和时间限制,我们建议在描述如何让任务进行交互的架构过程中增加额外的步骤。我们想探索如何完成我们的一项任务,并设计我们的界面和组件。让我们看看如何为电机任务定义组件及其接口。
步骤4–接口和组件设计
组件和界面设计是密切相关的活动。事实上,如果我们查看组件的标准定义,我们会发现以下描述:
软件组件是一个组合单元,具有合同规定的接口和明确的上下文依赖关系。
我们甚至不能在不讨论接口的情况下定义组件!接口是软件开发人员用来与组件交互的工具。因此,我们开始将我们的运动任务分解为组件是有意义的,然后我们可以为每个组件定义接口。
定义软件组件
嵌入式开发人员可以使用许多不同的方法和技术来识别将堆叠在一起以执行任务提供的期望行为的组件。我对几乎任何任务的偏好都是将系统分解成一个分层的软件图,从底层驱动程序开始,一直到应用程序代码。图2显示了一个示例,显示了电机任务的这种图。
这些组件的用途可能是不言自明的,但为了以防万一,让我们定义一下每个组件的作用:
pwm_drv — 微控制器上用于脉宽调制的硬件外设驱动器。
Abstraction layer — 打破电机驱动器和硬件之间的依赖关系。这允许我们使用PWM驱动器来驱动电机,或者用外部集成电路的驱动器来代替pwm_drv。
motor_drv — 设计用于控制电机的驱动器。它没有硬件依赖性。它唯一的依赖是抽象层。
motor_sm — 跟踪电机当前状态和所需状态的状态机。
motor_app — 特定应用支持功能。这些功能可能包括收集遥测数据、检查故障等。
motor_task — 保存实际电机任务代码的组件。该元件依赖于其他电机元件。电机任务可能包括RTOS应用程序交互,如信号量、队列、互斥和事件逻辑。
总之,这些组件具有接收命令的所有必要行为,该命令改变控制电机的状态机,然后启动电机。这种分解很酷的一点是,如果电机控制链的各个方面发生变化,比如新的驱动芯片、新的应用程序需求等,有了适当的接口,嵌入式开发人员只需要进行最小的更改。
定义接口
对于motor任务,我们需要定义两类接口。首先,有一个任务接口,它接收来自其他应用程序组件的命令,告诉它电机应该做什么,比如MOTOR_ON或MOTOR_OFF。其次,每个组件都有接口。
电机任务接口设计
在定义与电机任务通信的接口时,回顾图1中所示的数据资产是很有帮助的。我们可以看到控制器任务与电机任务交互。为了让控制器任务告诉电机任务电机应该做什么,需要几条信息:电机状态、电机方向、电机转速。
如果我们希望系统可伸缩以管理多个电机,我们甚至可以包括一个电机ID。例如,在有些应用中,状态机通过消息控制电机,但用户可以用消息覆盖状态。在这些情况下,我们甚至可能希望包含一个任务ID,以便电机任务知道哪个任务正在请求电机控制。
从数据接口的角度来看,嵌入式开发人员可以为数据定义一个结构,该结构将用于控制和电机任务的接口。用C语言编写如下代码:
MotorMessage_t结构定义了命令电机任务执行实际工作所需的数据接口。我们如何获得机动任务的信息取决于项目的需求。例如,使用消息队列、数据缓冲区或其他机制。然而,这些细节是由程序员决定的,而不是软件架构师。
定义组件接口
有几种不同的方法来为我们的组件设计界面。首先,我们可以做一个便签,列出我们认为需要的功能。接下来,我们可以使用一些UML图表工具并利用类图,即使我们没有编写面向对象的代码。最后,我们可以直接进入代码,开始编写测试,迫使我们为组件的行为开发接口。
类图很棒,因为它们允许嵌入式开发人员指定属性、操作和类之间的关系(模块和组件也可以)。有几件事我们应该注意。首先,电机任务使用电机应用程序、电机状态机和电机驱动器。这里没有继承关系。我们可以看到电机驱动器有一个电机接口。motor应用程序使用MotorError_t枚举,但除此之外,组件是解耦的,不进行交互。
接下来,我们定义了我们认为的操作是什么,以及组件需要的功能或方法。例如,我们可以看到电机驱动器有两个功能:电机初始化和电机命令。电机命令采用电机消息类型和所有信息来命令电机,这是通过电机接口(抽象层)完成的。
当我们查看类图时,我们可以看到电机任务与底层组件进行交互,并驱动电机的最终行为。首先,信息通过MotorQ进入电机任务;然后,电机任务运行电机状态机。最后,电机任务调用电机应用程序查找错误,并使用状态结果通过电机驱动器命令电机。从一个可能并不明显的类图中可以得出很多东西。那么,我们的架构是否遗漏了什么?
我们的设计缺少了什么?
我们可能有足够的资源来开始实现电机应用程序代码;然而,更多的图表和时间可以极大地提高清晰度。例如,嵌入式开发人员可以考虑额外的UML图,比如序列图,来显示控制任务与电机任务交互的时序要求。或是创建一个序列图,以便开发人员了解电机任务如何与其他组件交互。比如电机任务是应该在任务结束的时候命令电机,还是先做,把抖动降到最低?如果出现错误,处理的逻辑是什么?电机应该保持运转还是停止运转?
你通常会发现,你至少需要三到四个UML图来完整地指定一项任务,以便开发人员可以对其进行编码。我们只看了几个,这意味着还有更多的事情要做。我们可以开始开发测试,并使用测试驱动的开发来进一步发展我们的界面和对系统的理解。这可能是目前的发展方向。但是,我们知道的足够多,随着我们实现细节,问题将会出现,这将推动对初始架构的更改。
软件架构设计第4步结论
正如本文所见,我们可以利用UML图和组件堆叠图来识别组件并定义它们的接口。此外,类图可以成为设计师进行界面设计的优秀工具。嵌入式开发人员必须记住,虽然我们遵循一个简单的五步过程来开发我们的架构,但这些步骤只是一个指南,而不是全部。作为架构师,我们经常需要更深入。