주요 내용으로 건너뛰기

백엔드 통신

대부분의 애플리케이션에서 UI는 UI 외에 다른 시스템과 어떤 식으로든 연결되어 데이터를 주고받아야 합니다. 이는 하드웨어 주변 장치(센서 데이터, A/D 변환, 직렬 통신 등)와 연결될 수도 있고, 다른 소프트웨어 모듈과 연결될 수도 있습니다.

이 섹션에서는 이러한 연결을 구현하는데 있어 권장하는 솔루션에 대해 설명합니다.

첫 번째 방법은 주로 프로토타이핑에 사용되는 "빠르고 간편한(quick-and-dirty)" 방식인 반면, 두 번째 방법은 UI를 실제 애플리케이션에서 다른 구성 요소들과 아키텍처적으로 올바르게 연결하는 견고한 방법입니다.

이 섹션 마지막에는 두 가지 방법을 사용하는 예시를 살펴보겠습니다.

Model 클래스

모든 TouchGFX 애플리케이션에는 하나의 Model 클래스가 있습니다. Model 클래스는 UI 상태 정보를 저장하는 것 외에도 주변 시스템과 연결되는 인터페이스 역할을 합니다. 이를 통해 양측 하드웨어 주변 장치 모두를 참조하기도 하지만 시스템에서 다른 OS task와 통신하기도 합니다. 일반적으로 각각 View 클래스에서 다른 소프트웨어 모듈이나 하드웨어에 액세스하도록 설계하는 것은 바람직하지 않습니다.

Further reading
Model에 대한 자세한 내용은 MVP 패턴을 참조하십시오.

Model 클래스는 이러한 인터페이스 코드를 삽입하는 데 적합한데, 그 이유는 다음과 같습니다.

  1. Model 클래스에는 각 프레임마다 자동으로 호출되는 tick() 함수가 있어서 다른 하위 모듈에서 수신되는 이벤트를 찾아 여기에 반응하도록 구현할 수 있습니다.
  2. Model 클래스에는 UI에 수신된 이벤트를 알릴 수 있도록 현재 활성화된 Presenter를 가리키는 포인터가 있습니다.

시스템 연결

주변 시스템과 연결하는 방법은 GUI Task에서 직접 샘플링하는 것과 Secondary Task에서 샘플링하는 것, 두 가지가 있습니다.

GUI Task에서 샘플링하기

주변 시스템을 연결할 때 가장 좋은 방법은 샘플링 빈도와 샘플링 소요 시간, 그리고 소요 시간의 중요도에 따라 다릅니다.

이 세 가지와 관련하여 요건이 엄격하지 않다면 Model::tick 함수에서 직접 주변 시스템을 샘플링하는 것이 가장 간단합니다..

샘플링 발생 빈도가 프레임 속도(일반적으로 약 60Hz)보다 낮을 경우에는 카운터만 추가하여 N번째 실행 이벤트에서만 샘플링을 할 수 있습니다. 이러한 방식에서는 샘플링 연산이 다소 빨라야 합니다(일반적으로 1ms 이하). 그렇지 않으면 프레임 속도에 문제가 발생하게 되는데, 이는 샘플링이 GUI task의 context에서 이루어져 프레임 그리기 작업을 지연시키기 때문입니다.

Secondary Task에서 샘플링하기

GUI task의 context에서 주변 시스템과 직접 상호작용하는 코드를 삽입하는 것이 바람직하지 않을 경우에는 샘플링을 담당하는 OS task를 새로 생성하면 됩니다.

이렇게 생성된 task는 특정 시나리오에 따라 필요한 시간 간격으로 정확하게 실행되도록 구성할 수 있습니다. 또한 요건에 따라 새로운 task의 우선순위를 GUI task보다 낮게 또는 높게 할당할 수도 있습니다.

우선순위가 더 높으면 GUI task의 유형에 상관없이 지정한 시간에 정확하게 실행됩니다. 단, CPU를 사용하는 프로세스에서는 UI의 프레임 속도에 영향을 미칠 수 있다는 단점이 있습니다.

반대로 샘플링에서 시간이 중요한 요소가 아니라면 GUI task보다 우선순위를 낮게 할당하면 됩니다. 그러면 UI 프레임 속도가 주변 시스템의 샘플링에 의해 영향을 받지 않습니다. GUI task는 렌더링 동안 대기 상태가 됩니다(DMA 기반 픽셀 전송이 끝날 때까지 기다리는 경우 등). 따라서 우선순위가 낮은 task도 잦은 실행이 가능하므로 애플리케이션이 다수일 때도 적합합니다.

secondary task 접근 방식을 따른다면 RTOS에서 제공하는 task 간 메시징 시스템을 이용하는 것이 좋습니다. 모두는 아니지만 대부분 RTOS는 queue/mail 메커니즘을 지원하여 task 간 데이터(일반적으로 사용자 정의 C 구조체, 바이트 배열 또는 단순 정수) 전송이 가능합니다. 예를 들어 새로운 데이터를 GUI task로 전송하려면 해당 UI task에 대한 mailbox 또는 message queue를 설정한 후 이 메시지 시스템을 사용해 데이터를 GUI task로 보내면 됩니다. 그런 다음 Model::tick 함수를 사용해 GUI 작업 수신함을 폴링하여 새로운 데이터가 전송되었는지 확인할 수 있습니다. 만일의 경우에는 데이터를 읽어와서 그에 따라 UI를 업데이트합니다.

데이터를 UI로 전달하기

GUI 작업에서 샘플링하기 또는 보조 작업에서 샘플링하기, 중에서 무엇을 사용하든 상관없이 Model::tick 함수는 GUI 작업에서 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가 Model에게 현재 온도를 물어볼 수 있기 때문에 온도를 표시하는 스크린으로 전환할 때 온도 값을 UI(View)에 설정할 수 있습니다. 이제 새로운 온도 정보가 수신될 때 UI를 다시 업데이트할 수 있도록 만들어야 합니다. 이때는 Model에 현재 활성화된 Presenter를 가리키는 포인터가 있다는 사실을 이용합니다. 이 포인터는 인터페이스(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) {}
};

이로써 인터페이스 연결을 마쳤으므로 이제 수신되는 "new temperature" 이벤트인 Model::tick을 실제로 샘플링하는 일만 남았습니다.

Model.cpp
void Model::tick()
{
// Pseudo-code for sampling data
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. 위에서 Presenter가 온도 변화를 즉시 수신하여 적절한 조치를 취할 수 있습니다.

MVP 패턴의 한 가지 이점은 현재 활성화된 스크린에 따라 알림을 따로 처리할 수 있다는 사실입니다. 예를 들어 현재 온도와 아무 관련이 없는 설정 메뉴 (MainMenuPresenter/MainMenuView 활성화 등)를 표시하는 도중 온도 변화 이벤트가 발생했다고 가정하겠습니다.

위에서 notifyTemperatureChanged 함수는 기본적으로 빈 구현체를 갖기 때문에 이 알림은 MainMenuPresenter 파일에서 무시됩니다. 반면에 TemperatureControlPresenter 파일을 가지고 있다면 이 Presenter에서 notifyTemperatureChanged 함수를 재정의하고 해당 View에게 업데이트된 온도를 표시하라고 알릴 수 있습니다.

TemperatureControlPresenter.hpp
class TemperatureControlPresenter : public ModelListener
{
public:
// override the empty function.
virtual void notifyTemperatureChanged(int newTemperature) {
view.setTemp(newTemperature);
}
};

물론 TemperatureControlView View 클래스는 setTemp 메소드를 구현해야 합니다.

데이터를 UI에서 주변 시스템으로 전송하기

데이터/이벤트를 UI에서 주변 시스템으로 보내는 역방향 전송은 Model을 통해 매우 유사한 방식으로 이루어집니다. 위의 예에 이어서 새로운 목표 온도를 구성할 수 있는 기능을 추가해야 한다면 다음과 같이 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에서 새로운 목표 온도를 설정하면 View가 Model 객체를 가리키는 포인터가 저장된 Presenter에게 이를 알리고, 이에 따라 setNewTargetTemperature 함수를 호출할 수 있게 됩니다.

예제

다음 예제들은 특정 데모 보드에 맞게 구성된 전체 데모이긴 하지만 여기에 나온 대부분의 코드를 다른 데모 보드나 맞춤형 하드웨어에 재사용할 수 있습니다.

GUI Task에서

STM32F746에 유효한 예제로서 버튼을 샘플링하고 Model 클래스에서 LED를 직접 제어하는 방법을 나타낸 것입니다. 이 예제에서는 MVP 아키텍처를 사용해 2개의 view와 Model 클래스 사이의 데이터 값과 이벤트를 전송합니다. Model 클래스는 버튼을 샘플링한 후 애플리케이션 상태에 맞게 LED를 업데이트합니다.

STM32F429에 유효한 예제로서 Model 클래스에서 버튼을 샘플링하는 방법을 나타낸 것입니다. 이 예제에서는 MVP 아키텍처를 사용해 버튼 이벤트를 View에 전송합니다.

기타 Task에서

STM32F469에 유효한 예제로서 별도의 스레드에서 아날로그 입력을 샘플링하는 방법을 나타낸 것입니다. 이 예제에서는 MVP 아키텍처를 사용해 아날로그 값을 View에 전송합니다.

유효한 예제로서 Task 간 통신 및 UI와 데이터를 주고받는 방법을 나타낸 것입니다. 이 예제를 참조하여 자체 환경을 설정해보십시오. 이 예제에서는 C 코드로 구현된 백엔드 시스템과 C++ TouchGFX GUI 사이에서 통신합니다. 또한 FreeRTOS 외에 STM32F746G-DISCO 보드에서도 실행됩니다.

다중 Task에서

이 유효한 예제는 2018년 5월 28일에 열린 TouchGFX의 "하드웨어 통합" 웨비나에서 시연되었습니다.

애플리케이션은 STM32F769-DISCO 보드에 맞게 설계되었으며, LED 및 USER 버튼과 상호작용하면서 C 코드와 하드웨어 주변 장치를 TouchGFX 애플리케이션에 통합하는 방법을 보여줍니다.

이 애플리케이션은 버튼을 GPIO 모드로 구성합니다. 버튼을 누르면 먼저 btntask.c 파일에서 버튼 상태를 샘플링한 후 GUI message queue를 통해 메시지를 전달합니다. 따라서 버튼을 누르고 있으면 애플리케이션에서 애니메이션을 실행할 수 있습니다.

애플리케이션은 3개의 FreeRTOS task를 사용합니다. 1개는 GUI 용이고, 1개는 각 주변 장치(LED 및 USER 버튼) 용입니다.

Task 및 외부 인터럽트 라인에서

이 유효한 예제는 2018년 5월 28일에 열린 TouchGFX의 "하드웨어 통합" 웨비나에서 시연되었습니다.

애플리케이션은 STM32F769-DISCO 보드에 맞게 설계되었으며, LED 및 USER 버튼과 상호작용하면서 C 코드와 하드웨어 주변 장치를 TouchGFX 애플리케이션에 통합하는 방법을 보여줍니다.

이 애플리케이션은 버튼을 EXTI 모드(외부 인터럽트 라인 0)로 구성합니다. 인터럽트를 소거한 후 버튼을 누르면 인터럽트가 수신됩니다. 여기서는 GPIO 모드와 같은 동작은 허용되지 않습니다. 인터럽트가 수신될 때마다 GUI message queue를 통해서만 메시지가 전송되므로 애니메이션이 한 단계씩 차례로 실행됩니다.

애플리케이션은 2개의 FreeRTOS task를 사용합니다. 1개는 GUI 용이고, 나머지 1개는 LED 용입니다. 이때 다중 task 데모의 버튼 작업은 이 애플리케이션에서 유효한 상태로 남아, 주변 장치 상호작용 코드가 인터럽트 핸들러에 삽입되었다는 것을 예시합니다.