Issues porting to STM32F407 + ILI9325 + DMA

Description

Hi there,

I’m trying to port LVGL to be used with a STM32F407 (on the STM32F4-DISCO board), and had success after following the porting guide and using the less efficient flush_cb that writes pixel by pixel to memory. When I later try to use DMA, and do as in the lv_port_stm32F429_disco repository, I can see how first screen is written to the display, but not all of it (20-30 lines left, which keep random noise).

After this point, if I try to write some more objects to the display, nothing else shows up

What MCU/Processor/Board and compiler are you using?

STM32F407, STM32F4-DISCO, gcc, LVGL 7.1, and ILI9325 320x240 Display

What do you want to achieve?

Port LVGL to this MCU + Display, using DMA buffer transfer

What have you tried so far?

I tried to write to the display pixel by pixel, manually copying the lvgl buffer to the display buffer, with successful results:


static void my_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
  int32_t x, y;

  for (y = area->y1; y <= area->y2; y++) {
    for (x = area->x1; x <= area->x2; x++) {

      ili9325_WritePixel(x, y, color_p->full);
      color_p++;

    }
  }
  lv_disp_flush_ready(disp_drv);
}

But when I configure it to use DMA, the display is not showing the image on the whole surface, showing random data on the last 20 - 30 lines, and the data is updated no more, as if it would be writing to a wrong memory place and device display would halt. (Program in MCU continues)

Code to reproduce

static __IO uint16_t * my_fb = (__IO uint16_t *) 0x60020000;

static lv_disp_drv_t disp_drv;
static int32_t x1_flush;
static int32_t y1_flush;
static int32_t x2_flush;
static int32_t y2_fill;
static int32_t y_fill_act;
static lv_color_t *buf_to_flush;

static void my_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
  
  /*Return if the area is out the screen*/
  if (area->x2 < 0) return;
  if (area->y2 < 0) return;
  if (area->x1 > DISP_HOR_RES - 1) return;
  if (area->y1 > DISP_VER_RES - 1) return;

  /*Truncate the area to the screen*/
  int32_t act_x1 = area->x1 < 0 ? 0 : area->x1;
  int32_t act_y1 = area->y1 < 0 ? 0 : area->y1;
  int32_t act_x2 = area->x2 > DISP_HOR_RES - 1 ? DISP_HOR_RES - 1 : area->x2;
  int32_t act_y2 = area->y2 > DISP_VER_RES - 1 ? DISP_VER_RES - 1 : area->y2;

  x1_flush = act_x1;
  y1_flush = act_y1;
  x2_flush = act_x2;
  y2_fill = act_y2;
  y_fill_act = act_y1;
  buf_to_flush = color_p;


  /*##-7- Start the DMA transfer using the interrupt mode #*/
  /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */
  /* Enable All the DMA interrupts */
  HAL_StatusTypeDef err;
  err = HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,
                         (uint32_t) buf_to_flush,
                         (uint32_t) &my_fb[y_fill_act * DISP_HOR_RES + x1_flush],
                         (x2_flush - x1_flush + 1));
  if (err != HAL_OK) {
    while (1);    /*Halt on error*/
  }
}

void DMA_TransferComplete(DMA_HandleTypeDef *han) {

  y_fill_act++;

  if (y_fill_act > y2_fill) {
    lv_disp_flush_ready(&disp_drv);
  } else {
      buf_to_flush += x2_flush - x1_flush + 1;
    /*##-7- Start the DMA transfer using the interrupt mode ####################*/
    /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */
    /* Enable All the DMA interrupts */
    if (HAL_DMA_Start_IT(han, (uint32_t) buf_to_flush, (uint32_t) &my_fb[y_fill_act * DISP_HOR_RES + x1_flush],
                         (x2_flush - x1_flush + 1)) != HAL_OK) {
      while (1);    /*Halt on error*/
    }
  }
}

void MX_DMA_Init(void)
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();

  /* Configure DMA request hdma_memtomem_dma2_stream0 on DMA2_Stream0 */
  hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;
  hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0;
  hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
  hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
  hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;
  hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;
  hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH;
  hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
  hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_1QUARTERFULL;
  hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
  hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;
  if (HAL_DMA_Init(&hdma_memtomem_dma2_stream0) != HAL_OK)
  {
    Error_Handler();
  }

  /* DMA interrupt init */
  hdma_memtomem_dma2_stream0.XferCpltCallback = DMA_TransferComplete;
  /* DMA2_Stream0_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);

}

void MX_FSMC_Init(void)
{
  FSMC_NORSRAM_TimingTypeDef Timing = {0};

  /** Perform the SRAM1 memory initialization sequence
  */
  hsram1.Instance = FSMC_NORSRAM_DEVICE;
  hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
  /* hsram1.Init */
  hsram1.Init.NSBank = FSMC_NORSRAM_BANK1;
  hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
  hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
  hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
  hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
  hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
  hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
  hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
  hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
  hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
  hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
  hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
  hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
  hsram1.Init.PageSize = FSMC_PAGE_SIZE_NONE;
  /* Timing */
  Timing.AddressSetupTime = 15;
  Timing.AddressHoldTime = 15;
  Timing.DataSetupTime = 255;
  Timing.BusTurnAroundDuration = 15;
  Timing.CLKDivision = 16;
  Timing.DataLatency = 17;
  Timing.AccessMode = FSMC_ACCESS_MODE_A;
  /* ExtTiming */

  if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
  {
    Error_Handler( );
  }

}

Screenshot and/or video

Result with DMA method:

I think part of your problem is that you have an extra lv_disp_flush_ready call in flush_cb, immediately after the DMA transaction starts. Since you already have one in DMA_TransferComplete (and that is the correct spot to put it) you can just remove the one in flush_cb.

Thanks @embeddedt, I’ve tried your suggestion an edited the code, as you’re certainly right that that call doesn’t belongs there. Sadly, my problem is still there.

The other suspicious thing is that you’re calling lv_disp_flush_ready with &disp_drv instead of just disp_drv. I don’t know if you retyped the code or copy-pasted it; if it’s the latter, you may want to double check that.

Thanks again. In flush_cb at the first snippet there are as you said copy-paste issues… ampersand is not needed as disp_drv its passed to the function as a pointer. (Anyways, this first snippet was working).

In the second snippet, the call lv_disp_flush_ready(&disp_drv) is correct as display_drv is a global of type lv_disp_drv_t. I’ve added in the example the related global variables.

I was wondering, if the problem could be something related to the speed at which DMA is writing to the LCD GRAM?

I think you need to use the return value from lv_disp_drv_register… pretty sure a global variable won’t work.

Thanks @embeddedt for your suggestion. I’ve checked and lv_disp_drv_register returns a pointer to a lv_disp_t object, not a lv_disp_drv_t, that is what lv_disp_flush_ready needs… Also, in the lv_port_stm32F429_disco port, they are using globals to hold it as well, so I think that the problem is not there.

Can you try to set the screen to be less than the actual resolution and see if you have the same behaviour?

Thanks @papadkostas for your suggestion. I reduced the display size and found the same behavior, ≈10% of the new expected area not being written.

Nevertheless, while playing a bit more, I finally I found the problem: In the flush_cb function, I needed to call:

 ili9325_WriteReg(LCD_REG_32, (ILI9325_LCD_PIXEL_HEIGHT - 1 - Ypos));
 ili9325_WriteReg(LCD_REG_33, Xpos);
 LCD_IO_WriteReg(LCD_REG_34);

before attempting to write to the display. First two lines set the cursor in the desired position, and third line prepares to write to GRAM.

As I’m already indicating the position by means of setting the registers, I changed the third parameter in HAL_DMA_Start_IT

(uint32_t)&my_fb[y_fill_act * DISP_HOR_RES + x1_flush]

to

(uint32_t) my_fb

The final flush callback would be:

static void my_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {

  /*Return if the area is out the screen*/
  if (area->x2 < 0) return;
  if (area->y2 < 0) return;
  if (area->x1 > DISP_HOR_RES - 1) return;
  if (area->y1 > DISP_VER_RES - 1) return;

  /*Truncate the area to the screen*/
  int32_t act_x1 = area->x1 < 0 ? 0 : area->x1;
  int32_t act_y1 = area->y1 < 0 ? 0 : area->y1;
  int32_t act_x2 = area->x2 > DISP_HOR_RES - 1 ? DISP_HOR_RES - 1 : area->x2;
  int32_t act_y2 = area->y2 > DISP_VER_RES - 1 ? DISP_VER_RES - 1 : area->y2;

  x1_flush = act_x1;
  y1_flush = act_y1;
  x2_flush = act_x2;
  y2_fill = act_y2;
  y_fill_act = act_y1;
  buf_to_flush = color_p;

  /* Set Cursor */
  ili9325_SetCursor(x1_flush, y1_flush);

  /* Prepare to write GRAM */
  LCD_IO_WriteReg(LCD_REG_34);

  /*##-7- Start the DMA transfer using the interrupt mode #*/
  /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */
  /* Enable All the DMA interrupts */
  HAL_StatusTypeDef err;

  err = HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,
                         (uint32_t) buf_to_flush,
                         (uint32_t) my_fb,
                         (x2_flush - x1_flush + 1));

  if (err != HAL_OK) {
    while (1);    /*Halt on error*/
  }
}

void DMA_TransferComplete(DMA_HandleTypeDef *han) {
  y_fill_act++;

  if (y_fill_act > y2_fill) {
    lv_disp_flush_ready(&disp_drv);

  } else {

    buf_to_flush += x2_flush - x1_flush + 1;
    /*##-7- Start the DMA transfer using the interrupt mode ####################*/
    /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */
    /* Enable All the DMA interrupts */
    if (HAL_DMA_Start_IT(han, (uint32_t) buf_to_flush, (uint32_t) my_fb,
                         (x2_flush - x1_flush + 1)) != HAL_OK) {
      while (1);    /*Halt on error*/
    }
  }
}

void ili9325_SetCursor(uint16_t Xpos, uint16_t Ypos)
{
  
  ili9325_WriteReg(LCD_REG_32, (ILI9325_LCD_PIXEL_HEIGHT - 1 - Ypos));
  ili9325_WriteReg(LCD_REG_33, Xpos);

}

Now other problems arises, but I’ll post them in another topic.