Подключение интерфейса к вашей системе. Примеры touchGFX stm32

В большинстве приложений пользовательский интерфейс должен быть как-то связан с остальной частью вашей системы, а также отправлять и получать данные. Это может быть взаимодействие с аппаратными периферийными устройствами (данные датчиков, аналого-цифровые преобразования, последовательная связь и т. Д.) Или взаимодействие с другими программными модулями.

В этой статье описываются рекомендуемые решения для реализации этого подключения.

Первый метод является «быстрым и грязным» подходом, в первую очередь предназначенным для создания прототипов, тогда как второй метод является архитектурно обоснованным способом правильного соединения пользовательского интерфейса с остальными компонентами в реальном приложении.

В конце этой статьи мы даем ссылку на примеры использования обоих методов.

Модельный класс

Все приложения TouchGFX имеют класс Model, который помимо хранения информации о состоянии пользовательского интерфейса также предназначен для функционирования в качестве интерфейса с окружающей системой. Под этим мы имеем в виду как аппаратную периферию, так и общение с другими задачами ОС в вашей системе. Обычно это плохой дизайн для доступа к другим программным модулям или аппаратным средствам в отдельных классах View.

Класс Model хорошо подходит для размещения любого такого кода интерфейса, потому что:

  1. Класс Model имеет функцию tick (), которая автоматически вызывается для каждого кадра и может быть реализована для поиска и реагирования на события из других подмодулей.
  2. Класс Model имеет указатель на вашего в настоящее время активного Presenter, чтобы иметь возможность уведомлять пользовательский интерфейс о входящих событиях.

Аппаратное взаимодействие

Метод 1: Выборка непосредственно из задачи GUI

Лучший способ взаимодействия с аппаратным обеспечением зависит от того, как часто вам нужно выполнять выборку, сколько времени это занимает и насколько критично время. Если ваши требования в этом отношении снисходительны, самый простой подход - просто выбрать аппаратное обеспечение непосредственно в  Model::tickфункции. Если выборка происходит реже, чем ваша частота кадров (обычно около 60 Гц), вы можете просто добавить счетчик и выполнять выборку только каждый N-й такт. Когда это сделано таким образом, ваша операция сэмплирования должна быть несколько быстрой (обычно 1 мс или меньше), иначе ваша частота кадров начнет страдать, так как сэмплирование выполняется в контексте задачи с графическим интерфейсом и будет задерживать рисование кадра.

Метод 2: Выборка из вторичной задачи

В качестве альтернативы, если нежелательно помещать взаимодействие с оборудованием непосредственно в контекст GUITask, вы можете создать новую задачу ОС, отвечающую за выборку. Вы можете настроить выполнение этой задачи с точными временными интервалами, необходимыми для вашего конкретного сценария. Также в зависимости от ваших потребностей эта новая задача может иметь более низкий или более высокий приоритет, чем задача с графическим интерфейсом. Если он имеет более высокий приоритет, то вы гарантированно выполняете его в указанное вами время, независимо от того, что выполняет задача с графическим интерфейсом. Это имеет тот недостаток, что, если это процесс, потребляющий ЦП, это может повлиять на частоту кадров пользовательского интерфейса. Если, с другой стороны, выборка не является критичной по времени, вы можете назначить задаче более низкий приоритет, чем задаче с графическим интерфейсом, чтобы аппаратная выборка никогда не влияла на частоту кадров пользовательского интерфейса.

Если вы используете подход вторичной задачи, мы рекомендуем вам воспользоваться системой обмена сообщениями между задачами, которая предоставляется вашей ОСРВ. Большинство, если не все, ОСРВ имеют механизм очереди / почты, который позволяет отправлять данные (обычно определяемые пользователем структуры C, байтовые массивы или простые целые числа) из одной задачи в другую. Чтобы передать новые данные в задачу GUI, настройте почтовый ящик или очередь сообщений для теста пользовательского интерфейса и отправьте данные в задачу GUI с помощью этой системы обмена сообщениями. Затем вы можете Model::tickопросить почтовый ящик задачи с графическим интерфейсом, чтобы проверить, поступили ли какие-либо новые данные. В случае, прочитайте данные и обновите пользовательский интерфейс соответственно.

Распространение данных в пользовательском интерфейсе

Независимо от того, используете ли вы метод 1 или метод 2, Model::tickфункция - это место, где GUITask узнает о новых данных, отображаемых в пользовательском интерфейсе. Помимо работы в качестве интерфейса к вашей окружающей системе, вспомните ранее, что Modelкласс также отвечает за хранение данных о состоянии, поэтому могут быть некоторые переменные состояния, которые также необходимо обновить.

Давайте рассмотрим простой пример, когда датчик температуры подключен к системе и текущая температура должна отображаться в пользовательском интерфейсе. В процессе подготовки мы добавим класс 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 можете спросить модель о текущей температуре, что позволяет докладчику установить это значение в пользовательском интерфейсе (представление) при входе в экран, отображающий температуру. Что нам нужно сделать сейчас, это иметь возможность снова обновлять пользовательский интерфейс при получении новой информации о температуре. Для этого мы используем тот факт, что в модели есть указатель на вашего в настоящее время активного докладчика. Тип этого указателя - интерфейс ( 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. Переменная currentTempera всегда актуальна, так что ваш Presenter может в любой момент получить текущую температуру.
  2. Ведущий немедленно уведомляется об изменениях температуры и может принять соответствующие меры.

Одним из преимуществ шаблона MVP является то, что вы получаете отдельную обработку уведомлений в зависимости от того, на каком экране вы находитесь в данный момент. Предположим, например, что событие изменения температуры происходит при отображении какого-либо меню настроек (например, MainMenuPresenter / MainMenuView активен), где текущая температура не имеет значения.

Поскольку функция notifyTemperaCCanged имеет пустую реализацию по умолчанию, это уведомление просто игнорируется MainMenuPresenter. С другой стороны, если у вас есть TemperatureControlPresenter, вы можете в этом презентаторе переопределить функцию notifyTeurationChanged и сообщить в View, что он должен отображать обновленную температуру:

TemperatureControlPresenter.hpp:
class TemperatureControlPresenter : public ModelListener
{
public:
  // override the empty function.
  virtual void notifyTemperatureChanged(int newTemperature) {
    view.setTemp(newTemperature);
  }
};
Класс View, TemperatureControlView, должен, конечно, реализовывать метод setTemp. 

Передача данных из пользовательского интерфейса в окружающую систему

Обратное направление, в котором данные / события передаются из пользовательского интерфейса в окружающую систему, осуществляется через модель практически таким же образом. Продолжая приведенный выше пример, если нам нужно добавить возможность настройки нового заданного значения (целевой температуры), мы добавим в модель следующее:

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);
}

В случае, если пользователь устанавливает новую целевую температуру в пользовательском интерфейсе, представление может информировать презентатора, который содержит указатель на объект модели и, следовательно, может вызывать  setNewTargetTemperatureфункцию.

Примеры

Способ 1 - из задачи графического интерфейса

Загрузите эту ссылку, чтобы найти рабочий пример для STM32F746, показывающий, как создать образец кнопки и управлять светодиодом непосредственно в классе Model. В примере используется архитектура MVP для передачи значений и событий между двумя представлениями и классом Model. Класс Model выполняет выборку кнопки и обновляет светодиод в соответствии с состоянием приложения.

Загрузите эту ссылку, чтобы найти рабочий пример для STM32F429, показывающий, как создать образец кнопки в классе Model. В примере используется архитектура MVP для передачи события кнопки в представление.

Способ 2 - из другого задания

Загрузите эту ссылку, чтобы найти рабочий пример для STM32F469, показывающий, как сэмплировать аналоговый вход в отдельном потоке. В примере используется архитектура MVP для передачи аналогового значения в представление.
 
Был реализован рабочий пример, показывающий межзадачную связь и распространение в и из пользовательского интерфейса. Используйте это как вдохновение для ваших собственных настроек. В этом примере происходит обмен данными между серверной системой, реализованной в коде C, и графическим интерфейсом C ++ TouchGFX. Пример работает на плате STM32F746G-DISCO поверх FreeRTOS. 

Способ 3 - из нескольких задач (4.9.3)

Этот рабочий пример был продемонстрирован на вебинаре TouchGFX «Интеграция с вашим оборудованием» от 28 мая 2018 года.  

Приложение было разработано для платы STM32F769-DISCO и взаимодействует со светодиодом и кнопкой пользователя, чтобы показать, как интегрировать как код C, так и аппаратные периферийные устройства в ваше приложение TouchGFX.  

Приложение настраивает кнопку в режиме GPIO. Поведение заключается в том, чтобы определить состояние кнопки в btntask.c и пропустить сообщение через очередь сообщений GUI, если кнопка нажата. Это позволяет нам продвигать анимацию в приложении, удерживая кнопку нажатой.

Приложение использует три задачи FreeRTOS. Один для графического интерфейса, один для каждого периферийного устройства (светодиод и кнопка USER).

Метод 4 - Из задачи и внешней линии прерывания (4.9.3)

Этот рабочий пример был продемонстрирован на вебинаре TouchGFX «Интеграция с вашим оборудованием» от 28 мая 2018 года.  

Приложение было разработано для платы STM32F769-DISCO и взаимодействует со светодиодом и кнопкой пользователя, чтобы показать, как интегрировать как код C, так и аппаратные периферийные устройства в ваше приложение TouchGFX. 

Это приложение настраивает кнопку в режиме EXTI (внешняя линия прерывания 0). Поведение заключается в получении прерывания при нажатии кнопки, после которого прерывание очищается. Это не допускает того же поведения, что и в GPIO, но вместо этого мы будем пошагово выполнять анимацию, потому что сообщение отправляется только через очередь сообщений графического интерфейса всякий раз, когда получено прерывание.

Приложение использует две задачи FreeRTOS. Один для графического интерфейса, один для светодиода. (Задача Button из метода 3 остается активной в этом приложении, чтобы проиллюстрировать, что периферийный код взаимодействия был перемещен в обработчик прерываний).

Добавить комментарий

Обратная связь

Интересуют вопросы реализации алгоритмов, программирования, выбора электроники и прочая информация, постараюсь осветить в отдельных статьях

пишите мне на netdm@mail.ru