2021-01-03 07:13 AM
Hello,
We are working with a custom board using the STM32H753II that connects to a 480x320 ILI9486 LCD via the FMC (8080 interface). The display and its custom-written driver work fine when writing pixel data using the CPU. To improve performance, we want to use the DMA2D available in the MCU for most rendering tasks.
The first thing I tried was using the DMA2D in Mem-to-Mem with Blending and fixed color BG mode (MODE 101) with the FG color format set to A4 for anti-aliased font rendering (size 12x16). OMAR was set to the same address used when writing manually. I expected this to work just fine. However, the displayed characters are extremely distorted and not recognizable unless you know what they should be.
To further debug this issue, I created some test characters. One consists of a gradient from 0x0 to 0xB from left to right. The other consists of a 2px wide vertical line in the center.
All characters were drawn in the order they are shown and right after one another using interrupts.
Here is a close-up of the distorted characters (on cyan background to visualize where pixels were written:
This image shows the gradient character. The black line next to it shows how tall the character is supposed to be. This is the only character from the transfer that was started outside of the DMA2D interrupt.
This image shows three of the of the vertical line characters and one more gradient character. The reddish hue on the last character is just some dirt, sorry about that.
The vertical line gets chopped up into 2x2 squares distributed all over the visible character. The gradient also shifts about every two lines. Interestingly, the characters are not fully drawn. Depending on the character (and whether or not the character was drawn already), some of the bottom lines may be fully or partially missing. Additionally, the shifting looks different depending on whether the MCU is cold-booting or reset using the NRST pin.
A closer look at the gradient characters shows that 4 pixels seem to go missing at a time, though I only checked the first few lines since it is sometimes not clear which pixels are missing.
To ensure that this issue isn't caused by the A4 mode, I also tested the Register-to-Memory mode. There, the same issues could be observed. For a 64x128 rectangle, only about half was actually drawn. When drawing a 64x256 rectangle, only a few extra lines were drawn. The number of pixels drawn seemed to largely be independent of the orientation of the rectangles.
Here is the initialization code:
// Densely packed output
DMA2D->OOR = 0;
DMA2D->AMTCR = (0xA8<<DMA2D_AMTCR_DT_Pos) | DMA2D_AMTCR_EN; // Does not change anything
DMA2D->CR = 0;
__HAL_RCC_DMA2D_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA2D_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2D_IRQn);
Here is the code preparing for text rendering:
// Mem-to-mem with blend and fixed color bg
DMA2D->CR = ((0b101)<<DMA2D_CR_MODE_Pos | GLI_IE_FLAGS);
DMA2D->FGOR = 0; // Pixel data is densely packed
// FGMAR, OMAR, NLR are set per character
// BGMAR, BGOR are not used, set to dummy values
DMA2D->BGMAR = 0;
DMA2D->BGOR = 0;
// CLUT not used
// OCOLR not used, set to dummy values
DMA2D->OCOLR = MAGENTA;
DMA2D->OOR = 0;
// A4 FG format
DMA2D->FGPFCCR = ((0b00)<<DMA2D_FGPFCCR_AM_Pos | (0b1010)<<DMA2D_FGPFCCR_CM_Pos);
DMA2D->FGCOLR = GL_RGB565_TO_RGB888(BLACK);
// RGB888 BG format
DMA2D->BGPFCCR = ((0b00)<<DMA2D_BGPFCCR_AM_Pos | (0b0001)<<DMA2D_BGPFCCR_CM_Pos);
DMA2D->BGCOLR = GL_RGB565_TO_RGB888(WHITE);
// RGB565 Output format
DMA2D->OPFCCR = ((0b010)<<DMA2D_OPFCCR_CM_Pos);
Here is the per-character code:
// Prepare draw area (function is the same as direct write code)
// Using FONT_WIDTH+4 to visually separate characters for testing
gli_set_draw_area(dqelem->x+(FONT_WIDTH+4)*gli_curtextidx, dqelem->y, FONT_WIDTH, FONT_HEIGHT);
// Set font size
DMA2D->NLR = (uint32_t)((FONT_WIDTH)<<DMA2D_NLR_PL_Pos | (FONT_HEIGHT)<<DMA2D_NLR_NL_Pos);
// Fetch the character to be displayed and put its address in FGMAR
uint8_t c = dqelem->text[gli_curtextidx];
DMA2D->FGMAR = (uint32_t)(&(DEFAULT_FONT[c]));
// Increase character to draw for next iteration
gli_curtextidx++;
// (Re-)set output address
DMA2D->OMAR = LCD_DATA_ADDR;
// Send Memory Write command to LCD
GLI_START_DRAW();
// D/I Barriers, don't change anything visually
__DSB();
__ISB();
// Start the transfer
DMA2D->CR |= (uint32_t)DMA2D_CR_START;
Here is the rectangle code:
// Prepare draw area (function is the same as direct write code)
gli_set_draw_area(dqelem_r->x, dqelem_r->y, dqelem_r->w, dqelem_r->h);
// Reg-to-mem
DMA2D->CR = ((0b011)<<DMA2D_CR_MODE_Pos | GLI_IE_FLAGS);
// FGMAR, BGMAR, FGOR, BGOR, FGPFCCR, BGPFCCR are not used, leave as is
// Set OCOLR to output color
DMA2D->OCOLR = dqelem_r->color;
DMA2D->OPFCCR = ((0b010)<<DMA2D_OPFCCR_CM_Pos);
// Set NLR to right size
DMA2D->NLR = (uint32_t)((dqelem_r->w)<<DMA2D_NLR_PL_Pos | (dqelem_r->h)<<DMA2D_NLR_NL_Pos);
// Send MemWrite Command
GLI_START_DRAW();
// D/I Barriers
__DSB();
__ISB();
// Start the transfer
DMA2D->CR |= (uint32_t)DMA2D_CR_START;
The FMC init code is relatively standard. If needed, I can also post it here.
I have already read the DMA2D section of the Reference Manual several times and also checked for errata, but could find nothing there.
Thanks in advance for suggestions!
EDIT: After putting the __HAL_RCC_DMA2D_CLK_ENABLE(); line at the start of the init function, at least the text renders fine. Considering that OOR and CR should already be reset to zero and AMTCR should only cause performance issues, this is still weird and should probably be mentioned somewhere.
The rectangles still don't work, just as before.
Solved! Go to Solution.
2021-01-03 08:53 AM
My guess is, that the FMC setting violates the LCD controller's back-to-back write specification. Try to increase FMC cycle time, by whatever means (probably by increasing FMC_BTRx.DATAST or ADDSET).
JW
2021-01-03 08:53 AM
My guess is, that the FMC setting violates the LCD controller's back-to-back write specification. Try to increase FMC cycle time, by whatever means (probably by increasing FMC_BTRx.DATAST or ADDSET).
JW
2021-01-03 09:32 AM
Yes, that makes sense.
Here are the write timings previously used for the FMC:
// Write Timings
timingAdv.AddressSetupTime = 0; // Min 0 / 0ns
timingAdv.AddressHoldTime = 15; // Not used
timingAdv.DataSetupTime = 2; // Min 1 / 10ns
timingAdv.BusTurnAroundDuration = 2; // Min 2 / 15ns
timingAdv.CLKDivision = 16; // Not used
timingAdv.DataLatency = 17; // Not used
timingAdv.AccessMode = FMC_ACCESS_MODE_A;
I have configured the FMC to be clocked by PLL2R. PLL2R is configured to run at 100MHz. The clock initialization code is based on the generated code from STM32CubeMX.
According to ILI9486L datasheets I could find, the current timings should be correct by themselves. The datasheet calls for a write cycle time of 50ns. My interpretation of the datasheet was that the total clock periods per write cycle were equivalent to BUSTURN+ADDSET+DATAST+1. This would provide the required 50ns. As it turns out, I thought that MODE A caused BUSTURN to always be applied, even when larger transactions are split (since DMA2D is probably using 32/64 bit transactions), but this is only the case for MODE D. Instead of switching to mode D, I increased ADDSET to 1 and DATAST to 3, resulting in the following code that works:
// Write Timings
// Write cycle min. 50ns / 5 cycles
// Calculated as ADDSET+DATAST+1
timingAdv.AddressSetupTime = 1; // Min 0 / 0ns
timingAdv.AddressHoldTime = 15; // Not used
timingAdv.DataSetupTime = 3; // Min 1 / 10ns
timingAdv.BusTurnAroundDuration = 2; // Min 2 / 15ns
timingAdv.CLKDivision = 16; // Not used
timingAdv.DataLatency = 17; // Not used
timingAdv.AccessMode = FMC_ACCESS_MODE_A;
I have also lowered the DMA2D AMTCR DT to 1, since the FMC will now properly limit the speed.
The rectangles now render to exactly one quarter of their intended size, which is probably an unrelated bug.
Thank you!
-- notna