Need help with ST7789 display

Windorey

Active member
Hi, I have a library running (LVGL) with the ST7789_t3 driver on my Teensy 4.1, and I want to use DMA instead of blocking updates.
Currently, this is my code.
Code:
#include <Arduino.h>
//LVGL>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include <lvgl.h>
#include <ST7735_t3.h> // Hardware-specific library
#include <ST7789_t3.h> // Hardware-specific library
#include <SPI.h>

#define TFT_MISO 12
#define TFT_MOSI 11 //a12
#define TFT_SCK 13  //a13
#define TFT_DC 10
#define TFT_CS 9
#define TFT_RST 8

// For 1.54" TFT with ST7789
ST7789_t3 tft = ST7789_t3(TFT_CS, TFT_DC, TFT_RST);

/*Change to your screen resolution*/
static const uint32_t screenWidth = 320;
static const uint32_t screenHeight = 240;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * 10];

lv_disp_t *disp;

/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{

  tft.writeRect(area->x1, area->y1, (area->x2 - area->x1 + 1), (area->y2 - area->y1 + 1), (uint16_t *)color_p);
  lv_disp_flush_ready(disp);
}


// ENCODER
#define PIN_ENCODE_A 16
#define PIN_ENCODE_B 4
#define PIN_ENCODE_BTN 17
long enc_current_position = 1;
long enc_last_position = 0;
#include <RotaryEncoder.h>  // https://github.com/mathertel/RotaryEncoder
RotaryEncoder encoder(PIN_ENCODE_A, PIN_ENCODE_B, RotaryEncoder::LatchMode::TWO03);

void enc_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
  enc_current_position = encoder.getPosition();
  data->enc_diff = enc_current_position - enc_last_position;
  enc_last_position = enc_current_position;
  data->state = digitalRead(PIN_ENCODE_BTN) ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
}

void enc_update() {
  encoder.tick();
}




void setup()
{



  lv_init();


  tft.init(240, 320); // initialize a ST7789 chip, 240x240 pixels
  tft.setRotation(1);
  tft.fillScreen(ST7735_BLACK);


  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 10);

  /*Initialize the display*/
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  disp = lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/

  pinMode(PIN_ENCODE_BTN, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(PIN_ENCODE_A), enc_update, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_ENCODE_B), enc_update, CHANGE);
}

void loop()
{
  lv_timer_handler(); /* let the GUI do its work */
}

Pay attention to `my_disp_flush`, it is the part I want to use DMA in.

I have already implemented this functionality with my ESP32, and because of it, it gets higher performance than my Teensy. But I want to use my Teensy. Thank you.
 
Hi, I have a library running (LVGL) with the ST7789_t3 driver on my Teensy 4.1, and I want to use DMA instead of blocking updates.
Currently, this is my code.
Code:
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{

  tft.writeRect(area->x1, area->y1, (area->x2 - area->x1 + 1), (area->y2 - area->y1 + 1), (uint16_t *)color_p);

  tft.updateScreenAsync();   // DMA the stuff to the screen

  lv_disp_flush_ready(disp);
}


  lv_init();


  tft.init(240, 320); // initialize a ST7789 chip, 240x240 pixels

  tft.useFrameBuffer(true); // Use DMA - if you already have a frame buffer, there's an API to set that memory for the driver to use

  tft.setRotation(1);
  tft.fillScreen(ST7735_BLACK);


  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 10);

  /*Initialize the display*/
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  disp = lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/

  pinMode(PIN_ENCODE_BTN, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(PIN_ENCODE_A), enc_update, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_ENCODE_B), enc_update, CHANGE);
}

Pay attention to `my_disp_flush`, it is the part I want to use DMA in.

I have already implemented this functionality with my ESP32, and because of it, it gets higher performance than my Teensy. But I want to use my Teensy. Thank you.

You may be able to simply turn on the teensy 4 frame buffer, and then request an async update - this triggers the use of existing DMA code in the ST7735_t3 driver.

Check out the updateScreenAsync(), useFrameBuffer(bool), and setFrameBuffer(uint16_t *) methods in the ST7735_t3 driver.

There's also a call back on Frame Complete available.

Tim
 
Hello, It is sort of working.
I did get a massive FPS boost ( before 68 now 1004! ), but now there's some artifacts.

Here is a video showing the artifacts

(edit) Is there a way to change the display's SPI clock speed?
 
Hello, It is sort of working.
(edit) Is there a way to change the display's SPI clock speed?

I had to fork this lib and hard code the SPI speed. I have been able to set the SPI speed up to 75MHz, but I have read other people were able to reach 80MHz.

I am going to test this in my setup (also using LVGL). Although, I remember the DMA frame buffers using a large amount of RAM2, which could be a deal breaker.
 
Here is a video showing the artifacts

I was able to reproduce this behavior. I am failure certain the issue here is that the DMA transfer happens async and LVGL thinks that the buffers are ready to be refilled as soon as `lv_disp_flush_ready(disp);` is called. So there is some level of buffer clobbering going on. I think this can be solved by having a global flag (again, probably requires a fork of the display lib) that indicates when the DMA transfer is complete, which allows LVGL to continue with the next buffer. Note: this will only work if the LVGL tick/task handler is being called from your primary execution context... it needs to be an interruptible by your primary interrupt. However, I have not tested this because I do not have the RAM2 available in my application for the DMA buffers, but if I will let you know if I get this working.
 
Here is a video showing the artifacts

I was able to reproduce this behavior. I am failure certain the issue here is that the DMA transfer happens async and LVGL thinks that the buffers are ready to be refilled as soon as `lv_disp_flush_ready(disp);` is called. So there is some level of buffer clobbering going on. I think this can be solved by having a global flag (again, probably requires a fork of the display lib) that indicates when the DMA transfer is complete, which allows LVGL to continue with the next buffer. Note: this will only work if the LVGL tick/task handler is being called from your primary execution context... it needs to be an interruptible by your primary interrupt. However, I have not tested this because I do not have the RAM2 available in my application for the DMA buffers, but if I will let you know if I get this working.
 
"So there is some level of buffer clobbering going on."

Thinking on this more. I believe the data is getting clobbered while it is getting transferred to the DMA buffer. DMA is inherently asnyc but the underlying lib should block on the call to `tft.updateScreenAsync();` if there is an ongoing transfer (I did not look at the code to confirm this). I believe there is some kind of sync required just to prevent data corruption, but would need to did into it.
 
There is a library for Teensy 4.x that works amazingly with LVGL. It is called ILI9341_T4. Maybe that library can be ported to support ST7789? It has super high performance, VSync and a lot more nice features.
 
Back
Top