본문 바로가기
임베디드 개발/STM32 (ARM Cortex-M)

STM32 ] TouchGFX 설치하고 여러 Widget과 Interaction 사용 해보기

by eteo 2023. 7. 17.

 

 

TouchGFX 설치하고 사용해보기

 

설치하기

 

 

1. 공식 웹사이트에서 TOUCHGFX를 다운받아 설치한다.

 

https://www.st.com/en/embedded-software/x-cube-touchgfx.html

 

X-CUBE-TOUCHGFX - STMicroelectronics

X-CUBE-TOUCHGFX - TouchGFX advanced and free of charge graphical framework optimized for STM32 microcontrollers, X-CUBE-TOUCHGFX, STMicroelectronics

www.st.com

 

 

압축 해제 후 아래 경로에 .msi 설치파일이 있다.

Utilities\PC_Software\TouchGFXDesigner

 

 

 

 

2. STM32CubeMX 툴에서 TouchGFX Generator를 설치한다.

 

 

 

최신버전을 보기위해 Refresh를 누르고 설치할 버전을 선택한 뒤 Install Now를 누른다.

 

 

 

 

 

X-CUBE-TOUCHGFX 배포판은 아래 경로에 위치한다.

C:\Users\<user>\STM32Cube\Repository\Packs\STMicroelectronics\X-CUBE-TOUCHGFX\4.13.0

 

 

 

 

 

3. TouchGFX designer를 실행한다.

 

 

 

 

 

 

 

 

 

메뉴

 

 

 

좌측의 Create 버튼을 누르면 Simulator 또는 보드를 선택할 수 있다. 보드를 선택하는 경우 Color Depth와 Resolution은 고정이다.

 

 

 

 

 

 

좌측의 Example 버튼을 누르면 UI Component 사용법을 익힐 수 있고 그 아래 Demo를 누르면 많은 Demonstration 프로젝트를 확인할 수 있다.

 

 

 

 

 

 

 

 

 

 

Widget과 Interaction 사용하기

 

 

 

✔ Image

 

 

 

 

Image의 Properties에서 Image를 누르고 touchGFX에서 기본제공하는 Image들을 불러올 수 있다. 또는 Project를 눌러 로컬이미지를 프로젝트에 포함시킬 수 있다.

 

 

 

 

 

 

 

 

 

 

 

✔ Button

 

 

 

Button의 Trigger는 Click, Touch, Toggle, Repeat가 있다.

 

 

 

Button의 Visual Elements는 Box With Border, Text, Icon, Image가 있으며 이중에 몇개를 조합할 수 있다.

 

Elements들도 Widget과 마찬가지로 맨 위에 있는 것이 제일 앞에 보이고 밑에있는게 뒤에서 보인다.

 

 

그리고 각 Element는 Position 오프셋을 조정할 수있고 Released 상태일 때와 Pressed 상태일 때의 이미지 등을 다르게 설정할 수 있다.

 

 

 

 

 

 

✔ Interaction 스크린 전환

 

Screens에서 Add Screen을 눌러 스크린을 추가할 수 있다.

 

 

 

그리고 화면 우측 Interaction에 + 버튼을 누른 뒤 특정 버튼이 클릭되었을 때 Change screen Action이 실행되도록 추가할 수 있다.

 

 

 

 

 

 

 

 

 Text Area

 

 

 

 

 

 

 

 

 

 한글폰트 추가

 

화면 우측 Texts - Texts 를 누르고 GB 옆의 + Add Language 버튼을 눌러 KO를 추가한다.

 

 

 

 

그리고 KO열에 각 텍스트에 대한 한국어 번역을 수동으로 추가할 수 있고, 번역하지 않을 Text는 영어로 둘 수 있다.

 

 

 

화면 우측 하단의 Config - General - Selected Language를 통해 기본 언어를 바꿀 수 있다.

 

 

 

 

이 내용은 TextKeysAndLanguages.hpp 파일에서 볼 수 있다.

 

 

<프로젝트명>\generated\texts\include\texts

 

/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */

#ifndef TOUCHGFX_TEXTKEYSANDLANGUAGES_HPP
#define TOUCHGFX_TEXTKEYSANDLANGUAGES_HPP

enum LANGUAGES
{
    GB,
    KO,
    NUMBER_OF_LANGUAGES
};

enum TEXTS
{
    T_RESETTEXTID,
    T___SINGLEUSE_X0HV,
    T___SINGLEUSE_O0W6,
    T___SINGLEUSE_6LO5,
    T___SINGLEUSE_BVQI,
    T___SINGLEUSE_KDW3,
    T___SINGLEUSE_R0GT,
    T_RESETINBUTTONID,
    T___SINGLEUSE_DOWR,
    T___SINGLEUSE_QR2V,
    T___SINGLEUSE_O7KU,
    NUMBER_OF_TEXT_KEYS
};

#endif // TOUCHGFX_TEXTKEYSANDLANGUAGES_HPP

 

 

 

 

 

 

 

 

 

 

그리고 한글을 지원하는 무료 오픈소스 글꼴을 다운받은 뒤 프로젝트 폴더의 assets - fonts 폴더 안에 넣고 touchGFX를 재시작한다.

 

디폴트 경로는 아래와 같다.

 

C:\TouchGFXProjects\<프로젝트명>\assets\fonts

 

 

Typographies에서 한영전환할 Typograpy를 만들고 Font에서 방금 추가한 폰트를 선택할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 Interaction으로 호출될 가상메소드 추가하기

 

Action을 Call new virtual function을 선택하고 Funtion Name을 알아보기 쉽게 짓는다.

 

 

 

F4를 누르거나 하단의 Generate Code를 누른다.

 

 

 

 

아래 경로에 MainViewBase.hpp 파일이 있다.

 

<프로젝트명>\generated\gui_generated\include\gui_generated\main_screen

 

열어보면 아까 생성한 가상 메소드가 보이고 그 구현체는 비어있다. 이는 하위클래스 MainView에서 메소드를 직접 구현해야 한다.

 

/*********************************************************************************/
/********** THIS FILE IS GENERATED BY TOUCHGFX DESIGNER, DO NOT MODIFY ***********/
/*********************************************************************************/
#ifndef MAINVIEWBASE_HPP
#define MAINVIEWBASE_HPP

#include <gui/common/FrontendApplication.hpp>
#include <mvp/View.hpp>
#include <gui/main_screen/MainPresenter.hpp>
#include <touchgfx/widgets/Box.hpp>
#include <touchgfx/widgets/Image.hpp>
#include <touchgfx/widgets/TextArea.hpp>
#include <touchgfx/widgets/ToggleButton.hpp>
#include <touchgfx/containers/buttons/Buttons.hpp>
#include <touchgfx/containers/progress_indicators/LineProgress.hpp>
#include <touchgfx/widgets/canvas/PainterRGB565Bitmap.hpp>
#include <touchgfx/containers/Slider.hpp>

class MainViewBase : public touchgfx::View<MainPresenter>
{
public:
    MainViewBase();
    virtual ~MainViewBase();
    virtual void setupScreen();

    /*
     * Virtual Action Handlers
     */
    virtual void languageSwitchButtonClicked()
    {
        // Override and implement this function in Main
    }
...

 

 

 

 

아래 경로에서 MainView.hpp를 열고 메소드를 다시 선언한다.

 

<프로젝트명>\gui\include\gui\main_screen

 

#ifndef MAINVIEW_HPP
#define MAINVIEW_HPP

#include <gui_generated/main_screen/MainViewBase.hpp>
#include <gui/main_screen/MainPresenter.hpp>

class MainView : public MainViewBase
{
public:
    MainView();
    virtual ~MainView() {}
    virtual void setupScreen();
    virtual void tearDownScreen();
    virtual void languageSwitchButtonClicked();
protected:
};

#endif // MAINVIEW_HPP

 

 

 

아래 경로의 MainView.cpp에 실체를 구현한다.

 

<프로젝트명>\gui\src\main_screen

 

 

#include <gui/main_screen/MainView.hpp>
#include <touchgfx/utils.hpp>
#include <texts/TextKeysAndLanguages.hpp>

MainView::MainView()
{

}

void MainView::setupScreen()
{
    MainViewBase::setupScreen();
}

void MainView::tearDownScreen()
{
    MainViewBase::tearDownScreen();
}

void MainView::languageSwitchButtonClicked()
{
	touchgfx_printf("languageSwitchButtonClicked\n");
	
	int idLanguage = Texts::getLanguage() + 1;
	if(idLanguage == NUMBER_OF_LANGUAGES)
	{
		idLanguage = 0;
	}
	Texts::setLanguage(idLanguage);
	MainView::invalidate();	
}

 

가상메소드 안에서는 현재 LANGUAGE ENUM 값이 0이면 1로 1이면 0으로 세팅한 후 MainView::invalidate()를 통해 화면을 다시 그린다.

 

참고로 #include <touchgfx/utils.hpp> 를 추가한 뒤에 touchgfx_printf() 함수를 사용하여 디버깅용으로 쓸 수 있다. 이 코드는 타겟에서 실행되도 미치는 영향이 없다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 Model에 데이터 추가하기

 

 

 

TouchGFX 사용자 인터페이스는 Model-View-Controller(MVC) 패턴에서 파생된 Model-View-Presenter(MVP)라는 아키텍처 패턴을 따른다.

 

MVP 패턴의 이점은 다음과 같다.

  • 관심 영역 분리: 코드를 각자의 책임을 가진 별개의 영역으로 분할한다. 이렇게 하면 코드가 더 단순해져서 재사용하기 쉽고 유지 관리도 쉬워진다.
  • 유닛 테스트: UI 로직(presenter)이 시각적 계층(view)과 분리되어 있기 때문에 이러한 부분을 훨씬 쉽게 단독으로 테스트할 수 있다.

 

  • model : 사용자 인터페이스에 표시하거나, 그 밖에 실행할 데이터를 정의하는 인터페이스.
  • view : model에서 데이터를 표시하고 해당 데이터를 실행할 프리젠터에게 사용자 명령(이벤트)을 전달하는 패시브 인터페이스.
  • presenter는 model과 view에 따라 실행된다. 저장소(model)에서 데이터를 가져온 후 뷰에 표시할 수 있도록 형식을 지정한다.

 

 

 

 

 

 

멀티 스크린에서 공유할 수 있는 데이터를 모델에 추가하기.

 

아래 경로에서 Model.hpp 파일을 연다.

 

<프로젝트명>\gui\include\gui\model

 

#ifndef MODEL_HPP
#define MODEL_HPP
#include <touchgfx/hal/types.hpp>

class ModelListener;

class Model
{
public:
    Model();

    void bind(ModelListener* listener)
    {
        modelListener = listener;
    }

    void tick();
	
	void setSharedVal(uint8_t val)
	{
		sharedVal = val;
	}
	
	uint8_t getSharedVal()
	{
		return sharedVal;
	}

protected:
    ModelListener* modelListener;
	uint8_t sharedVal;
};

#endif // MODEL_HPP

 

 

변수와 getter, setter를 추가한다.

 

참고로 #include <touchgfx/hal/types.hpp> 를 추가하면 stdint 자료형을 쓸 수 있다.

 

 

 

아래 경로에서 Model.cpp를 열고 생성자에서 변수를 초기화한다.

 

<프로젝트명>\gui\src\model

 

#include <gui/model/Model.hpp>
#include <gui/model/ModelListener.hpp>

Model::Model() : modelListener(0), sharedVal(0)
{

}

void Model::tick()
{

}

 

 

 

 

 

 View에서 Model 접근하기

 

아래 경로에서 각각 스크린의 -Presenter.hpp 파일을 열어 Model에 접근할 수 있도록한다.

 

<프로젝트명>\gui\include\-_screen

 

 

#ifndef MAINPRESENTER_HPP
#define MAINPRESENTER_HPP

#include <gui/model/ModelListener.hpp>
#include <mvp/Presenter.hpp>
#include <touchgfx/hal/types.hpp>

using namespace touchgfx;

class MainView;

class MainPresenter : public touchgfx::Presenter, public ModelListener
{
public:
    MainPresenter(MainView& v);

    /**
     * The activate function is called automatically when this screen is "switched in"
     * (ie. made active). Initialization logic can be placed here.
     */
    virtual void activate();

    /**
     * The deactivate function is called automatically when this screen is "switched out"
     * (ie. made inactive). Teardown functionality can be placed here.
     */
    virtual void deactivate();

    virtual ~MainPresenter() {}
	
	void setSharedVal(uint8_t val)
	{
		model->setSharedVal(val);
	}
	
	uint8_t getSharedVal()
	{
		return model->getSharedVal();
	}

private:
    MainPresenter();

    MainView& view;
};

#endif // MAINPRESENTER_HPP

 

 

 

이제 여러 스크린의 -View.cpp에서 presenter->getSharedVal(), presenter->setSharedVal() 을 통해 모델 데이터에 접근할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 Interaction, Screen transition begins 사용해서 위젯의 속성 값 바꾸기

 

 

여러 스크린에서 공유하는 변수의 상태값을 스크린 트랜지션 이벤트가 시작할 때 확인해서 토글 스위치가 Released 상태 또는 Pressed 상태로 바꿀 수 있다.

 

 

 

 

디자이너 툴에서 위젯의 시작속성을 바꾸면 그에대한 코드는 gui_generated의 -ViewBase.hpp, -ViewBase.cpp 에 생성된다. 해당 코드를 보고 위젯의 속성을 어떻게 제어할지 알 수 있다.

 

 

#include <gui/main_screen/MainView.hpp>

MainView::MainView()
{
}

void MainView::setupScreen()
{
	MainViewBase::setupScreen();
}

void MainView::tearDownScreen()
{
	MainViewBase::tearDownScreen();
}

void MainView::languageSwitchButtonClicked()
{
	Texts::setLanguage(!Texts::getLanguage());
	MainView::invalidate();
}

void MainView::checkLanguage()
{
	if (Texts::getLanguage())
	{
		languageSwitchButton.forceState(true);
	}
	else
	{
		languageSwitchButton.forceState(false);
	}

	languageSwitchButton.invalidate();
}

 

Texts::getLanguage()로 현재 값을 확인해서 기본값(GB)인 0이면 토글스위치를 false로 KO인 경우 true로 설정한다.

그리고 화면을 전부 다시 그릴 필요없이 languageSwitchButton만 다시 그린다.

 

혹은 Interaction을 사용하지 않고 기존에 있는 setupScreen, tearDownScreen에 포함시키는 방법도 있따.

 

 

 

 

 

 

 

 

 슬라이더와 게이지 연동하기

 

화면전환 이후에도 유지되는 변수를 만들기 위해서는 ScreenView 클래스의 멤버변수가 아니라 Model에 데이터를 추가해야한다. ScreenView 클래스는 화면전환시마다 constructor와 destructor가 호출된다.

 

 

 

void MusicView::screenTransitionBegins()
{
    if (Texts::getLanguage())
    {
        languageSwitchButton.forceState(true);
    }
    else
    {
        languageSwitchButton.forceState(false);
    }

    languageSwitchButton.invalidate();

    volumeSlider.setValue(presenter->getVolume());
    volumeSlider.invalidate();

    volumeGauge.setValue(presenter->getVolume());
    volumeGauge.invalidate();
}

void MusicView::sliderValueChanged(int value)
{
    presenter->setVolume(value);
    volumeGauge.setValue(presenter->getVolume());
    volumeGauge.invalidate();
}

 

 

 

 

 

 

 

 

 

 Wildcard Text 사용하기

 

와일드카드란 텍스트에서 런타임 시 숫자와 같이 다른 것으로 대체할 수 있는 마커("<...>")를 말한다.

여기서는 볼륨이 int형이기 때문에 <d>를 사용할거다.

 

와일드카드는 코정 텍스트와 결합해서 사용할 수 있다. Wildcard를 추가하고 Use wildcard buffer를 체크한다.

Auto-size를 풀고 Alignment를 변경한다.

 

 

 

 

// ...
    Unicode::snprintf(dbTextBuffer, DBTEXT_SIZE, "%d", presenter->getVolume());
    dbText.invalidate();
// ...

 

 

TextArea 위젯은 Unicode를 사용하기 때문에 Unicode 버퍼 작성을 지원하는 snprintf 함수를 사용해야한다. 

 

 

 

 

그리고 Texts - Typographies 에 들어가 해당 Typography의 Wildcard Ranges에 문자 0-9를 추가해야다. TouchGFX에는 필요한 문자만 포함되도록 되어있기 때문이다.

 

 

 

 

 

 

 

 

 

 

 code에서 screen transition 하기

 

먼저 screen 프로퍼티에서 Actions +를 눌러 추가한다.

 

 

 

다음 Interactions를 눌러 방금 만든 screen의 Action이 호출될 때 Change screen 하도록 추가한다.

 

 

코드를 생성하면 -ViewBase.hpp와 -ViewBase.cpp에 아래와 같이 구현된다.

 

/*
 * Custom Actions
 */
virtual void changeToMain();
void WashViewBase::changeToMain()
{
    //changeToMainIsCalled
    //When changeToMain is called change screen to Main
    //Go to Main with screen transition towards West
    application().gotoMainScreenSlideTransitionWest();
}

 

 

 

이제는 -View.cpp에서 Action을 호출하기만 하면 된다.

void WashView::backStopButtonClicked()
{
    switch (washType)
    {
    case NOTUSED:
        changeToMain();
        break;
    case ING:
    case PAUSE:
		// ...
        break;
    default:
        break;
    }
}

 

 

 

 

 

 

 

 

 

 

 

 런타임에서 다른 Text로 변경하기

 

Texts - Texts에서 + Add new text 버튼을 눌러 텍스트를 직접 추가해준다. Typography, Alignment도 선택하고 알아보기 쉽게 아이디를 지정한다.

 

 

 

 

코드를 생성하면 아래 경로 TextKeysAndLanguages.hpp 파일에 id가 보인다.

 

gui_generated\texts\include\texts

 

enum TEXTS
{
    T_WASHSTOPID,
    T_WASHPAUSEID,
    T___SINGLEUSE_WZ1T,
    T___SINGLEUSE_5U34,
    T_WASHSTARTID,
    T_WASHBACKID,
    T___SINGLEUSE_GGBH,
    T___SINGLEUSE_3D1G,
    T___SINGLEUSE_ZDA4,
    T___SINGLEUSE_H3YX,
    T___SINGLEUSE_YSAA,
    T___SINGLEUSE_IL7G,
    T___SINGLEUSE_MNZM,
    T___SINGLEUSE_X0HV,
    T___SINGLEUSE_O0W6,
    T___SINGLEUSE_6LO5,
    T___SINGLEUSE_BVQI,
    T___SINGLEUSE_KDW3,
    T___SINGLEUSE_R0GT,
    T_RESETINBUTTONID,
    T___SINGLEUSE_DOWR,
    T___SINGLEUSE_QR2V,
    T___SINGLEUSE_O7KU,
    NUMBER_OF_TEXT_KEYS
};

 

#include <texts/TextKeysAndLanguages.hpp> 하고

 

<widgetName>.setText(TypedText(<idName>)) 으로 텍스트를 변경한다.

 

#include <texts/TextKeysAndLanguages.hpp>

// ...

void WashView::backStopButtonClicked()
{
    switch (washType)
    {
    case NOTUSED:
        changeToMain();
        break;
    case ING:
    case PAUSE:
        washType = NOTUSED;
        washCounter = 0;

        backStopButton.setText(TypedText(T_WASHBACKID));
        backStopButton.invalidate();
        startPauseButton.setText(TypedText(T_WASHSTARTID));
        startPauseButton.invalidate();

        Unicode::snprintf(numberTextBuffer, NUMBERTEXT_SIZE, "%d", washCounter);
        numberText.invalidate();

        washProgress.setValue(washCounter);
        washProgress.invalidate();
        break;
    default:
        break;
    }
}

 

 

 

 

 

 

 

 

 

 

 handleTickEvent() 사용하기

 

model.hpp/cpp에도 tick()이 있으니 공동으로 사용하는 거라면 거기에 추가해도 된다. 여기선 -View.hpp/cpp에 추가했다.

 

virtual void handleTickEvent(); 를 -View.hpp에 추가하고 -View.cpp에 구현한다.

 

참고로 handleTickEvent()는 프레임마다 호출된다. 60프레임이기 때문에 tickCount % 60 == 0 으로 1초를 얻어낼수도 있다.

 

아래는 Tick 이벤트 핸들러 안에서 세탁기 상태머신에 따라 다르게 동작하도록 하였다.

 

#ifndef WASHVIEW_HPP
#define WASHVIEW_HPP

#include <gui_generated/wash_screen/WashViewBase.hpp>
#include <gui/wash_screen/WashPresenter.hpp>

enum WASHTYPE
{
    NOTUSED,
    ING,
    PAUSE
};

class WashView : public WashViewBase
{
public:
    WashView();
    virtual ~WashView() {}
    virtual void setupScreen();
    virtual void tearDownScreen();
    virtual void screenTransitionBegins();
    virtual void languageSwitchButtonClicked();
    virtual void handleTickEvent();
    virtual void backStopButtonClicked();
    virtual void startPauseButtonClicked();

protected:
    int tickCounter;
    int washCounter;
    WASHTYPE washType;
};

#endif // WASHVIEW_HPP

 

void WashView::handleTickEvent()
{
    tickCounter++;

    if (tickCounter % 5 == 0)
    {
        tickCounter = 0;
        int temp;

        switch (washType)
        {
        case NOTUSED:
            break;
        case ING:
            washCounter++;
            temp = washCounter;
            if (washCounter > 100)
            {
                temp = 100;

                if (washCounter > 110)
                {
                    washCounter = 0;
                    temp = 0;
                    washType = NOTUSED;
                    backStopButton.setText(TypedText(T_WASHBACKID));
                    backStopButton.invalidate();
                    startPauseButton.setText(TypedText(T_WASHSTARTID));
                    startPauseButton.invalidate();
                }
            }
            washProgress.setValue(temp);
            washProgress.invalidate();
            Unicode::snprintf(numberTextBuffer, NUMBERTEXT_SIZE, "%d", temp);
            numberText.invalidate();
            break;
        case PAUSE:
            break;
        default:
            break;
        }
    }
}

 

 

 

 

 

 

 

✔ Run Simulator

 

F5를 눌러 Simulator를 실행할 수 있고 아래 경로에서 실행파일을 실행해도 된다.

 

C:\TouchGFXProjects\<ApplicationName>\build\bin