2026-05-05 1:47 AM
Hi,
first time TouchGFX user but not new to graphics programming and hoping to get some help understanding a TouchGFX problem I'm having on a Riverdi RVT50HQSNWC01-B I'm thinking of using for an automotive dashboard display.
If I don't throttle my display changes, the display buffers don't have the same content. The buffer with the most complete looking content does have a small glitch towards the bottom right hand side of the RPM segmented display, so I'm guessing it's not as simple as TouchGFX didn't get around to doing all updates on both buffers but more like it ran out of space somewhere to manage the updates? I've tried putting invalidate()'s either side of making changes, as suggested in this (https://community.st.com/t5/stm32-mcus-touchgfx-and-gui/double-buffer-one-buffer-is-not-updated/m-p/165271#M9822) thread, but that doesn't help. The only thing that helps is to throttle updates inside the screen handleTickEvent() to every other tick (and not do them the first 3 ticks either or it freezes ...) and then the display is rock solid.
Is this expected behaviour? Is there a buffer size I can increase to overcome this? I'm trying to understand if TouchGFX is not the right solution for this project and I should look elsewhere, or I just need to understand the limitations and how to work around them better and it'll be fine?
I've tried to include screen grabs to better quantify how much content there is on this screen. Note that the ValueElementType1 elements aren't even dynamic yet, they're just displaying the content from the designer program so there's going to be a lot more going on when those are live.
I'm assuming the segmented RPM component (many circle segments) is likely to be the main problem here so I'll paste code for that but am happy to share any of the code:
void RPM_SPEED_Type_1::Refresh()
{
//
// Speed numbers
//
static uint32_t lastSpeed = -1 ;
if( gSpeed != lastSpeed ) {
lastSpeed = gSpeed ;
// convert to MPH
uint32_t speed = (gSpeed * 621371u + 500000u) / 1000000u;
uint32_t speedFraction = speed - ( ( speed / 10 ) * 10) ;
speed /= 10 ;
RPM_T1_SPEED_NUM_TEXT1.invalidateContent();
touchgfx::Unicode::snprintf(RPM_T1_SPEED_NUM_TEXT1Buffer,RPM_T1_SPEED_NUM_TEXT1_SIZE,"%d",speed);
RPM_T1_SPEED_NUM_TEXT1.invalidateContent();
//RPM_T1_SPEED_NUM_TEXT1.invalidate();
// Now the fraction bar graph
static uint8_t speedFractionStates[5] = { 1,1,1,1,1 } ;
static touchgfx::BoxWithBorder* speedFractionBoxes[5] = { &RPM_T1_SPEED_FracBar1, &RPM_T1_SPEED_FracBar2, &RPM_T1_SPEED_FracBar3, &RPM_T1_SPEED_FracBar4, &RPM_T1_SPEED_FracBar5 };
speedFraction+=1;
speedFraction/=2;
for( uint i = 0; i < 5; i++ ) {
if( ( speedFraction > i ) && ( speed < 100 ) ) {
if( speedFractionStates[i] == 0 ) {
speedFractionStates[i] = 1 ;
speedFractionBoxes[i]->invalidate();
speedFractionBoxes[i]->setVisible(true);
speedFractionBoxes[i]->invalidate();
}
} else {
if( speedFractionStates[i] != 0 ) {
speedFractionStates[i] = 0 ;
speedFractionBoxes[i]->invalidate();
speedFractionBoxes[i]->setVisible(false);
speedFractionBoxes[i]->invalidate();
}
}
}
}
//
// MPH / KPH : Calib text
//
static uint16_t lastTripFlagsSpeed = -1;
if( ( gTripFlags&(16+8) ) != lastTripFlagsSpeed ) {
lastTripFlagsSpeed = gTripFlags&(16+8) ;
uint32_t calib = 1 ;
if( (gTripFlags & 16) != 0 ) calib = 2 ;
RPM_T1_TRIPFLAGStextArea.invalidateContent();
if( ( gTripFlags & 8 ) != 0 ) {
touchgfx::Unicode::snprintf(RPM_T1_TRIPFLAGStextAreaBuffer,RPM_T1_TRIPFLAGSTEXTAREA_SIZE,"MPH : %d",calib);
} else {
touchgfx::Unicode::snprintf(RPM_T1_TRIPFLAGStextAreaBuffer,RPM_T1_TRIPFLAGSTEXTAREA_SIZE,"KPH : %d",calib);
}
RPM_T1_TRIPFLAGStextArea.invalidateContent();
//RPM_T1_TRIPFLAGStextArea.invalidate();
}
//
// RPM graphic and numbers
//
static uint8_t rpmStates[24] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };
static touchgfx::Circle* rpmSegments[24] = { &RPM_T1_seg_01, &RPM_T1_seg_02, &RPM_T1_seg_03, &RPM_T1_seg_04, &RPM_T1_seg_05, &RPM_T1_seg_06, &RPM_T1_seg_07, &RPM_T1_seg_08,
&RPM_T1_seg_09, &RPM_T1_seg_10, &RPM_T1_seg_11, &RPM_T1_seg_12, &RPM_T1_seg_13, &RPM_T1_seg_14, &RPM_T1_seg_15, &RPM_T1_seg_16,
&RPM_T1_seg_17, &RPM_T1_seg_18, &RPM_T1_seg_19, &RPM_T1_seg_20, &RPM_T1_seg_21, &RPM_T1_seg_22, &RPM_T1_seg_23, &RPM_T1_seg_24 };
static touchgfx::PainterRGB565* rpmPainters[24] = { &RPM_T1_seg_01Painter, &RPM_T1_seg_02Painter, &RPM_T1_seg_03Painter, &RPM_T1_seg_04Painter, &RPM_T1_seg_05Painter, &RPM_T1_seg_06Painter, &RPM_T1_seg_07Painter, &RPM_T1_seg_08Painter,
&RPM_T1_seg_09Painter, &RPM_T1_seg_10Painter, &RPM_T1_seg_11Painter, &RPM_T1_seg_12Painter, &RPM_T1_seg_13Painter, &RPM_T1_seg_14Painter, &RPM_T1_seg_15Painter, &RPM_T1_seg_16Painter,
&RPM_T1_seg_17Painter, &RPM_T1_seg_18Painter, &RPM_T1_seg_19Painter, &RPM_T1_seg_20Painter, &RPM_T1_seg_21Painter, &RPM_T1_seg_22Painter, &RPM_T1_seg_23Painter, &RPM_T1_seg_24Painter };
static uint32_t rpmColours[24];
static uint32_t minRPM = 500 ;
static uint32_t maxRPM = 4300 ;
static uint32_t lastRPM = maxRPM ;
static uint32_t lastSegRPM = maxRPM ;
static uint32_t rpmOffColour = touchgfx::Color::getColorFromRGB(0, 30, 0);
uint32_t segRPMRange = ( maxRPM - minRPM ) / 24 ;
static bool rpmGotColours = false ;
if( rpmGotColours == false ) {
rpmGotColours = true ;
for( int i = 0 ; i < 24 ; i++ ) {
rpmColours[ i ] = rpmPainters[ i ] -> getColor() ;
}
}
// Text RPM display
if( gRPM != lastRPM ) {
lastRPM = gRPM ;
RPM_T1_RPMtextArea.invalidateContent();
touchgfx::Unicode::snprintf(RPM_T1_RPMtextAreaBuffer,RPM_T1_RPMTEXTAREA_SIZE,"%d",gRPM);
RPM_T1_RPMtextArea.invalidateContent();
//RPM_T1_RPMtextArea.invalidate();
}
// Segmented RPM display
uint32_t rpm;
int32_t rpmDelta = gRPM - lastSegRPM ;
if( rpmDelta > 0 ) {
if( rpmDelta > (int32_t)segRPMRange ) {
rpm = lastSegRPM + segRPMRange ;
} else {
rpm = gRPM ;
}
} else {
if( (0-rpmDelta) > (int32_t)segRPMRange ) {
rpm = lastSegRPM - segRPMRange ;
} else {
rpm = gRPM ;
}
}
if( rpm != lastSegRPM ) {
lastSegRPM = rpm ;
if( rpm > maxRPM ) rpm = maxRPM ;
if( ( rpm > 0 ) && ( rpm < minRPM ) ) rpm = minRPM ;
if(rpm > 0) {
rpm -= minRPM ;
// scale RPM (0 - maxRPM) to segment index 0 - 23
rpm += segRPMRange / 2 ; // add half a segment
rpm /= segRPMRange ;
if( rpm > 23 ) rpm = 23 ;
}
// update all of the segments
for(uint32_t i=0; i < 24; i++ ) {
if( (rpm > 0 ) && ( i <= rpm ) ) {
// all segments that should be lit
if(rpmStates[i] == 0) {
rpmStates[i] = 1 ;
//rpmSegments[i]->setVisible(true);
rpmSegments[i]->invalidate();
rpmPainters[ i ] -> setColor( rpmColours[ i ] ) ;
rpmSegments[i]->invalidate();
}
} else {
// all segments that should be hidden / dim
if( rpmStates[i] != 0 ) {
rpmStates[i] = 0 ;
//rpmSegments[i]->setVisible(false);
rpmSegments[i]->invalidate();
rpmPainters[ i ] -> setColor( rpmOffColour ) ;
rpmSegments[i]->invalidate();
}
}
}
}
}
Thanks for reading this far!
2026-05-05 10:41 AM - edited 2026-05-05 10:45 AM
Please place code in snip </>
static uint32_t rpmColours[24];
static uint32_t minRPM = 500 ;
static uint32_t maxRPM = 4300 ;
static uint32_t lastRPM = maxRPM ;
static uint32_t lastSegRPM = maxRPM ;
static uint32_t rpmOffColour = touchgfx::Color::getColorFromRGB(0, 30, 0);
uint32_t segRPMRange = ( maxRPM - minRPM ) / 24 ;
static bool rpmGotColours = false ;
if( rpmGotColours == false ) {
rpmGotColours = true ;
for( int i = 0 ; i < 24 ; i++ ) {
rpmColours[ i ] = rpmPainters[ i ] -> getColor() ;
}
}and for your design try show how you realise refresh call . Plus use containers objects require precise knowledge, i recommend start without containers.
Plus invalidate only required objects = compare getcolor and required new color and invalidate once only changing segment.
2026-05-05 11:36 AM
Hi,
thanks for taking the time to reply. My original experiment was without containers and I got the same result. I already track the state of each segment so I know if the state changed, the colour has to change - no need to compare the colours. I also limit the number of segments that can change per Refresh() so there's not actually that much change going on in any single frame.
Refresh() is called from the screen handleTickEvent(). In the no containers version, all that code as in the handleTickEvent() and it had the same behaviour.