cancel
Showing results for 
Search instead for 
Did you mean: 

Getting started with TouchGFX and bringing up a board

BWKidd
Associate III

Hi All,

I'm trying to get started with TouchGFX.

I'm currently working with a NUCLEO-L496ZG eval board connected to an NHD-2.8-240320AF-CSXP-FT (320 x 240 TFT LCD with ST7789 internal controller.)

I've successfully connected up the display via FMC and been able to display colors and images and such to the screen, just using statements like 

*(__IO uint16_t *)(0x60000000) = command;

*(__IO uint16_t *)(0x60020000) = data;

to send data to the command and data registers, and everything seems to be working well.

Now I'm trying to bring in TouchGFX into the mix and I'm having a difficult time. I've read through the board bring up process, and where I think I'm having trouble is knowing which functions to modify from the generated code to integrate the ST7789 driver code I've written.

To give you an idea of what I've tried and what my setup is, here's what my TouchGFX setup looks like in the IOC:

BWKidd_0-1709759336646.png

After generating the code, I used TouchGFX designer and opened the ApplicationTemplate.touchgfx.part file and then added a simple splash bitmap to the default screen:

BWKidd_1-1709759709814.png

(I know, I could quit being an electrical engineer and draw pictures in paint for a living, right?)

I then generated code, went back to STM32CubeIDE re built with no errors.

On first debug session, nothing happened on the LCD, which makes sense since I hadn't modified any of the generated code. Based on the example here FMC and SPI Display Interface | TouchGFX Documentation I realized I was probably supposed to modify flushFrameBuffer in the TouchGFXHAL.cpp file, so I did so like this:

void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)

{

// Set Cursor

TFT_setAddress_WidthHeight(rect.x, rect.y, rect.width, rect.height);

TFT_flushFrameBuffer(frameBuffer0);

}

That didn't make any difference, and I noticed that setting a breakpoint within this function, the flushFrameBuffer function doesn't seemed to get called anyways, and it was at this point that I realized there is too much I don't know/ don't understand. I don't even know of "frameBuffer0" points to the frame buffer I created in the TouchGFX part of the IOC file. According to a note in there, the framebuffer is supposed to be called "framebuf" but the compiler doesn't recognize that.

I realize that I'm missing a big chunk of knowledge here, so please forgive my ignorance. Could someone point me in the right direction?

14 REPLIES 14

@MM..1 

Thanks, that makes sense, but I'm not sure how and when to trigger a vsync. Should I setup a hardware timer and call HAL::getInstance()->vSync() inside a hardware timer ISR?

GaetanGodart
ST Employee

Hello @BWKidd ,

 

The flashFrameBuffer is used when a certain area of the screen has to be redrawn (basicaly when you invalidate something, change to a different screen, run an animation, etc).

What you should care about is that TouchGFX send a endFrame callback when it is done updating the whole screen. After that you are ready to update a new frame so you should send a vsync signal (do not use a timer for that):

GaetanGodart_0-1713519986236.png

The TouchGFX process is constantly waiting for a vsync signal.

 

Regards,

Gaetan Godart
Software engineer at ST (TouchGFX)

@GaetanGodart I hope you have been well! I'm finally getting back to this, and have managed a little success in that I am now able to finally get a screen I have designed in touchGFX to compile and display on my prototype setup. The bad news is I cheated and did what you told me not to do - I setup a timer and I've been calling OSWrappers::signalVSync() 10x a second. 

I tried to implement the code your recommended in your previous post with TouchGFXHAL::endFrame(), but I wasn't certain what tearingEffectCount was, and further, It doesn't seem like TouchGFXHAL::endFrame() gets called unless OSWrappers::signalVSync gets called first.

So my question, or maybe request, is could you clarify how I should be triggering vSync? Many Thanks!

While I still need to resolve the correct way of calling the vSync signal, I did want to post how I finally managed to go from nothing to having screens from touchGFX Designer displayed on my setup. It might be helpful to someone else...or maybe just me, but here it is. Fair warning, there's a lot of detail because I get running into brick walls of knowing which button to push, or where to add this code, etc, and I know when I need to do this again in a year, I won't remember.

Development Environment:

STM32CubeIDE 1.15.0

TouchGFX 4.24.0

Hardware Summary:

Processor: STM32L496ZGT6U (On a NUCLEO-L496ZG Dev Board)

Display: NHD-2.8-240320AF-CSXP-FT (ST7789VI Driver)

Schematic:

 

BWKidd_1-1722628665227.png

 

 

STM32CubeIDE Project Creation:

Created a new project using File à New à STM32 Project

 

From the “Target Selection” dialog box, I selected the “Board Selector” tab, and then selected the “NUCLEO-L496ZG” board, named the project and clicked “Finish”

 

The GPIO pins were configured as follows:

 

    • PB0 was set to GPIO_Output, User Label “DISP_RST” (Display Reset line)

 

The FMC was enabled and configured:

BWKidd_2-1722628717301.png

 

Then the corresponding GPIO Pins configured for use with the FMC:

BWKidd_3-1722628717303.png

 

 

I also activated TIM17 to provide 1 millisecond interrupts for general periodic tasks.

 

BWKidd_4-1722628717305.png

 

The interrupt was also enabled within the “NVIC Settings” tab.

 

BWKidd_5-1722628717306.png

 

The timer was started in the User Code 2 Section of main.c:

main.c

 

/* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim17); // Start Timer 17

 

 

And the corresponding callback was written in the main.c User Code 0 section:

 

main.c

 

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef * htim) {
       // Check which timer triggered interrupt
       if (htim == &htim17) {     // Timer16
              // Run code that should occur every 1 millisecond
       }
}

 

Drivers for the ST7789 that I had written were added to the /Core/Inc folder:

 

ST7789_DRIVER.h contains a list of the various ST7789 register addresses, configuration bits, and macros for sending data via the FMC:

 

ST7789_DRIVER.h

 

...
// FMC Command macros to write to the ST7789V's command and data registers
#define TFT_writeCommand(command) (*(__IO uint16_t *)(0x60000000) = command)
#define TFT_writeData(data) (*(__IO uint16_t *)(0x60020000) = data)
...

 

ST7789_DRIVER.c Implements an init function, display turn on/off command, and a function for setting the address to be written to in display ram.

 

ST7789_DRIVER.h

 

...
void TFT_init(void);		// Init GPIO and send initialization commands to display

void TFT_DisplayOn(void);	// Turn Display On
void TFT_DisplayOff(void);	// Turn Display Off

void TFT_setAddress_StartStop(uint16_t xStart, uint16_t yStart, uint16_t xStop, uint16_t yStop);
...

 

Some macros were defined for common colors in RGB565 format, and a simple function to paint the entire screen one color was then written in the main.c User Code 0 section to test setup thus far:

 

main.c

 

...
#define RGB565_BLACK       0x0000  // Black
#define RGB565_WHITE       0xFFFF  // White
#define RGB565_RED         0xF800  // Red
#define RGB565_GREEN       0x07E0  // Green
#define RGB565_BLUE        0x001F  // Blue
#define RGB565_CYAN        0x07FF  // Cyan
#define RGB565_MAGENTA     0xF81F  // Magenta
#define RGB565_YELLOW      0xFFE0  // Yellow
#define RGB565_ORANGE      0xFC00  // Orange
#define RGB565_PURPLE      0x780F  // Purple
#define RGB565_BROWN       0x8A22  // Brown
#define RGB565_GRAY        0x8410  // Gray
#define RGB565_PINK        0xF81F  // Pink
#define RGB565_VIOLET      0x901A  // Violet

void fillDisplay(const uint16_t color_RGB565) {
	TFT_setAddress_StartStop(0, 0, TFT_WIDTH, TFT_HEIGHT);

	for (int y = 0; y < TFT_HEIGHT; y++) {
		for (int x = 0; x < TFT_WIDTH; x++) {
			TFT_writeData(color_RGB565);
		}
	}
}
...

 

To test the code and make sure everything was working so far, I added the following to the while(1) loop in the user code section of main.c:

 

main.c

 

...
fillDisplay(RGB565_RED);
HAL_Delay(1000);
fillDisplay(RGB565_WHITE);
HAL_Delay(1000);
fillDisplay(RGB565_BLUE);
HAL_Delay(1000);
...

 

The display then started sequentially flashing red, white, and blue, one color per second.

With all that verified and the display operating, I opened the ioc file and clicked on “Middleware and Software Packs” à “TouchGFX”

 

BWKidd_6-1722629133472.png

 

 

In the Software Packs Component Selector, I had to click the arrow next to the “Graphics Application” to expand it, then select “ToughGFX Generator.

BWKidd_7-1722629133474.png

 

 

In order to enable TouchGFX, I then had to enable the CRC module:

 

BWKidd_8-1722629133477.png

 

Back at the X-CUBE-TOUCHGFX item, I clicked the “Graphics Application” and configured TouchGFX:

 

BWKidd_9-1722629133480.png

After generating code, I got a bunch of errors related to TouchGFX files that were not found, so I then opened TouchGFX Designer, navigated to the projects associated “ApplicationTemplate.touchgfx.part” file, opened it, generated code using the BWKidd_10-1722629133480.png button. I had to repeat this process again (rebuilt the code in STM32CubeIDE and regenerated the code in TouchGFX Designer), and then build the code one more time. I’m not sure why but it worked.

 

BWKidd_11-1722629133483.png

 

 

Next, I needed to integrate my ST7789 driver code into the TouchGFX generated code. This is where I had been stuck for several months. First I moved my ST7789_DRIVER.h/c files into the <Project Folder>/TouchGFX/target/ folder. Then I modified TouchGFXHAL.cpp file (in <Project Folder>/TouchGFX/target/) as follows:

 

At the beginning of the file, I added #include for the ST7789 driver files, then added my initialization functions to the TouchGFXHAL::initialize function:

 

TouchGFXHAL.cpp

 

...
/* USER CODE END Header */

#include <TouchGFXHAL.hpp>

/* USER CODE BEGIN TouchGFXHAL.cpp */
#include "ST7789_DRIVER.h"

using namespace touchgfx;

void TouchGFXHAL::initialize()
{
    // Calling parent implementation of initialize().
    //
    // To overwrite the generated implementation, omit the call to the parent function
    // and implement the needed functionality here.
    // Please note, HAL::initialize() must be called to initialize the framework.
	TFT_init();
	TFT_DisplayOn();

    TouchGFXGeneratedHAL::initialize();
}
...

 

Near the middle I modified the TouchGFXHAL::flushFrameBuffer to set the address within the frame buffer and then send the frame buffer to the display (via FMC)

 

TouchGFXHAL.cpp

 

...
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
    // Calling parent implementation of flushFrameBuffer(const touchgfx::Rect& rect).
    //
    // To overwrite the generated implementation, omit the call to the parent function
    // and implement the needed functionality here.
    // Please note, HAL::flushFrameBuffer(const touchgfx::Rect& rect) must
    // be called to notify the touchgfx framework that flush has been performed.
    // To calculate the start address of rect,
    // use advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect)
    // defined in TouchGFXGeneratedHAL.cpp

	uint16_t* frameBuffer = getTFTFrameBuffer();

	// Set the address window to the area that needs to be updated
	TFT_setAddress_StartStop(rect.x, rect.y, rect.right(), rect.bottom());

	// Write the frame buffer data to the display
	for (int32_t y = rect.y; y < rect.bottom(); y++)
	{
		for (int32_t x = rect.x; x < rect.right(); x++)
		{
			uint16_t color = frameBuffer[y * TFT_WIDTH + x];
			TFT_writeData(color);
		}
	}

    TouchGFXGeneratedHAL::flushFrameBuffer(rect);
}
...

 

Now here’s the part that I’m pretty sure I’m not doing correctly, but its what got things working, at least as far as being able to compose a GUI in TouchGFX and have it show up on my display. This forum topic seems to indicate that others are doing something similar:

 

Calling OSWrappers::signalVSync() in No-OS Applica... - STMicroelectronics Community

 

To trigger vSync, I first created a function that is intended to be called every millisecond. It calls OSWrappers::signalVSync() every 100 counts (100 milliseconds). I put it in TouchGFXHAL.cpp, since this seemed like a convenient spot.

 

TouchGFXHAL.cpp

 

...
/* USER CODE END Header */

#include <TouchGFXHAL.hpp>

/* USER CODE BEGIN TouchGFXHAL.cpp */
#include "ST7789_DRIVER.h"
#include <touchgfx/hal/OSWrappers.hpp>

using namespace touchgfx;

void TOUCHGFXHAL_1msTimerTick(void) {	// used for simulating vSync from display
	static int countUp = 0;

	countUp++;
	if (countUp >= 100) {
		OSWrappers::signalVSync();	// Trigger vSync
		countUp = 0;				// reload counter
	}
}

void TouchGFXHAL::initialize()
...

 

I put a prototype for this function in ST7789_DRIVER.h:

ST7789_DRIVER.h

 

...
void TOUCHGFXHAL_1msTimerTick(void);
...

 

I then added a call to this function in my timer callback in main.c:

main.c

 

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef * htim) {
	// Check which timer triggered interrupt
	if (htim == &htim17) {	// Timer16
		// Run code that should occur every 1 millisecond
		TOUCHGFXHAL_1msTimerTick();
	}
}

 

Generating the code in TouchGFX Designer, and then building and running the code from STM32CubeIDE, I now get the image I added to TouchGFX Designer! Victory! Well, small victory anyways, but I’ll take it.

 

BWKidd_12-1722629360171.jpeg

I've attached my main.c, ST7789_DRIVER.h/c files, modified TouchGFXHAL.cpp, and project IOC files.

 

BWKidd
Associate III

And my ST7789 driver code (since max 3 attachments)