2024-11-06 07:42 AM - edited 2024-11-07 04:11 AM
I have a custom board, which uses STM32H747IXH6 and MB1166 LCD screen from the related discovery board. I am trying to get TouchGFX up and running on this custom setup, but in my PCB design, I missed out the dedicated TE pin in the connector (I'm following the RaspberryPi DSI convention of DSI CLK, Data Lanes and I2C only). I can initialise the display, but I cannot get my TouchGFX application running. The process at the moment is as follows:
Init DSI Host
Reset the LCD display
Config DSI PLL
Config Host Timeouts
Config PHY Timers
Config LPCommand
Config AdaptedCommand Mode
Invert clock and data pins (to correct for PCB error)
Register busIO for OTM8009A
Init OTM8009A (as per Discovery examples)
Config Flow Control
Force RX Low Power
Init LTDC
Active low VSYNC and HSYNC polarity
803x483 total width and height (800x480 + porches)
Format RGB888
Double frame buffer
Start address 0xD0000000
Init FMC (IS42S32800J)
Init as Bank 2
Init TouchGFX
Start TouchGFX Thread
Now when TouchGFX starts, taskEntry sits at OSWrappers::waitForVSync(); I assume that this VSync should be called from HAL_DSI_TearingEffectCallback in TouchGFXHAL, but this callback never fires. It appears a tearing effect is requested earlier in taskEntry by LCD_ReqTear, which in the discovery code writes OTM8009A_CMD_TEEON to the LCD driver. I have modified LCD_ReqTear to send OTM8009A_CMD_WRTESCN with a param of 533 (0x2,0x15) instead, as per the CMDMode_TearingEffect example, but the TearingEffectCallback is still not triggered.
What am I missing here? I can't attach the full project for confidentiality reasons, but I'm happy to share main.c and TouchGFXHAL.cpp
I'm aware also that TearingEffectCallback and EndOfRefreshCallback still contain code that assumes the display is split in two (as per both the examples I have borrowed from), I wouldn't have thought this was the issue however, as neither of these callbacks ever fire.
Edit
I have updated taskEntry to enable the DSI interrupts and call a DSI_Refresh before the first wait for Vsync, but the callbacks still don't fire. Examining the SFRs shows that the DSI transfer hangs on BUSY (DSI_WISR), so presumably the callback doesn't fire due to the refresh never finishing. What could be preventing the transfer from occurring?
Solved! Go to Solution.
2024-11-14 08:10 AM
Sorted! I got it working with the example and then copied the TouchGFXHAL code to my custom project. The colours are wrong, but we're off to a good start!
2024-11-07 04:11 AM
Post updated to add more information
2024-11-07 07:00 AM
Update:
I have moved the OTM8009A initialisation code to the end of the LTDC initialisation routine. The general flow is now:
Reset LCD via GPIO
Init DSI
Init LTDC
Start DSI
Init OTM8009A via DSI
Modify DSI flow control & low power RX
Request scan
uint8_t ScanLineParams[2];
uint16_t scanline = 500;
ScanLineParams[0] = scanline >> 8;
ScanLineParams[1] = scanline & 0x00FF;
HAL_DSI_LongWrite(&hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 2, 0x44, ScanLineParams);
HAL_DSI_ShortWrite(&hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, OTM8009A_CMD_TEEON, 0x00);
Turn the display on and call a refresh
HAL_DSI_ShortWrite(&hdsi,0,DSI_DCS_SHORT_PKT_WRITE_P0,OTM8009A_CMD_DISPON,0x00);
HAL_DSI_Refresh(&hdsi);
Then start the TouchGFX task
HAL_DSI_EndOfRefreshCallback is now called once, but no tearing effect callback is generated, thus VSync does not fire and TouchGFX doesn't take over the management of the process. Based on AN46860, I have enabled the bus turnaround request and the tearing effect acknowledge request.
2024-11-07 07:37 AM
I'm not sure where this goes wrong, but I can try to explain what I would do in this case, maybe there is a missing step somewhere in that process.
First, since you should now be getting TE signals on the bus rather than on a GPIO, set that under DSIHOST in CubeMX:
Now,when your DSI interrupt fires, it should be able to determine if a TE signal has been received:
HAL_DSI_TearingEffectCallback() is weak, so you can define that yourself, which it seems from what you write is already done in TouchGFXHAL.
Are you still running adapted command mode, or are you in video mode?
Have you looked at the code that split the display in two? I get that that can be a very confusing solution, but what basically happens is that the LTDC frame buffer pointer is moved, and then an LTDC transfer to the screen is triggered. If a transfer is never triggered, I'm not sure there would be a callback, but I'm not sure wether you actually ever get to that point?
2024-11-07 07:54 AM
I'm definitely running in adapted command mode, yes. I've also worked out how the code that splits the display in two works and have adapted my DSI EOR callback and LTDC setup to match.
At the end of my peripheral init (User code 2 in main), I call the following
HAL_DSI_Stop(&hdsi);
DSI_LPCmdTypeDef LPCmd = {0};
LPCmd.LPGenShortWriteNoP = DSI_LP_GSW0P_ENABLE;
LPCmd.LPGenShortWriteOneP = DSI_LP_GSW1P_ENABLE;
LPCmd.LPGenShortWriteTwoP = DSI_LP_GSW2P_ENABLE;
LPCmd.LPGenShortReadNoP = DSI_LP_GSR0P_ENABLE;
LPCmd.LPGenShortReadOneP = DSI_LP_GSR1P_ENABLE;
LPCmd.LPGenShortReadTwoP = DSI_LP_GSR2P_ENABLE;
LPCmd.LPGenLongWrite = DSI_LP_GLW_ENABLE;
LPCmd.LPDcsShortWriteNoP = DSI_LP_DSW0P_ENABLE;
LPCmd.LPDcsShortWriteOneP = DSI_LP_DSW1P_ENABLE;
LPCmd.LPDcsShortReadNoP = DSI_LP_DSR0P_ENABLE;
LPCmd.LPDcsLongWrite = DSI_LP_DLW_ENABLE;
LPCmd.LPMaxReadPacket = DSI_LP_MRDP_ENABLE;
LPCmd.AcknowledgeRequest = DSI_ACKNOWLEDGE_DISABLE;
if (HAL_DSI_ConfigCommand(&hdsi, &LPCmd) != HAL_OK)
{
Error_Handler();
}
HAL_DSI_Start(&hdsi);
uint8_t ScanLineParams[2];
uint16_t scanline = 500;
ScanLineParams[0] = scanline >> 8;
ScanLineParams[1] = scanline & 0x00FF;
HAL_DSI_LongWrite(&hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 2, 0x44, ScanLineParams);
//HAL_DSI_ShortWrite(&hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, OTM8009A_CMD_TEEON, 0x00);
HAL_DSI_ShortWrite(&hdsi,
0,
DSI_DCS_SHORT_PKT_WRITE_P0,
OTM8009A_CMD_DISPON,
0x00);
HAL_DSI_Refresh(&hdsi);
Then go on to start the touchGFX task as per usual. I would have thought this command would have generated a TE on the DSILink via the Refresh call, which I believe kicks off the LTDC transfer. As touchGFX has not started at this point, I'd expect to just see the random noise of the uninitialised buffer on screen (which I do), but still no TE interrupt generated.
Interestingly, as soon as I call DSI_Start, the GPRDE bit is set in the DSI ISR1 register. Could this be an indicator for something? I also get constant LTDC interrupts with no flags in the LTDC ISR, so I assume this is cause by GPRDE.
2024-11-08 05:18 AM
The reference manual for H747 has this detail about the GPRDE bit:
In the original TouchGFX project, the LTDC is turned off when the DSI_Start command is called. It may lead to some undefined behaviour if the DSI is in the middle of a transfer when the DSI host starts up, so I would try to call
You might also have noticed that there is a 1 ms delay in the original HAL_DSI_EndOfRefreshCallback. I do not actually know why it is there, but I do know that the whole thing stops working if it is removed. My guess is that it ensures that the DSI bus or LTDC is ready for the modification that will happen because of the half-screen thing, so you could also just try to add a delay before starting the DSI host.
2024-11-14 03:14 AM
I've made progress, but still no complete working solution. I'm now using a timer to trigger vsync every 20ms from main:
When my code launches, the first framebuffer is written to the LCD. This is done by forcing a DSI Refresh from TouchGFXHAL::taskEntry(). The refresh that writes to the screen is on line 154.
This is fine, so long as my display is static. We get through backPorchExited() and go into the infinite loop, checking for vsync - although nothing ever actually changes in the framebuffer as there's nothing new to display.
If the framebuffer needs to be updated, either through user code in the view or putting an animated image in the display, the first refresh draws the initial framebuffer, but backPorchExited() never returns, it gets stuck on Model::Tick().
I have to assume that there is an issue with the updating of the framebuffer. Its address is allocated in CubeMX - does this need manually setting in TouchGFXHAL::initialize()?
And DMA2D is configured with a matching format
We're using external SRAM here, but FMC is configured and correctly as I can see the framebuffer in the memory monitor
What other issues could cause the hang when copying the new framebuffer?
2024-11-14 03:57 AM
Furthermore, I can see that TouchGFXHAL::endFrame() and therefore flushFrameBuffer() are never called
2024-11-14 04:15 AM
The stack trace when the task hangs is as follows
Could it be something to do with the DCache issues on STM32H7 devices?
2024-11-14 04:22 AM
One thing that could lead to the tick method hanging is trying to measure MCU usage but not having the correct hooks in the OS to support this.
Do you have a call to enableMCULoadCalculation(true) somewhere? I believe removing that (or setting it to false in TouchGFXHAL::initialize()) would solve the problem so TouchGFX can execute.
I would advise using double buffers if you have space for that in RAM. In general, it is easier to have their address set by allocation than by manual address.
If set to by allocation, you can define what sector they go in with the linker script, you can see how that is handled for your particular compiler in most of the board setups that ship with the TouchGFX designer.
As you set it by address, it will be placed at that address.
You can see what the start address of the currently active framebuffer is in the LTDC->CFBAR register.
But with single frame buffer and no TE signal to synchronize, I would expect tearing on the display when anything moves.