cancel
Showing results for 
Search instead for 
Did you mean: 

Weird horizontal artifacts in touchGFX buttons

BWKidd
Associate III

Hi Folks and Happy New Year!

I've been trying to track down a problem I'm having with my project for several months now. Essentially I'm getting these red-ish lines through portions of my buttons, but only when (I presume) they are drawn or redrawn - namely when the screen first loads, when they are pressed, or when as in this example, they have text drawn over them that updates. 

IMG_7199.JPEG

In the case of the above picture, I've been able to get the lines to appear more consistently by layering some text over top of some of the buttons and then updating that text with each tick where it says "Tick Counter: xxxx". I managed to luck out and have it appear on the "Cancel" of the cancel button this time, though it probably only happens about 1 out of 10 times when the screen loads.

These lines have been appearing pretty consistently throughout my development on the project, and they seem (though I'm still not completely sure) to be happening regardless of my application code. The reason I say this is that I've commented out large portions of my application code and had them still appear. Frustratingly, they are not consistent. I can do a build, have them appear, and then insert a trivial line of code (even a NOP), and then they will disappear, only to reappear again after I add a few more lines.

This really seems to point to some sort of issue where I'm writing off the edge of an array or something (it seems that parts of my frame buffer are getting inadvertently overwritten? But then why just buttons? And why does it still happen when I essentially remove my application code?) 

I've included my TOUCHGFXHAL code that has my driver code in it.

Details on my project:

MCU: STM32L496VGTN

Display: NHD-2.8-240320AF-CSXP-FT (ST7789 Controller) connected via FMC 16-bit parallel

External Flash: MT25QL128A using quad-spi with a custom loader

Firmware details: No RTOS (Bare Metal/super-loop)

Touch GFX Configuration:

BWKidd_0-1735835937379.png

 

Other possible helpful details:

  • My display has no TE line, so I'm updating it manually using a timer at 10Hz by calling Oswrappers::signalVSync(). I had this called in the timer callback function, but recently moved it into the main loop and the problem actually seemed to happen more consistently if anything.
  • I've confirmed that these artifacts are actually contained in the framebuffer, so its not some sort of electrical noise that's corrupting pixels as they're transmitted to the display.

Any tips, tricks, or advice towards debugging this issue would be greatly appreciated. You might say figuring this out has become my new year's resolution!

 

17 REPLIES 17
BWKidd
Associate III

On additional thing I just noticed - if I set the button's alpha value to 254 or less, instead of 255, I don't seem to get the artifacts. I don't know how this factors into the problem, but figured I would mention it.

mathiasmarkussen
ST Employee

Hello,

Can you try to increase the stack size in the linker script?

BWKidd
Associate III

@mathiasmarkussen 

Thank you for your response. I had increased my stack and heap size from their project defaults a while ago - they are currently set to a minimum heap of 0x1000 and minimum stack of 0x2000: 

BWKidd_0-1737552587421.png

Do you think I should try higher? I did try filling the stack with a known pattern to make sure it did not seem to be suffering from overflow, and it appeared to be well behaved and within the limits of the stack boundaries, though it is always possible I mis-interpreted the results.

 

One further note, my second post about the alpha value appears to have been yet another 'red herring', Further testing seems to indicate that changing the alpha value did not really have any effect on the presence or absence of these artifacts.

 

 

Hello Kidd

Have you test to move your images to internal flash instead of external flash ? It might be worth of trying.

Br JTP

BWKidd
Associate III

@JTP1 I'll see if I can attempt this. The project has a lot of text/image assets, so I'll have to create a stripped-down version and see if I can reproduce the artifacts. Is the suspicion that the lines are being caused by something wrong with the external flash interface?

BWKidd
Associate III

@JTP1 The bars do go away when I force everything onto the internal flash - unfortunately this doesn't mean much at the moment, as even small changes in unrelated code causes these lines to come and go from build to build. I certainly am open to examining my quadspi code further, though I'm not sure what I would be looking for.

How do you access the qspi in your application? If it's memory mapped which i guess based on your touchGFXHAL.cpp, I can only think of configuration being on the edge or something like dummy cycles being wrong, although I would think that would be consistent and worse.

You could start by trying to create an image with a single uniform colour and placing that in external memory, and reading some contents of it into a buffer and confirming that you have the correct colour. You should probably try for the very beginning, somewhere in the middle, and the very end of the image. If you are using memory mapped mode you can also just inspect the memory in a debugger.

If that looks fine you could put it in a UI and continously invalidate it. You can do that by setting an interaction that runs on every tick, either with code calling invalidate(); or selecting the show widget option and selecting your image.

See if you have any artifacts in that case, otherwise you can overlay one or several box, possibly with some alpha blending, and see if that triggers the problem.

If you have access to a logic analyzer that is capable of decoding qspi, you could try to capture some data and verify that all the data sent by the flash on the interface is correct.

 

Another thing entirely is your screen updates. If the screen is running at 60 Hz, which is likely, your timer updates the frame buffer more than once per display frame. Is the FMC interface fast enough to complete in 10 ms?
Try setting your timer at a lower rate and see if you still have the glitches.

 

mathiasmarkussen
ST Employee

Hello,

I have a new suggestion, that I think you should try first.

At the time that void TouchGFXHAL::flushFrameBuffer is called, the rendering is done, but DMA2D may still be transferring asset data from external flash to the frame buffer. To handle this, you can add dma.flush(); before your display code.

I still think you should consider changing your tick rate to 16 ms or so, unless a 10ms tick rate is important for you for some reason.

Using a DMA to transfer data to the FMC (using interrrupts and a control flag to make sure the transfer is done before you set up a new one) would probably give a good performance gain. You may need to use linked list mode to be able to break up the data in multiple chunks

You could also consider a partial frame buffer. This would take up less memory and transfer less data to the display at a time, so the DMA will work in normal mode. If you want an example of how this can be achieved with FMC (8 bit in this case) and DMA transfer to the display, you can look at the NUCLEO-H563ZI + RVA35HI board setup in the designer. I think it is available from version 4.24 onwards.

@mathiasmarkussen Thank you for these ideas. I'm accessing my QSPI external flash in memory mapped mode:

 

static void MX_QUADSPI_Init(void)
{

  /* USER CODE BEGIN QUADSPI_Init 0 */

  /* USER CODE END QUADSPI_Init 0 */

  /* USER CODE BEGIN QUADSPI_Init 1 */

  /* USER CODE END QUADSPI_Init 1 */
  /* QUADSPI parameter configuration*/
  hqspi.Instance = QUADSPI;
  hqspi.Init.ClockPrescaler = 2;
  hqspi.Init.FifoThreshold = 4;
  hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE;
  hqspi.Init.FlashSize = 23;
  hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE;
  hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
  hqspi.Init.FlashID = QSPI_FLASH_ID_2;
  hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
  if (HAL_QSPI_Init(&hqspi) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN QUADSPI_Init 2 */

  // Initialize QUADSPI Flash Memory
   if (QSPI_ResetChip() != HAL_OK) {
 	  ;	// TODO: Add Error Handling
   }

   HAL_Delay(1);

   if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
 	  ;	// TODO: Add Error Handling
   }

   if (QSPI_WriteEnable() != HAL_OK) {
 	  ;	// TODO: Add Error Handling
   }

   if (QSPI_Configuration() != HAL_OK) {
 	  ;	// TODO: Add Error Handling
   }

   if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
 	  ;	// TODO: Add Error Handling
   }

   // Setup memory mapping
 	if (CSP_QSPI_EnableMemoryMappedMode() != HAL_OK) {
 		;	// TODO: Add Error Handling
 	}
  /* USER CODE END QUADSPI_Init 2 */

}

I suppose that my dummy cycles could be wrong, but the only place I have problems are where there are touch-gfx buttons, never over top of backgrounds or other drawing objects, and I'm using the same code in my custom loader which seems to function correctly - would dummy cycle setup being wrong still allow such things to operate properly?

I'm currently testing my code with a simple screen with 3 buttons and a text area that is drawn such that in overlaps with all three buttons (I've attached a video showing what it looks like running):

BWKidd_0-1737729529759.png

 

I'm invalidating the "Test Text Area" with every tick.

I'm also using a slower tick - 10x per second rather than 60, since I don't need smooth animations for this application.

Per some suggestions made to me outside this forum I have moved my screen update code into my timer tick routine and I also changed to using DMA2D to move the data, which as you alluded to substantially improves the speed it takes to update the display:

 

void TOUCHGFXHAL_signalVSync() {	// Wrapper for triggering VSync

    // VSync has occurred, increment TouchGFX engine vsync counter
    HAL::getInstance()->vSync();
    // VSync has occurred, signal TouchGFX engine
    OSWrappers::signalVSync();

    if (refreshRequested && !displayRefreshing)
    {
        // Swap frame buffers immediately instead of waiting for the task to be scheduled in.
        // Note: task will also swap when it wakes up, but that operation is guarded and will not have
        // any effect if already swapped.

        touchgfx::HAL::getInstance()->swapFrameBuffers();

        displayRefreshing = true;
        HAL_GPIO_WritePin(TP_PC0_GPIO_Port, TP_PC0_Pin, GPIO_PIN_HIGH);	// Raise pin for timing display write
        // Set window, enable display reading to GRAM, transmit buffer using DMA
        setLCDwindow(0, 0, TFT_WIDTH, TFT_HEIGHT);
        LCD_IO_WriteReg(RAMWR);

        uint32_t sourceAddress = (uint32_t)(currFbBase);
		uint32_t destinationAddress = (uint32_t)(FMC_BANK1_MEM); // FMC / LCD Data Register

		// Configure DMA2D
		hdma2d.Init.Mode = DMA2D_M2M;                     	// Memory to Memory mode
		hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565;      	// Output format: RGB565
		hdma2d.Init.OutputOffset = 0;	// Set the offset based on the width of the rectangle being drawn

		if (HAL_DMA2D_Init(&hdma2d) != HAL_OK)
		{
			Error_Handler();
		}

		// Note it appears that DMA transfers are limited to about 70,000 pixels (RGB565/2-bytes each) of data so transfers are
		// broken up into two equal transfers

		// Start the 1st half of the transfer
		if (HAL_DMA2D_Start(&hdma2d, sourceAddress, destinationAddress, TFT_WIDTH, TFT_HEIGHT/2) != HAL_OK)
		{
			Error_Handler();
		}

		// Wait for transfer to complete
		if (HAL_DMA2D_PollForTransfer(&hdma2d, 1000) != HAL_OK)
		{
			Error_Handler();
		}

		// Start the 2nd half of the transfer (Note transfer starts half-way through frameBuffer, which is
		// TFT_WIDTH*TFT_HEIGHT/2 pixels, but each pixel is 2 bytes, so actual offset is sourceAddress+TFT_WIDTH*TFT_HEIGHT
		if (HAL_DMA2D_Start(&hdma2d, sourceAddress+TFT_WIDTH*TFT_HEIGHT, destinationAddress, TFT_WIDTH, TFT_HEIGHT/2) != HAL_OK)
		{
			Error_Handler();
		}

		// Wait for transfer to complete
		if (HAL_DMA2D_PollForTransfer(&hdma2d, 1000) != HAL_OK)
		{
			Error_Handler();
		}

		HAL_GPIO_WritePin(TP_PC0_GPIO_Port, TP_PC0_Pin, GPIO_PIN_LOW);	// Raise pin for timing display write

        displayRefreshing = false;
    }
}