在大多数应用程序中,UI需要以某种方式连接到系统的其余部分,并发送和接收数据。这可以与硬件外围设备(传感器数据,A / D转换,串行通信等)接口,也可以与其他软件模块接口。

本文介绍了实现此连接的推荐解决方案。

第一种方法是“快速且肮脏的”方法,主要用于原型制作,而第二种方法是在架构上合理地将UI与现实应用程序中的其余组件连接的一种方法。

在本文的最后,我们链接到使用这两种方法的示例。

模型类

所有TouchGFX应用程序都有一个Model类,该类除了存储UI状态信息之外,还旨在充当您周围系统的接口。这样,我们既指硬件外围设备,也指系统中的其他OS任务。通常,访问各个View类中的其他软件模块或硬件不是一个好的设计。

Model类非常适合放置任何此类接口代码,因为:

  1. Model类具有tick()函数,该函数会在每帧中自动调用,并且可以实现为查找其他子模块中的事件并对事件做出反应。
  2. Model类具有指向您当前活动的Presenter的指针,以便能够将传入事件通知UI。

硬件接口

方法1:直接从GUI任务采样

与硬件接口的最佳方法取决于您需要采样的频率,采样的时间和时间的紧迫性。如果您在这些方面的要求比较宽松,那么最简单的方法就是直接在Model::tick功能中对硬件进行采样  。如果采样发生的频率低于帧速率(通常在60Hz左右),则可以添加一个计数器,并且仅在第N个时钟间隔进行一次采样。用这种方法完成后,您的采样操作必须快一些(通常为1ms或更短),否则您的帧速率将开始受到影响,因为采样是在GUI任务的上下文中完成的,并且会延迟绘制帧。

方法2:从辅助任务采样

或者,如果不希望将硬件交互直接放在GUITask的上下文中,则可以创建一个新的OS任务来执行采样。您可以配置此任务以在特定情况下所需的确切时间间隔上运行。另外,根据您的需要,此新任务的优先级可以比GUI任务低或高。如果它具有更高的优先级,那么可以保证它在指定的时间准确运行,而不管GUI任务在做什么。这样做的缺点是,如果这是占用CPU的进程,则可能会影响UI的帧速率。另一方面,如果采样不是时间紧迫的,则可以为任务分配比GUI任务更低的优先级,这样UI帧速率就不会受到硬件采样的影响。

如果您使用辅助任务方法,我们建议您利用RTOS提供的任务间消息传递系统。大多数(如果不是全部)RTOS具有队列/邮件机制,可让您将数据(通常是用户定义的C结构,字节数组或简单整数)从一个任务发送到另一个任务。为了将新数据传递到GUI任务,请为UI tast设置邮箱或消息队列,然后使用此消息传递系统将数据发送到GUI任务。然后,您可以Model::tick轮询GUI任务的邮箱,以检查是否有新数据到达。以防万一,读取数据并相应地更新UI。

将数据传播到UI

无论使用方法1还是方法2,该Model::tick功能都是GUITask知道要在UI中显示的新数据的地方。除了充当与周围系统的接口之外,还记得以前Model该类还负责保存状态数据,因此可能还需要更新一些状态变量。

让我们考虑一个简单的示例,其中将温度传感器连接到系统,并且当前温度将显示在UI中。在准备中,我们将扩展Model类以支持此操作:

Model.hpp:
class Model
{
public:
  // Function that allow your presenters to read current temperature.
  int getCurrentTemperature() const { return currentTemperature; }
// Called automatically by framework every tick. void tick(); ... private: // Variable storing last received temperature; int currentTemperature; ... };

通过上述操作,您Presenters 可以向模型询问当前温度,从而允许演示者在进入显示温度的屏幕时在UI(视图)中设置此值。我们现在需要做的是能够在收到新的温度信息时再次更新UI。为此,我们利用了模型具有指向您当前活动的演示者的指针这一事实。该指针的类型是接口(ModelListener),您可以对其进行修改以反映适当的应用程序特定事件:

ModelListener.hpp:
class ModelListener
{
public:
  // Call this function to notify that temperature has changed.
  // Per default, use an empty implementation so that only those
// Presenters interested in this specific event need to
// override this function. virtual void notifyTemperatureChanged(int newTemperature) {} };

现在我们已经连接了该接口,剩下的就是对传入的“新温度”事件进行实际采样了。 Model::tick

Model.cpp
void Model::tick()
{
  // Pseudo-code for Method 1 or Method 2. Depends on your concrete Operating System
  if (OS_Poll(GuiTaskMBox))
  {
    // Here we assume that you have defined a "Message" struct containing type and data,
    // along with some event definitions.
    struct Message msg = OS_Read(GuiTaskMBox);
    if (msg.eventType == EVT_TEMP_CHANGED)
    {
       // We received information that temperature has changed.
       // First, update Model state variable
       currentTemperature = msg.data;

      // Second, notify the currently active presenter that temperature has changed.
// The modelListener pointer points to the currently active presenter. if (modelListener != 0) { modelListener->notifyTemperatureChanged(currentTemperature); } } } }

上面的方法可以确保两件事:

  1. currentTemperature变量始终是最新的,以便您的Presenter可以随时获取当前温度。
  2. 主持人会立即收到有关温度变化的通知,并可以采取适当的措施。

MVP模式的优点之一是,您可以根据当前所处的屏幕来单独处理通知。例如,假设在显示某种与当前温度无关的设置菜单(例如,MainMenuPresenter / MainMenuView处于活动状态)时发生了温度变化事件。

由于notifyTemperatureChanged函数具有默认的空实现,因此MainMenuPresenter完全忽略了此通知。另一方面,如果您有TemperatureControlPresenter,则可以在此演示者中重写notifyTemperatureChanged函数,并通知View它应显示更新的温度:

TemperatureControlPresenter.hpp:
class TemperatureControlPresenter : public ModelListener
{
public:
  // override the empty function.
  virtual void notifyTemperatureChanged(int newTemperature) {
view.setTemp(newTemperature);
} };
当然,View类TemperatureControlView必须实现setTemp方法。 

将数据从UI传输到周围的系统

数据/事件从UI传输到周围系统的相反方向是通过Model进行的,方法大致相同。如果需要添加配置新设定点(目标温度)的功能,则从上一个示例继续进行,我们将在模型中添加以下内容:

Model.hpp:
void setNewTargetTemperature(int newTargetTemp)
{
  // Pseudo-code for sending an event to a task responsible for controlling temperature.
  struct Message msg;
  msg.eventType = EVT_SET_TARGET_TEMP;
  msg.data = newTargetTemp;
  OS_Send(SystemTaskMBox, &msg);
}

如果用户在UI中设置新的目标温度,则视图可以通知Presenter,该Presenter拥有指向Model对象的指针,因此可以调用该  setNewTargetTemperature函数。

例子

方法1-从GUI任务

下载此链接可找到STM32F746的工作示例,该示例演示如何在Model类中对按钮进行采样并直接控制LED。该示例使用MVP体系结构在两个视图和Model类之间传递值和事件。Model类对按钮进行采样并更新LED以匹配应用程序的状态。

下载此链接可找到STM32F429的工作示例,其中显示了如何对Model类中的按钮进行采样。该示例使用MVP架构将按钮事件传输到视图。

方法2-从其他任务

下载此链接可找到STM32F469的工作示例,该示例显示如何在单独的线程中对模拟输入进行采样。该示例使用MVP架构将模拟值传输到View。
 
一个工作示例显示了任务间通信以及往返于UI的传播。以此为您自己设置的灵感。该示例在用C代码实现的后端系统和C ++ TouchGFX GUI之间进行通信。该示例在FreeRTOS之上的STM32F746G-DISCO板上运行。 

方法3-从多个任务(4.9.3)

该工作示例已在2018年5月28日举行的TouchGFX网络研讨会“与硬件集成”中进行了演示。  

该应用程序是为STM32F769-DISCO板设计的,并与LED和用户按钮交互,以显示如何将C代码和硬件外围设备集成到您的TouchGFX应用程序中。  

应用程序以GPIO模式配置按钮。行为是在btntask.c中采样按钮的状态,如果按下按钮,则将消息通过GUI消息队列传递。这使我们可以通过按住按钮来在应用程序中推进动画。

该应用程序使用三个FreeRTOS任务。一个用于GUI,每个用于外围设备(LED和用户按钮)。

方法4-从任务和外部中断线(4.9.3)

该工作示例已在2018年5月28日举行的TouchGFX网络研讨会“与硬件集成”中进行了演示。  

该应用程序是为STM32F769-DISCO板设计的,并与LED和用户按钮交互,以显示如何将C代码和硬件外围设备集成到您的TouchGFX应用程序中。 

该应用程序将按钮配置为EXTI模式(外部中断线0)。行为是在按下按钮后接收中断,然后清除中断。这不允许与GPIO中的行为相同,但是我们将单步执行动画,因为仅在接收到中断时才通过gui消息队列发送消息。

该应用程序使用两个FreeRTOS任务。一个用于GUI,一个用于LED。(方法3中的Button任务在此应用程序中保持活动状态,以说明外围设备交互代码已移至中断处理程序中)。

 

原文