STM32U5 ILI9488 connected to 16-bit FMC and using GPDMA in 2D mode
Hello, I connected an ILI9488 480x320 display on a STM32U5 using FMC in 16-bit mode.
I transfer the framebuffer to the display using GPDMA in 2D mode, and all works fine.
But only as long as I transfer the entire framebuffer or a rectangular region that takes the full width of the screent, where rect_y >= 0 and rect_height < =DISPLAY_SIZE_Y and rect_x = 0 and rect_width = DISPLAY_SIZE_X. This way one line in the frambuffer follows the next line, an easy task for the GPDMA, where 2D mode is inrelevant.
Things don’t work out well when I want rect_x > 0 and rect_width < DISPLAY_SIZE_X, so a random rectangle somewhere from the framebuffer. This way for each new line the GPDMA needs to skip (DISPLAY_SIZE_X - rect_width) pixels to arrive at the next data in the framebuffer. I haven’t been able to configure that. The output image gets extremely slanted and extremely resduced in vertical size if if I make the rectangle 4 pixels more narrow than the display horizontal resolution.
I hope my description is understandable.
This works fine:
GPDMA_FrameBuffer_Transfer_Screen(0, 0, 480, 320);
GPDMA_FrameBuffer_Transfer_Screen(0, 60, 480, 200);But this results in a slanted and vertically reduced image:
GPDMA_FrameBuffer_Transfer_Screen(0, 0, 476, 320);My code looks like this:
Display_result_t GPDMA_Init(void)
{
/* The GPDMA1 Channels 12, 13, 14, and 15 support 2D addressing */
Display_GPDMA.GPDMA_handle = DISPLAY_GPDMA_HANDLE;
Display_GPDMA.GPDMA_handle.Init.Request = DMA_REQUEST_SW; /* Software triggered Memory-to-Memory */
Display_GPDMA.GPDMA_handle.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
Display_GPDMA.GPDMA_handle.Init.Direction = DMA_MEMORY_TO_MEMORY;
/* Initialize GPDMA1, handle_GPDMA1_Channel12, Linked-List Mode, suitable for 2D addressing */
Display_GPDMA.GPDMA_handle.InitLinkedList.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
Display_GPDMA.GPDMA_handle.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
Display_GPDMA.GPDMA_handle.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0; /* Port 0 */
Display_GPDMA.GPDMA_handle.InitLinkedList.TransferEventMode = DMA_TCEM_LAST_LL_ITEM_TRANSFER;
Display_GPDMA.GPDMA_handle.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_NORMAL;
Display_GPDMA.GPDMA_handle.Init.SrcInc = DMA_SINC_INCREMENTED; /* Source increments */
Display_GPDMA.GPDMA_handle.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD; /* 16-bit width, step 2 bytes */
Display_GPDMA.GPDMA_handle.Init.SrcBurstLength = 1;
Display_GPDMA.GPDMA_handle.Init.DestInc = DMA_DINC_FIXED; /* Destination fixed */
Display_GPDMA.GPDMA_handle.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD; /* 16-bit pixel width */
Display_GPDMA.GPDMA_handle.Init.DestBurstLength = 1;
Display_GPDMA.GPDMA_handle.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT1;
Display_GPDMA.GPDMA_handle.Init.Mode = DMA_NORMAL;
if (HAL_DMAEx_List_Init(&Display_GPDMA.GPDMA_handle) != HAL_OK)
{
if (Display.ErrorCallback != NULL)
{
Display.ErrorCallback(DISPLAY_ERROR, __FILE__, __LINE__);
}
return DISPLAY_ERROR;
}
return DISPLAY_OK;
}
Display_result_t GPDMA_FrameBuffer_Transfer_Screen(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
DMA_NodeConfTypeDef node_config = { 0 };
/* The GPDMA1 Channels 12, 13, 14, and 15 support 2D addressing */
Display_GPDMA.GPDMA_handle = DISPLAY_GPDMA_HANDLE;
/* The GPDMA1 Channels supporting 2D addressing are 12, 13, 14, or 15 */
/*
* =========================================================================
* NODE 1 & 2: Set Address Windows (Linear Array Incremented -> Fixed FMC CMD)
* =========================================================================
*/
/* Configure General Node Parameters */
memset(&node_config, 0, sizeof(node_config));
/* Clear queue handle using official HAL layout initialization */
Display_GPDMA.Queue.Head = NULL;
/* Set up Universal Node Behavior Fields */
node_config.NodeType = DMA_GPDMA_2D_NODE;
/* Basic Linear Channel Settings */
node_config.Init.Request = DMA_REQUEST_SW; /* Software-triggered */
node_config.Init.Direction = DMA_MEMORY_TO_MEMORY; /* Direction setting */
node_config.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST; /* Basic burst requirement */
node_config.Init.Mode = DMA_NORMAL; /* Normal list execution */
/* Source settings (Reading the framebuffer) Addressing Rules (Source increments, Destination is fixed) */
node_config.Init.SrcInc = DMA_SINC_INCREMENTED; /* Source increments */
node_config.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD; /* 16-bit width, step 2 bytes */
node_config.Init.SrcBurstLength = 1;
/* Destination settings (Writing to stationary FMC address) */
node_config.Init.DestInc = DMA_DINC_FIXED; /* Destination fixed */
node_config.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD; /* 16-bit pixel width */
node_config.Init.DestBurstLength = 1;
/* Port Assignment and Event Signals */
node_config.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT1;
node_config.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
/* Unused default parameters for safe clearing */
node_config.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
node_config.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
node_config.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
/* Build Node 1 (First 153,600 Bytes / 38,400 Words) */
node_config.DataSize = (uint32_t)w; /* ToDo: Inner loop: Source items in a single horizontal row (16-bit pixels) */
node_config.SrcAddress = (uint32_t)&FrameBuffer[Display.Framebuffer.Show][y][x];
node_config.DstAddress = DISPLAY_16BIT_ADDR_DATA; /* Route to the DATA register (RS High) */
/* Repeated block configuration parameters */
node_config.RepeatBlockConfig.SrcAddrOffset = (DISPLAY_SIZE_X - w); /* Stride skip remaining pixels of line to start of next line in pixels (HALFWORD) */
node_config.RepeatBlockConfig.BlkSrcAddrOffset = 0; /* Force clear uninitialized register properties to prevent memory junk drift */
node_config.RepeatBlockConfig.DestAddrOffset = 0; /* Fixed destination */
node_config.RepeatBlockConfig.BlkDestAddrOffset = 0;
node_config.RepeatBlockConfig.RepeatCount = h * DISPLAY_BYTES_PER_PIXEL; /* ToDo: Total rows (outer loop), entire horizontal block repeat count, Translates to height * 2 bytes */
if (HAL_DMAEx_List_BuildNode(&node_config, &TestNode1) != HAL_OK)
{
if (Display.ErrorCallback != NULL)
{
Display.ErrorCallback(DISPLAY_ERROR, __FILE__, __LINE__);
}
return DISPLAY_ERROR;
}
/* Insert nodes into queue (NULL acts as tail append) */
if (HAL_DMAEx_List_InsertNode_Tail(&Display_GPDMA.Queue, &TestNode1) != HAL_OK)
{
if (Display.ErrorCallback != NULL)
{
Display.ErrorCallback(DISPLAY_ERROR, __FILE__, __LINE__);
}
return DISPLAY_ERROR;
}
/* Initialize DMA and Link Queue */
Display_GPDMA.GPDMA_handle.InitLinkedList.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
Display_GPDMA.GPDMA_handle.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION; /* Process whole list */
Display_GPDMA.GPDMA_handle.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0; /* Access memory list from Port0 */
Display_GPDMA.GPDMA_handle.InitLinkedList.TransferEventMode = DMA_TCEM_LAST_LL_ITEM_TRANSFER;
/* 8. Initialize Channel */
if (HAL_DMAEx_List_Init(&Display_GPDMA.GPDMA_handle) != HAL_OK)
{
if (Display.ErrorCallback != NULL)
{
Display.ErrorCallback(DISPLAY_ERROR, __FILE__, __LINE__);
}
return DISPLAY_ERROR;
}
/* Link the completed memory list schema queue to the hardware peripheral handle of GPDMA Channel 12 */
if (HAL_DMAEx_List_LinkQ(&Display_GPDMA.GPDMA_handle, &Display_GPDMA.Queue) != HAL_OK)
{
if (Display.ErrorCallback != NULL)
{
Display.ErrorCallback(DISPLAY_ERROR, __FILE__, __LINE__);
}
return DISPLAY_ERROR;
}
/* Send the window location and size to be updated to the ILI9488 */
Display_16Bit_Set_DisplayWindow(x, y, w, h);
/* Register the callback before starting the interrupt handler */
HAL_DMA_RegisterCallback(&Display_GPDMA.GPDMA_handle, HAL_DMA_XFER_CPLT_CB_ID, My_DMA_Complete_Callback);
if (HAL_DMAEx_List_Start(&Display_GPDMA.GPDMA_handle) != HAL_OK)
{
if (Display.ErrorCallback != NULL)
{
Display.ErrorCallback(DISPLAY_ERROR, __FILE__, __LINE__);
}
return DISPLAY_ERROR;
}
return DISPLAY_OK;
}
What am I doing wrong?
