LVGL compatible DMA enabled LCD drivers for Teensy 4.x

honey_the_codewitch

Well-known member
I noticed the existing drivers require an entire framebuffer to be used to support DMA.
I retooled and stripped these drivers so that they can do partial updates to the display, the way libraries like LVGL and htcw_uix expect.
They don't do anything but initialize, rotate, and send bitmaps to the display.

The SSD1351 currently can't be rotated to 1 or 3 without scrambling the output. Not sure why yet, but it works otherwise, and since it's 128x128 i don't consider it a show stopper.

One nice thing is the lcd_spi_driver_t4 makes it easy to implement new displays, complete with DMA support.

Shared Lib Code: https://github.com/codewitch-honey-crisis/lcd_spi_driver_t4
SSD1351 https://github.com/codewitch-honey-crisis/ssd1351_t4
ST7789 https://github.com/codewitch-honey-crisis/st7789_t4

To use, initialize with the pins (I haven't tested software SPI yet, so use a CS pin for your DC line and use the hardware pins for maximum speed and reliability)
Then set your flush complete callback off the class. You'll use this to notify a library like LVGL that the most recent DMA transfer was completed
Then call begin() - the order of this and the above doesn't really matter, you can set callbacks any time
You can call rotation(x) to change the rotation of the display. Works like the T3 drivers, or adafruit drivers
You can then call flush() to send a bitmap to the display. Usually libs like LVGL will call this.

Note that these are NOT general purpose graphics libraries. They are display drivers meant to be used with libraries like LVGL that don't drive hardware directly.

Also, these libraries are structured to be Arduino and PlatformIO ready, but I have not registered them with either of those repos. To use them presently, you'll have to download them manually from the above links.
 
This is neat, I might give it a go with a 320*320 ST7796s display I have (I've written an 8080 async driver for it, but might try SPI first)

Would be great if you could provide an example sketch on your main repo.
 
Folks I found a bug with the DMA that I didn't catch before. It's corrupting once you stop updating the display. As long as you continue to update it works fine. I'm running it down, but I'm waiting on some hardware i need first that should get here any day.
 
@honey_the_codewitch - Any luck on finding the issue?

With several different UI projects going on, plus it looks like Zephyr drivers and the like are geared around LVGL. So I thought
I would try out your driver and see how well LVGL works. But I am novice in using LVGL. I tried your example in the ST7789_h library
but it fails to compile with:

Code:
In file included from c:\users\kurte\documents\arduino\libraries\lvgl\src/lv_init.h:16,
                 from c:\users\kurte\documents\arduino\libraries\lvgl\lvgl.h:21,
                 from c:\Users\kurte\Documents\Arduino\libraries\lvgl\src/lvgl.h:16,
                 from C:\Users\kurte\AppData\Local\Temp\.arduinoIDE-unsaved2025820-31312-1iufs41.npry\sketch_sep20a\sketch_sep20a.ino:3:
c:\users\kurte\documents\arduino\libraries\lvgl\src/lv_conf_internal.h:60:18: fatal error: ../../lv_conf.h: No such file or directory
   60 |         #include "../../lv_conf.h"                /* Else assume lv_conf.h is next to the lvgl folder. */
      |                  ^~~~~~~~~~~~~~~~~
compilation terminated.

So I know I am missing something for it to work here.

It does work for my Arduino Giga with their display shield, as their LVGL example sketch does build:
1758384009809.png


Thanks,
Kurt
 
@honey_the_codewitch - Any luck on finding the issue?

With several different UI projects going on, plus it looks like Zephyr drivers and the like are geared around LVGL. So I thought
I would try out your driver and see how well LVGL works. But I am novice in using LVGL. I tried your example in the ST7789_h library
but it fails to compile with:

Code:
In file included from c:\users\kurte\documents\arduino\libraries\lvgl\src/lv_init.h:16,
                 from c:\users\kurte\documents\arduino\libraries\lvgl\lvgl.h:21,
                 from c:\Users\kurte\Documents\Arduino\libraries\lvgl\src/lvgl.h:16,
                 from C:\Users\kurte\AppData\Local\Temp\.arduinoIDE-unsaved2025820-31312-1iufs41.npry\sketch_sep20a\sketch_sep20a.ino:3:
c:\users\kurte\documents\arduino\libraries\lvgl\src/lv_conf_internal.h:60:18: fatal error: ../../lv_conf.h: No such file or directory
   60 |         #include "../../lv_conf.h"                /* Else assume lv_conf.h is next to the lvgl folder. */
      |                  ^~~~~~~~~~~~~~~~~
compilation terminated.

So I know I am missing something for it to work here.

It does work for my Arduino Giga with their display shield, as their LVGL example sketch does build:
View attachment 38205

Thanks,
Kurt
You know what? I haven't, and then work and life beckoned and I completely forgot about it. If you're going to use Arduino, it handles LVGL badly. You need to put an lv_conf.h in your Arduino/Libraries folder next to the LVGL folder if i recall correctly. And yeah, that's global to all projects, and it's as terrible as it seems. This is why I use PlatformIO to target Arduino, frankly. I actually need to pick this up again. I just forgot about it. I'll make some time this week.
 
Last edited:
You know what? I haven't, and then work and life beckoned and I completely forgot about it. If you're going to use Arduino, it handles LVGL badly. You need to put an lv_conf.h in your Arduino/Libraries folder next to the LVGL folder if i recall correctly. And yeah, that's global to all projects, and it's as terrible as it seems.
I hear you! However somehow the Arduino GIGA is setup to use LVGL without having to use the global config file.

I believe there graphics library has stuff in it to specify a config file that is part of that library.

That is their example starts off with:
Code:
#include "Arduino_H7_Video.h"
#include "Arduino_GigaDisplayTouch.h"

#include "lvgl.h"
And I believe the Arduino_H7_Video that ships with each build install has the config.

That is their src directory has:
1758388186827.png

So would interesting to try to setup within your library like st7789_t4
Will play later
 
I looked into this a bit. It seems the Arduino H7 video library includes an LVGL config for LVGL 8.x and 9.x
On windows you'll find it here. In linux, i forget where they put it, but you should find it off your home dir somewhere
<system drive>:\Users\<you>\AppData\Local\Arduino15\packages\arduino\hardware\mbed_giga\4.4.1\libraries\Arduino_H7_Video\src
The trouble is, I don't know how they are telling LVGL to find that file, since it's not in the standard location I think? It's really hard to tell with all the build munging going on. In any case, it's not exactly duplicatable without having access to a custom hardware/board profile I think. It's being included through munging during the build.
 
Last edited:
I have been looking for a screen driver library to run LVGL, and I just found this thread. This library seems like it would be ideal for my latest project, a power logging instrument based around teensy 4.1 with PSRAM, still in prototype stage and unfinished.

However, I am using an ili9488 which I believe has a three byte (18 bit) colour space. I had a quick look to see what would need to be done to add a driver but I then saw your base driver has new functions including flush8 and flush16 - I think I would need a flush24?
 
I have been looking for a screen driver library to run LVGL, and I just found this thread. This library seems like it would be ideal for my latest project, a power logging instrument based around teensy 4.1 with PSRAM, still in prototype stage and unfinished.

However, I am using an ili9488 which I believe has a three byte (18 bit) colour space. I had a quick look to see what would need to be done to add a driver but I then saw your base driver has new functions including flush8 and flush16 - I think I would need a flush24?
The issue with 24-bit (even 18-bit with 6 extra NOP bits) is the DMA facilities for this chip expect AFAIK, 8, 16, or 32 bit transfers. It also likes to do byte swapping which I haven't figured out how to disable. That creates some issues trying to communicate with that display.

An aside, but I have a gripe about the design of the ILI9488s regarding that, and that is that 16-bit works over i8080 but not SPI. I don't know what possessed them to set it up this way, but it has led me to look for options that perform better. SPI is already slow, and then adding another 1/3 of empty overhead into the color stream over it is just bad, especially since it only gains you 2 bits in color depth, which at least to my eyes does not make a big difference. I know "a different display would be better" is probably not what you're looking for here, but I feel I would be remiss if I did not at least bring up that among IoT displays, for the above reasons, it's probably my least favorite.

I'd like to support it, but the DMA facilities in the Teensy aren't very well documented, and I had a really difficult time getting this stuff working as it was. I might be able to if I can find a DMA capable ILI9488 driver for the teensy, but i don't know if one was produced. I'll at least look into it, but I can't promise anything.

Edit: Thinking on it, it should be possible to make it work using 8 bit transfers, but this creates another issue, and that is that you're going to run into serious limits in terms of how big those subbitmaps can be that it sends to the display, because each pixel is 3 bytes, and you only have 32768 transfer iterations you can make. divide that by 3, that's how many pixels you can send at once.
 
Last edited:
That's a shame, I may need to switch processors again. The ili9488 works quite nicely over SPI running LVGL on the ESP32-S3 using the excellent LovyanGFX driver. I will see if performance is acceptible using standard drivers, but I guess probably not.
 
Why would you want to run th 9488 in 18 or 24 bit mode?
Its a waste of resources on these MCUs

I have two 8080 drivers for the 948x displays that run on FlexIO.
One lib supports the T4.1 on FlexIO3 - no DMA support here, just interrupt based reloading - semi hardware support

Another lib for the MicroMod Teensy - on FlexIO2 with DMA support. I used it on a product I designed with @Dogbone06 called the Hurt Alert (hurtalert.shop) and it works great on 16 bit color with LVGL.

If you want to stay on the SPI bus, there is ILI9488_t3 lib wish supports async transfer - I’ve used this as well and it’s great for what it is!
 
Because, sadly, I only have the SPI pins brought out on the screen module I used. I now recall that the ESP32-S3 used a parallel connection on the WT32-SC01 module, not SPI as I said above, whoops.

It's a nice screen module with a capacitive touch but perhaps I will need to lower my performance expectations. I bet the Teensy is a ripper on the 9488 with a parallel connection.

I will get skpang's demo which uses the ILI9488_t3 lib up and running on my hardware and see how it feels. It's not super important, this is just a one-off instrument for my workbench because I'm too cheap to buy a joulescope and have parts on-hand. Thanks!
 
Full screen updates with LVGL over SPI will be a tad slow on any MCU.
But if you’re just updating some labels and small objects, SPI will be fast enough!
If you keep the wires/traces low, you could increase the SPI bus speed up to 80Mhz in some cases
 
Very nice, thanks @KurtE that is indeed a drop-in replacement for the unit I'm using, changing the pinout is no issue because that part of my prototype is point-to-point flyleads. I have bought one, I will be able to continue development using the 9488 then swap out the hardware and drivers when it arrives in a few weeks.
 

Attachments

  • signal-2025-09-27-11-45-42-519.jpg
    signal-2025-09-27-11-45-42-519.jpg
    281 KB · Views: 28
  • signal-2025-09-27-11-45-42-519.jpg
    signal-2025-09-27-11-45-42-519.jpg
    283 KB · Views: 31
  • signal-2025-09-27-11-45-42-519.jpg
    signal-2025-09-27-11-45-42-519.jpg
    348.9 KB · Views: 31
Last edited:
I do not have an ST7796, and have not used one. Assuming it works similarly to the ST7789 it should be possible to write a driver for it pretty easily using my baseline library.
 
Sure, when the new screen arrives, I'll have a go at writing a driver using this library. It does look pretty easy!

Are you familiar with the LovyanGFX driver library? It's class-based configuration is very clever. It's really nice to be able to declare all the details of the screen and touch hardware within the project code. Here is an example from another project of mine.

C++:
//LovyanGFX Display Class Config
class LGFX : public lgfx::LGFX_Device {
  lgfx::Panel_ST7796  _panel_instance;  // ST7796UI
  lgfx::Bus_Parallel8 _bus_instance;    // MCU8080 8B
  lgfx::Light_PWM     _light_instance;
  lgfx::Touch_FT5x06  _touch_instance;
public:
  LGFX(void) {
    //Bus
    { auto cfg = _bus_instance.config();
      cfg.freq_write = 60000000;   
      cfg.pin_wr = 47;             
      cfg.pin_rd = -1;             
      cfg.pin_rs = 0;             
      // LCD data interface, 8bit MCU (8080)
      cfg.pin_d0 = 9;             
      cfg.pin_d1 = 46;             
      cfg.pin_d2 = 3;             
      cfg.pin_d3 = 8;             
      cfg.pin_d4 = 18;             
      cfg.pin_d5 = 17;             
      cfg.pin_d6 = 16;             
      cfg.pin_d7 = 15;             
      _bus_instance.config(cfg);   
      _panel_instance.setBus(&_bus_instance);     
    }
    //LCD Panel
    { auto cfg = _panel_instance.config();   
      cfg.pin_cs           =    -1; 
      cfg.pin_rst          =    4; 
      cfg.pin_busy         =    -1;
      cfg.panel_width      =   320;
      cfg.panel_height     =   480;
      cfg.offset_x         =     0;
      cfg.offset_y         =     0;
      cfg.offset_rotation  =     0;
      cfg.dummy_read_pixel =     8;
      cfg.dummy_read_bits  =     1;
      cfg.readable         =  true;
      cfg.invert           = true;
      cfg.rgb_order        = false;
      cfg.dlen_16bit       = false;
      cfg.bus_shared       =  true;
      _panel_instance.config(cfg);
    }
    //Backlight
    { auto cfg = _light_instance.config();   
      cfg.pin_bl = 45;             
      cfg.invert = false;           
      cfg.freq   = 44100;           
      cfg.pwm_channel = 7;         
      _light_instance.config(cfg);
      _panel_instance.setLight(&_light_instance); 
    }
    //Touch
    { auto cfg = _touch_instance.config();
      cfg.x_min      = 0;
      cfg.x_max      = 319;
      cfg.y_min      = 0; 
      cfg.y_max      = 479;
      cfg.pin_int    = 7; 
      cfg.bus_shared = true;
      cfg.offset_rotation = 0;
      cfg.i2c_port = 1;
      cfg.i2c_addr = 0x38;
      cfg.pin_sda  = 6;   
      cfg.pin_scl  = 5;   
      cfg.freq = 400000; 
      _touch_instance.config(cfg);
      _panel_instance.setTouch(&_touch_instance); 
    }
    setPanel(&_panel_instance);
  }
};
 
Ive got an 8080 driver I wrote recently for the ST7796s, but its a 320x320 variant.. and only supports the MicroMod Teensy..
0c62daaf-e001-40a7-a7da-2e4102df4d22.jpeg
 
I'm familiar with Lovyan. I do not like its configuration setup. I think it's both ugly, and requires too much typing. I do not like what I have seen of the code. I do not like that it integrates support for a (limited) select touch panels. what if i want to use one it doesn't support? I do not like that it has a bunch of drawing functions that create 90s era graphics when most of the time it's just a vehicle for something like LVGL. Those are all just personal opinions, but you asked, so I'm offering them.

My ideal is something that just sends bitmaps to the display. And a separate touch driver for reading a panel. That way I don't have to hope that my panel driver supports my GT911, my FT6x36 or whatever. To be honest i really wish more people would just abstract and create demand draw frameworks like LVGL. I even made one eventually called htcw_uix (i also contribute to LVGL, but I needed something I could use for work that I could add to as required). That way you can keep the hardware separate from the drawing concerns.
 
@Rezo That code could probably be adapted. The initialization codes and setting the address window and such will largely be the same over i8080 and SPI. It should be relatively easy to adapt it to a SPI version using the driver core I provided in the OP.
 
I think there is already an SPI version by @KurtE

At the end of the day, what really varies between the ST and ILI displays is the initialization code and initial config.
They use the same register IDs for setting the address window, writing to ram etc

So for basic operations (write an array to a region) for LVGL, you don’t need to do a full function implementation of all the drawing functions.
 
oh interesting. yeah i could make one, but i can't test it. instead, I'll leave an open offer to help should one of you decide to implement this driver using the code I provided, but i'll need your help to actually test it. I'm loath to order another screen, just because I've ordered so many that I only ever use once, it's getting ridiculous.
 
Back
Top