LVGL compatible DMA enabled LCD drivers for Teensy 4.x

Thanks. Yes, there's a problem with importing the library. PlatformIO isn't seeming to accept the library from the Arduino Lib folder.
 
I imported the working Arduino file into PlatformIO with ST7735_t3 Lib and encountered the same error when compiling. I don't know why grrrrr
 
Last edited:

I need help, please. Where is the error?
Sometimes I have to go away and eat, and stuff like that...

Looking in the @h4yn0nnym0u5e repository for the referenced ST7735_t3 library, the source for ST7735_t3.cpp currently only goes to line 4782.
You're looking at the "master" branch, whereas we're using the "/dev/big-screen-t4" one.

I imported the working Arduino file into PlatformIO and encountered the same error when compiling. I don't know why grrrrr
So ... did the Arduino sketch start working for you?

What version of Teensyduino are you using in the Arduino IDE and in PlatformIO? I'm on 1.60, which means I don't compile that line in; I think it has a bug, probably, but reverting to test against 1.59 will be a pain. You could try changing that line to
C++:
    _dma_data[_spi_num]._pDMAtx->begin(true);
...but no guarantees it'll work.
 
i used Arduino IDE Version: 2.3.8
Date: 2026-02-25T15:38:21.789Z
CLI Version: 1.4.1

I changed the line of code. The compiler make no errors. But the programcode is crashing on my hardware.
 
Last edited:
I've been using 1.8.19 - I'll try 2.3.8 tomorrow, though I don't expect it to make a difference.

You didn't mention which Teensyduino you're using. I'm guessing not 1.60, and my suggested change may well be the cause of the crash - I couldn't test it, as noted above.
 
Hello. Thank you so much for your great help :)
I tried your new fix. It works with Arduino IDE 2.3.8 (including Teensyduino 1.6 ?) and PlatformIO 6.1.19 (Visual Studio Code).

For my project I'm using this 3.5inch Capacitive Touch Display from Waveshare

Screenshot 2026-03-09 185742.png


PS: I must apologize for my impatience. Sometimes small problems like this can hold up the whole project, and I still have a long way to go ;)
This is my new DIY project.. https://forum.pjrc.com/index.php?threads/new-teensy-4-1-diy-synthesizer.63255/post-366158
Best regards from germany. Rolf
 
Last edited:
Great, glad it's working for you now. I'll tidy it up a bit, but there won't be any material changes, just deletion of commented-out code. If you need further support for this code, you might want to post on this thread; I think the one we're in now was supposed to be LVGL-related.

No worries, I know how frustrating it is when you can't move your project forward due to what looks like a minor obstacle.
 
Hallo
Hello again ;)

I want to draw a 480x320 pixel JPG image onto my TFT monitor and I'm using the JPEGDecoder.h library for this. The image information is displayed correctly with jpegInfo(). But with renderJPEG(0, 0) no image is displayed. I copied the Star Trek image 240x77 Pixel from the library. Pic2 is an StarTrec image to scaled off 480x320 pixel.

Pic from Display

20260313_171709.jpg


StarTrek Pic
StarTrek.jpg
 

Attachments

  • Draw_JPG_Image.ino
    6.3 KB · Views: 21
  • Pic2.jpg
    Pic2.jpg
    20.1 KB · Views: 18
Last edited:
I think the problem is in the renderJPEG() function..
C:
if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height())
        {
            // open a window onto the screen to paint the pixels into
            tft.setAddrWindow(mcu_y, mcu_x, mcu_x + win_w - 1, mcu_y + win_h - 1);

            // push all the image block pixels to the screen
            while (mcu_pixels--)
                tft.pushColor(*pImg++); // Send to TFT 16 bits at a time
        }
 
The TFT_eSPI library also includes the JPEGDecoder.h library. The renderJPEG() function works and draw an jpg image.
I would like to use the TFT_eSPI library, but it does not support DMA with ST7796 displays. SPI speed on Teensy4.1 is 60MHz

Test Jpg image reading
 
Last edited:
Hallo. Good news :)
I'm currently using the TJpg_Decoder.h library to draw JPEG images on my ST7796S. It works with DMA, but the image format doesn't seem to be correct yet. The image is distorted and only black and white.

20260314_182934.jpg
StarTrek2.jpg
 
Not sure how well it would work for you, but the Jpeg decoder I have played with is:
My SD Viewer sketches also view PNG files using:

Example output read in from SD Card...:
1773520517088.png
 
Last edited:
This is the decoder on an ST7796 display.
The code is up in my project: https://github.com/KurtE/mtp_tft_picture_view
Conditional on several different displays

The code allows you to give a few different functions, to do things like, read in the next N bytes and to display a rectangle:
The code in mine does:
Code:
#ifdef __JPEGDEC__
JPEGDEC jpeg;


void processJPGFile(const char *name, bool fErase) {
    Serial.println();
    Serial.print(F("Loading JPG image '"));
    Serial.print(name);
    Serial.println('\'');
    uint8_t scale = 1;
    if (jpeg.open(name, myOpen, myClose, myReadJPG, mySeekJPG, JPEGDraw)) {
        int image_width = jpeg.getWidth();
        int image_height = jpeg.getHeight();
        int decode_options = 0;
        Serial.printf("Image size: %dx%d", image_width, image_height);
        switch (g_JPGScale) {
            case 1:
                scale = 1;
                decode_options = 0;
                break;
            case 2:
                scale = 2;
                decode_options = JPEG_SCALE_HALF;
                break;
            case 4:
                scale = 4;
                decode_options = JPEG_SCALE_QUARTER;
                break;
            case 8:
                scale = 8;
                decode_options = JPEG_SCALE_EIGHTH;
                break;
            default:
                {
                    if ((image_width > g_jpg_scale_x_above[SCL_16TH]) || (image_height > g_jpg_scale_y_above[SCL_16TH])) {
                        decode_options = JPEG_SCALE_EIGHTH | JPEG_SCALE_HALF;
                        scale = 16;
                    } else if ((image_width > g_jpg_scale_x_above[SCL_EIGHTH]) || (image_height > g_jpg_scale_y_above[SCL_EIGHTH])) {
                        decode_options = JPEG_SCALE_EIGHTH;
                        scale = 8;
                    } else if ((image_width > g_jpg_scale_x_above[SCL_QUARTER]) || (image_height > g_jpg_scale_y_above[SCL_QUARTER])) {
                        decode_options = JPEG_SCALE_QUARTER;
                        scale = 4;
                    } else if ((image_width > g_jpg_scale_x_above[SCL_HALF]) || (image_height > g_jpg_scale_y_above[SCL_HALF])) {
                        decode_options = JPEG_SCALE_HALF;
                        scale = 2;
                    }
                }
        }
        if (fErase && ((image_width / scale < g_tft_width) || (image_height / scale < g_tft_height))) {
            FillScreen((uint16_t)g_background_color);
        }

        if (g_center_image) {
            g_image_offset_x = (g_tft_width - image_width / scale) / 2;
            g_image_offset_y = (g_tft_height - image_height / scale) / 2;
        } else {
            g_image_offset_x = 0;
            g_image_offset_y = 0;
        }
        g_image_scale = scale;
        Serial.printf("Scale: 1/%d Image Offsets (%d, %d)\n", g_image_scale, g_image_offset_x, g_image_offset_y);

        jpeg.decode(0, 0, decode_options);
        jpeg.close();
    } else {
        Serial.println("Was not a valid jpeg file");
    }
}


int32_t myReadJPG(JPEGFILE *handle, uint8_t *buffer, int32_t length) {
    if (!myfile) return 0;
    return myfile.read(buffer, length);
}
int32_t mySeekJPG(JPEGFILE *handle, int32_t position) {
    if (!myfile) return 0;
    return myfile.seek(position);
}

int JPEGDraw(JPEGDRAW *pDraw) {
    if (g_debug_output) Serial.printf("jpeg draw: x,y=%d,%d, cx,cy = %d,%d\n",
                                      pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);

    writeClippedRect(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight, pDraw->pPixels);
    return 1;
}
#endif
The JPEGDraw function is the callback to draw the next chunk. I have it calling one of my own functions:

In the same sketch...
 
I also had good results with Larry Bank's JPEGDEC library.

It gives you callbacks with blocks of pixels. So it's not specific to any particular display. You create a function it calls with a pointer to its JPEGDRAW struct. You fill in the code to actually put those JPEGDRAW pixels onto whatever display you're using.

For example, with ILI9341_t3

Code:
// Function to draw pixels to the display
void JPEGDraw(JPEGDRAW *pDraw) {
  //Serial.printf("jpeg draw: x,y=%d,%d, cx,cy = %d,%d\n",
     //pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
  tft.writeRect(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight, pDraw->pPixels);
}
 
Thank you so much for your fantastic support. I can now draw JPEG images on the ST7796S at DMA speed.
The example "ILI9431_t3_slideshow" from Bitbank2, with a few modifications, works very well (see video).

ST7796_t3 Lib with DMA. SPI speed is 60MHz and Teensy4.1 is 720MHz
 
Last edited:
There's still a small bug in my code! I used PlatformIO and VS Code

in der Zeile 385
C:
if (jpeg.open((const char *)name, myOpen, myClose, myRead, mySeek, JPEGDraw))
                {
                    jpeg.decode(0, 0, 0);
                    jpeg.close();
                    tft.updateScreenAsync();
                }
                else
                {
                    Serial.print("error = ");
                    Serial.println(jpeg.getLastError(), DEC);
                }



Bug
Unbenannt.jpg


C:
/*
Jeannie II
Polyphonic DIY Synthesizer/Sampler
(c) by Rolf Degen and Andre' Laska März 2026
*/

#define TFT_MISO 12
#define TFT_MOSI 11
#define TFT_SCK 13
#define TFT_DC 8
#define TFT_CS 10
#define TFT_RST 9
#define TFT_BL 28 // TFT Backlight

// LED chip 74HC595
#define LED_latchPin 30
#define LED_clockPin 31
#define LED_dataPin 32
#define LED_oePin 29

// Keys Interrupt
int PIN_ENC_INT = 25;

#define BUILTIN_SDCARD 254

#include <Arduino.h>
#include <ST7796_t3.h> // Hardware-specific library
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_MCP23X17.h>
#include <avr/interrupt.h>
#include <JPEGDEC.h>

JPEGDEC jpeg;

// For 3.5" or 4.0" TFT with ST7796
ST7796_t3 tft = ST7796_t3(TFT_CS, TFT_DC, TFT_RST);

// FrameBuffer is located in Teensy4.1 Ram2
#define TFT_height 320
#define TFT_width 480
DMAMEM uint16_t FrameBuffer[TFT_height * TFT_width];

// CS SDCARD
#define BUILTIN_SDCARD 254

// this function determines the minimum of two numbers
#define minimum(a, b) (((a) < (b)) ? (a) : (b))

// Timer
elapsedMillis updateScreen_Timer = 0;
elapsedMillis Timer_1 = 0;
elapsedMillis Key_debounce_Timer = 0;

uint32_t LED_States = 0b000000000000000000000000;

Adafruit_MCP23X17 mcp0; // 1st MCP Key 1-16
Adafruit_MCP23X17 mcp1; // 2nd MCP Key 17-24
Adafruit_MCP23X17 mcp3; // 2nd MCP Encoder

boolean enc_action = false;
uint16_t mcp0_Status = 0xFFFF;
uint16_t mcp0_oldStatus = 0xFFFF;
uint16_t mcp1_Status = 0xFFFF;
uint16_t mcp1_oldStatus = 0xFFFF;
uint16_t mcp2_Status = 0xFFFF;
uint16_t mcp2_oldStatus = 0xFFFF;
uint8_t KeyFunction_State[24]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};


File myfile;

void *myOpen(const char *filename, int32_t *size)
{
    myfile = SD.open(filename);
    *size = myfile.size();
    return &myfile;
}
void myClose(void *handle)
{
    if (myfile)
        myfile.close();
}
int32_t myRead(JPEGFILE *handle, uint8_t *buffer, int32_t length)
{
    if (!myfile)
        return 0;
    return myfile.read(buffer, length);
}
int32_t mySeek(JPEGFILE *handle, int32_t position)
{
    if (!myfile)
        return 0;
    return myfile.seek(position);
}

// Jpeg Image draw ---------------------------------------------
void JPEGDraw(JPEGDRAW *pDraw)
{
    // Serial.printf("jpeg draw: x,y=%d,%d, cx,cy = %d,%d\n",
    // pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
    tft.writeRect(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight, pDraw->pPixels);
}

// init LED Status ----------------------------------------------
void init_LEDs()
{

    uint32_t data = 1;
    uint32_t multipl = 1;
    // take the latchPin low so
    // the LEDs don't change while you're sending in bits:

    for (size_t i = 0; i < 24; i++)
    {
        digitalWrite(LED_latchPin, LOW);
        // shift out highbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (data >> 16));
        // shift out middelbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (data >> 8));
        // shift out lowbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, data);
        // take the latch pin high so the LEDs will light up:
        digitalWrite(LED_latchPin, HIGH);
        digitalWrite(LED_oePin, HIGH);
        delay(15);
        multipl *= 2;
        data = data + multipl;
    }
}

// --------------------------------------------------------------
// set LED
// n = LED-No 0 - 23
// state = 1 = on  / 0 = off
// --------------------------------------------------------------
void set_LED(uint8_t n, bool state)
{
    uint32_t maske = 0;

    if (state)
    {
        LED_States |= (1 << n); // set LED
    }
    else
    {
        maske = ~(1 << n);
        LED_States &= maske; // clear LED
    }

    for (size_t i = 0; i < 24; i++)
    {
        digitalWrite(LED_latchPin, LOW);
        // shift out highbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (LED_States >> 16));
        // shift out middelbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (LED_States >> 8));
        // shift out lowbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, LED_States);
        // take the latch pin high so the LEDs will light up:
        digitalWrite(LED_latchPin, HIGH);
        digitalWrite(LED_oePin, HIGH);
    }
}

// init LED Status ----------------------------------------------
void LEDs_off()
{
    uint32_t data = 0xFFFFFF - 1;
    uint32_t multipl = 1;
    // take the latchPin low so
    // the LEDs don't change while you're sending in bits:

    for (size_t i = 0; i < 24; i++)
    {
        digitalWrite(LED_latchPin, LOW);
        // shift out highbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (data >> 16));
        // shift out middelbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (data >> 8));
        // shift out lowbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, data);
        // take the latch pin high so the LEDs will light up:
        digitalWrite(LED_latchPin, HIGH);
        digitalWrite(LED_oePin, HIGH);
        delay(15);
        multipl *= 2;
        data = data - multipl;
    }
}

// Rotary Encoder interrupt ------------------------------------
void Encoders_Interrupt()
{
    enc_action = true;
}

// Key fuction -------------------------------------------------
void Key1_function(bool state)
{
    if (!state)
    {
        set_LED(22, state);
        tft.waitUpdateAsyncComplete();
        tft.fillRect(0, 30, 220, 25, ST7735_BLACK);
        tft.updateScreenAsync();
    }
    else
    {
        set_LED(22, state);
        tft.waitUpdateAsyncComplete();
        tft.fillRect(0, 30, 220, 25, ST7735_GREEN);
        tft.updateScreenAsync();
    }
}

void Key2_function(bool state)
{
    if (!state)
    {
        set_LED(23, state);
        tft.waitUpdateAsyncComplete();
        tft.fillRect(0, 30, 220, 25, ST7735_BLACK);
        tft.updateScreenAsync();
    }
    else
    {
        set_LED(23, state);
        tft.waitUpdateAsyncComplete();
        tft.fillRect(0, 30, 220, 25, ST7735_RED);
        tft.updateScreenAsync();
    }
}

// read Keys ---------------------------------------------------
void read_keys()
{
    if (Key_debounce_Timer >= 50) // Key debounce
    {
        Key_debounce_Timer = 0;

        if (enc_action == true)
        {
            enc_action = false;
            mcp0_Status = mcp0.readGPIOAB();
            if (mcp0_Status == mcp0_oldStatus)
                return;

            for (byte i = 0; i < 16; i++)
            {
                bool n = bitRead(mcp0_Status, i);
                if (n != bitRead(mcp0_oldStatus, i))
                {
                    // i = function-no / n = state (low-active)

                    if (KeyFunction_State[i] == 0 && n == 0)
                    {
                        switch (i)
                        {
                        case 0:
                            Key1_function(0);
                            break;
                        case 1:
                            Key2_function(0);
                            break;
                        case 8:
                            Serial.println("KEY 8");
                            break;
                        }
                        KeyFunction_State[i] = 1;
                    }
                    else if (KeyFunction_State[i] == 1 && n == 0)
                    {
                        switch (i)
                        {
                        case 0:
                            Key1_function(1);
                            break;
                        case 1:
                            Key2_function(1);
                            break;
                        }
                        KeyFunction_State[i] = 0;
                    }
                }
            }
            mcp0_oldStatus = mcp0_Status;
        }
    }
}

void setup(void)
{
    Serial.begin(9600);

    // init io pins
    pinMode(TFT_BL, OUTPUT);       // TFT Backligth
    pinMode(LED_latchPin, OUTPUT); // LED
    pinMode(LED_clockPin, OUTPUT); // LED
    pinMode(LED_dataPin, OUTPUT);  // LED
    pinMode(LED_oePin, OUTPUT);    // LED
    digitalWrite(TFT_BL, LOW);     // TFT_BL off
    digitalWrite(LED_oePin, LOW);  // LED off

    // init TFT Touchscreen and 300KB FrameBuffer into Teensy4.1 RAM2
    tft.setFrameBuffer(FrameBuffer);
    tft.init(320, 480);
    tft.setRotation(1);
    tft.invertDisplay(true);
    tft.useFrameBuffer(true);
    tft.fillScreen(ST7735_BLACK);
    tft.updateScreenAsync();
    init_LEDs();                // all LEDs on
    LEDs_off();                 // all LEDs off
    digitalWrite(TFT_BL, HIGH); // TFT_Backlight on

    // init Key/Encoder IO Expander
    mcp0.begin_I2C(0x21, &Wire1); // Addr 0x20-0x27
    mcp1.begin_I2C(0x22, &Wire1);

    // init Wire1 for Encoders
    Wire1.begin();
    Wire1.setClock(400000UL); // I2C speed 400KHz

    // init MPC23017 Expander for Encoders & Keys
    mcp0.setupInterrupts(true, false, LOW);
    for (size_t i = 0; i < 16; i++)
    {
        mcp0.pinMode(i, INPUT_PULLUP);
        mcp0.setupInterruptPin(i, CHANGE);
    }
    mcp0.getCapturedInterrupt();
    mcp0.readGPIOAB();
    mcp0.clearInterrupts();
    attachInterrupt(digitalPinToInterrupt(PIN_ENC_INT), Encoders_Interrupt, FALLING);

    // init SDcard
    tft.setTextColor(ST7735_WHITE);
    tft.setTextSize(2);
    tft.setCursor(0, 5);
    const int chipSelect = BUILTIN_SDCARD;
    if (!SD.begin(chipSelect))
    {
        tft.print(F("SD-Card not connected!"));
        tft.updateScreenAsync();
        while (1);
    }
    else
    {
        tft.print(F("SD-Card initialized"));
        tft.updateScreenAsync();
    }

    delay(1000);
    tft.fillScreen(ST7735_BLACK);
    tft.setCursor(0, 5);
    tft.print(F("Load boot screen.."));
    tft.updateScreenAsync();
    delay(500);

}

void loop()
{
    int filecount = 0;
    tft.setCursor(0, 0);
    File dir = SD.open("/");
    while (true)
    {
        File entry = dir.openNextFile();
        if (!entry)
            break;
        if (entry.isDirectory() == false)
        {
            const char *name = entry.name();
            const int len = strlen(name);
            if (len > 3 && strcasecmp(name + len - 3, "JPG") == 0)
            {
                Serial.print("File: ");
                Serial.println(name);
                tft.print("File: ");
                tft.println(name);
                if (jpeg.open((const char *)name, myOpen, myClose, myRead, mySeek, JPEGDraw))
                {
                    jpeg.decode(0, 0, 0);
                    jpeg.close();
                    tft.updateScreenAsync();
                }
                else
                {
                    Serial.print("error = ");
                    Serial.println(jpeg.getLastError(), DEC);
                }
                filecount = filecount + 1;
                delay(2000);
                tft.fillScreen(ST7735_BLACK);
            }
        }
        entry.close();
    }
    if (filecount == 0)
    {
        Serial.println("No .JPG files found");
        tft.println("No .JPG files found");
        delay(2000);
    }
}
 
Last edited:
Wondering if you are actually gaining anything with the big_screen_t4 version of the ST7796? Also not really sure how much
you are saving with using updateScreenAsync() verus simple updateScreen? That is in cases like:


That is, you have:
Code:
if (jpeg.open((const char *)name, myOpen, myClose, myRead, mySeek, JPEGDraw))
                {
                    jpeg.decode(0, 0, 0);
                    jpeg.close();
                    tft.updateScreenAsync();
                }
                else
                {
                    Serial.print("error = ");
                    Serial.println(jpeg.getLastError(), DEC);
                }
                filecount = filecount + 1;
                delay(2000);
                tft.fillScreen(ST7735_BLACK);

So you use DMA to write it out, and then sleep for two seconds, which for sure it has completed by then.
Versus simply doing a fast output using the updateScreen(), which avoids all of the overhead of starting up the DMA and the
interrupts processing and the like.
 
This is just one example. While the display is drawing data, I can process buttons, potentiometers, encoders, MIDI commands, and audio data.
Look at my Video with old TouchDisplay.

 
Hello...

I'm having a problem when I'm transferring graphic data to the ST7796S display using framebuffer and DMA, while simultaneously querying the encoder via an MCP23017 with an 400KHz clock I2C bus. The encoder query is briefly interrupted when an image is being drawn. I'm using the library function

tft.asyncUpdateActive(), and when it's false, I query the encoder and redraw the graphic.

C:
/*
Jeannie II
Polyphonic DIY Synthesizer/Sampler
(c) by Rolf Degen and Andre' Laska März 2026
Version 27.03.2026
*/

#define TFT_MISO 12
#define TFT_MOSI 11
#define TFT_SCK 13
#define TFT_DC 8
#define TFT_CS 10
#define TFT_RST 9
#define TFT_BL 28 // TFT Backlight

// LED chip 74HC595
#define LED_latchPin 30
#define LED_clockPin 31
#define LED_dataPin 32
#define LED_oePin 29
#define LED_Patch 4
#define LED_OSC 5
#define LED_Mixer 6
#define LED_Filter 7
#define LED_ENV 8

// U9 GPIO Pin Register
#define Key_Patch 6
#define Key_OSC 8
#define Key_Mixer 9

// U10 MCP23017 Encoder Pin
#define enc1_A 256      // GPB0
#define enc1_B 512      // GPB1
#define enc2_A 1024     // GPB2
#define enc2_B 2048     // GPB3
#define enc3_A 4096     // GPB4
#define enc3_B 8192     // GPB5
#define enc4_A 16384    // GPB6
#define enc4_B 0        // GPA0
#define enc5_A 1        // GPA1
#define enc5_B 2        // GPA2
#define enc6_A 4        // GPA3
#define enc6_B 8        // GPA4
#define enc7_A 16       // GPA5
#define enc7_B 32       // GPA6


#define on 1
#define off 0

// SDcard
#define BUILTIN_SDCARD 254

#include <Arduino.h>
#include <ST7796_t3.h>                // Hardware-specific library
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <avr/interrupt.h>
#include <JPEGDEC.h>
#include <InternalTemperature.h>
#include <GUI.h>
#include <ArduinoJson.h>
#include <FT6336U.h>
#include <st7735_t3_font_OpenSans.h>
#include <Adafruit_MCP23X17.h>



// Jpeg Image encoder
JPEGDEC jpeg;

// For 3.5" TFT with ST7796S
ST7796_t3 tft = ST7796_t3(TFT_CS, TFT_DC, TFT_RST);

// Touch Controller
#define I2C_SDA 18
#define I2C_SCL 19
#define RST_N_PIN 16
#define INT_N_PIN 33
FT6336U ft6336u(I2C_SDA, I2C_SCL, RST_N_PIN, INT_N_PIN);

// FrameBuffer is located in Teensy4.1 Ram2
#define TFT_height 320
#define TFT_width 480
DMAMEM uint16_t FrameBuffer[TFT_height * TFT_width];

// CS SDCARD
#define BUILTIN_SDCARD 254

// Timer
elapsedMillis updateScreen_Timer = 0;
elapsedMillis Timer_1 = 0;
elapsedMillis Key_debounce_Timer = 0;

uint32_t LED_States = 0b000000000000000000000000;

Adafruit_MCP23X17 mcp0; // 1st MCP Key 1-16
Adafruit_MCP23X17 mcp1; // 2nd MCP Key 17-24
Adafruit_MCP23X17 mcp3; // 2nd MCP Encoder

uint8_t PIN_KEY_INT = 25;   // Keys Interrupt
uint8_t PIN_TS_INT = 33;    // Touch controller
uint8_t PIN_ENC_INT = 34;   // Encoder Interrupt

boolean key_action = false;
boolean ts_action = false;
boolean enc_action = false;
uint8_t old_ts_event_1 = 0;
uint16_t mcp0_Status = 0xFFFF;
uint16_t mcp0_oldStatus = 0xFFFF;
uint16_t mcp1_Status = 0xFFFF;
uint16_t mcp1_oldStatus = 0xFFFF;
uint16_t mcp2_Status = 0xFFFF;
uint16_t mcp2_oldStatus = 0xFFFF;
uint8_t KeyFunction_State[24]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

uint8_t loopCount = 0;
uint8_t Menu_page = 1;

// Encoders variable
volatile uint16_t current_GPIO1 = 0;
uint16_t old_GPIO1 = 0;
int16_t Enc_Pos[7]= {0,0,0,0,0,0,0};
uint16_t currentClkState[7] = {0,0,0,0,0,0,0};
uint8_t Enc_B[7] = {0,0,0,0,0,0,0};
uint16_t lastClkState[7] = {0,0,0,0,0,0,0};
unsigned long lastInterruptTime = 0;
int accelerationFactor = 1;

// Special colors
extern uint16_t tab_color_green;
extern uint16_t tab_color_grey;
extern uint16_t tab_color_red;
extern uint16_t light_grey;
extern uint16_t dark_grey;
extern uint16_t text_color_2;

/*
// read file directory -----------------------------------------
FLASHMEM void render_Preset_List(String preset_bank, uint8_t start_entry)
{
    JsonDocument doc;
    File dataFile;

    preset_list_start_pointer = start_entry; // save start_pointer

    start_entry += 1;
    String nameString_1 = "/PATCH/BANK_" + preset_bank + "/PRESET_";
    String nameString_2 = "/Data.json";
    uint8_t number_of_entries = 7;
    uint8_t count = start_entry + number_of_entries;
    uint8_t ID_no = 0;

    for (uint8_t i = start_entry; i < count; i++)
    {
        String countString = String(i);
        String filenameString = nameString_1 + countString + nameString_2;
        // convert String into char-Array
        char filename_[filenameString.length() + 1];
        filenameString.toCharArray(filename_, sizeof(filename_));

        File dataFile = SD.open(filename_, FILE_READ); // open "Data.json" file

        if (!dataFile)
        {
            Serial.println("error opening Data.json");
            return;
        }
        DeserializationError error = deserializeJson(doc, dataFile);
        if (error)
        {
            Serial.print(F("deserializeJson() Error: "));
            Serial.println(error.c_str());
            return;
        }

        const char *patchName = doc["patchName"];
        fileNames[ID_no++] = patchName + '\0';
        dataFile.close(); // close file

        if (ID_no > 128)
        {
            return;
        }
    }
}
*/

// CPU Temperature Monitoring ----------------------------------
float CPUtemperature(void)
{
    float temp = InternalTemperature.readTemperatureC();
    return temp;
}

// Jpeg Image draw ---------------------------------------------
File myfile;

// Function to draw pixels to the display
int JPEGDraw(JPEGDRAW *pDraw)
{
    //  Serial.printf("jpeg draw: x,y=%d,%d, cx,cy = %d,%d\n",
    //     pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
    //     for (int i=0; i<pDraw->iWidth*pDraw->iHeight; i++) {
    //      pDraw->pPixels[i] = __builtin_bswap16(pDraw->pPixels[i]);
    //     }
    tft.writeRect(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight, pDraw->pPixels);
    return 1;
}

void *myOpen(const char *filename, int32_t *size)
{
    myfile = SD.open(filename);
    *size = myfile.size();
    return &myfile;
}
void myClose(void *handle)
{
    if (myfile)
        myfile.close();
}
int32_t myRead(JPEGFILE *handle, uint8_t *buffer, int32_t length)
{
    if (!myfile)
        return 0;
    return myfile.read(buffer, length);
}
int32_t mySeek(JPEGFILE *handle, int32_t position)
{
    if (!myfile)
        return 0;
    return myfile.seek(position);
}

// init LED Status ----------------------------------------------
void init_LEDs()
{

    uint32_t data = 1;
    uint32_t multipl = 1;
    // take the latchPin low so
    // the LEDs don't change while you're sending in bits:

    for (size_t i = 0; i < 24; i++)
    {
        digitalWrite(LED_latchPin, LOW);
        // shift out highbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (data >> 16));
        // shift out middelbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (data >> 8));
        // shift out lowbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, data);
        // take the latch pin high so the LEDs will light up:
        digitalWrite(LED_latchPin, HIGH);
        digitalWrite(LED_oePin, HIGH);
        delay(15);
        multipl *= 2;
        data = data + multipl;
    }
}

// --------------------------------------------------------------
// set LED
// n = LED-No 0 - 23
// state = 1 = on  / 0 = off
// --------------------------------------------------------------
void set_LED(uint8_t n, bool state)
{
    uint32_t maske = 0;

    if (state)
    {
        LED_States |= (1 << n); // set LED
    }
    else
    {
        maske = ~(1 << n);
        LED_States &= maske; // clear LED
    }

    for (size_t i = 0; i < 24; i++)
    {
        digitalWrite(LED_latchPin, LOW);
        // shift out highbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (LED_States >> 16));
        // shift out middelbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (LED_States >> 8));
        // shift out lowbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, LED_States);
        // take the latch pin high so the LEDs will light up:
        digitalWrite(LED_latchPin, HIGH);
        digitalWrite(LED_oePin, HIGH);
    }
}

// clear Menu LEDs ----------------------------------------------
void clr_Menu_LEDs(void)
{
    set_LED(LED_Patch, off);
    set_LED(LED_OSC, off);
    set_LED(LED_Mixer, off);
    set_LED(LED_Filter, off);

}

// init LED Status ----------------------------------------------
void LEDs_off()
{
    uint32_t data = 0xFFFFFF - 1;
    uint32_t multipl = 1;
    // take the latchPin low so
    // the LEDs don't change while you're sending in bits:

    for (size_t i = 0; i < 24; i++)
    {
        digitalWrite(LED_latchPin, LOW);
        // shift out highbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (data >> 16));
        // shift out middelbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, (data >> 8));
        // shift out lowbyte
        shiftOut(LED_dataPin, LED_clockPin, MSBFIRST, data);
        // take the latch pin high so the LEDs will light up:
        digitalWrite(LED_latchPin, HIGH);
        digitalWrite(LED_oePin, HIGH);
        delay(15);
        multipl *= 2;
        data = data - multipl;
    }
}

// Key interrupt ----------------------------------------------
void Key_Interrupt()
{
    key_action = true;
}

// Touchscreen interrupt ---------------------------------------
void Touchscreen_Interrupt()
{
    ts_action = true;
    mcp1.readGPIO();
}

// Encoder interrupt ----------------------------------------------
void Encoder_Interrupt()
{  
    enc_action = true;
    current_GPIO1 = mcp1.readGPIOAB();
}

// Key fuction -------------------------------------------------
void Key1_function(bool state)
{
    if (!state) // state == 0
    {
        draw_page_1();
        // tft.waitUpdateAsyncComplete();
        tft.updateScreenAsyncT4();
    }
    else
    {
        set_LED(4, state); // state = 1
        // tft.waitUpdateAsyncComplete();
        draw_page_1();
        tft.updateScreenAsyncT4();
    }
}

void Key2_function(bool state)
{
    if (!state)
    {
        set_LED(23, state); // state == 0
        // tft.waitUpdateAsyncComplete();
        // tft.updateScreenAsyncT4();
    }
    else
    {
        set_LED(23, state); // state == 1
        // tft.waitUpdateAsyncComplete();
        draw_page_2();
        tft.updateScreenAsyncT4();
    }
}

// read Keys ---------------------------------------------------
void read_keys()
{
    if (Key_debounce_Timer >= 50) // Key debounce
    {
        Key_debounce_Timer = 0;

        if (key_action == true)
        {
            key_action = false;
            mcp0_Status = mcp0.readGPIOAB();
            if (mcp0_Status == mcp0_oldStatus)
                return;

            for (byte i = 0; i < 16; i++)
            {
                bool n = bitRead(mcp0_Status, i);
                if (n != bitRead(mcp0_oldStatus, i))
                {
                    // i = function-no / n = state (low-active)

                    if (KeyFunction_State[i] == 0 && n == 0)
                    {
                        switch (i)
                        {
                        case Key_Patch:
                            Menu_page = 1;
                            draw_page_1(); // Patch Menu
                            clr_Menu_LEDs();
                            set_LED(LED_Patch, on);
                            break;
                        case Key_OSC:
                            Menu_page = 2;
                            draw_page_2(); // Osc Menu
                            clr_Menu_LEDs();
                            set_LED(LED_OSC, on);
                            break;
                        case Key_Mixer:
                            Menu_page = 3;
                            draw_page_3(); // Mixer Menu
                            clr_Menu_LEDs();
                            set_LED(LED_Mixer, on);
                            break;
                        }
                        KeyFunction_State[i] = 0;
                    }
                    /*
                    else if (KeyFunction_State[i] == 1 && n == 0)
                    {
                        switch (i)
                        {
                        case 6:
                            //Key1_function(1);
                            set_LED(LED_Patch, off);
                            break;
                        case 8:
                            //Key2_function(1);
                            set_LED(LED_OSC, off);
                            break;
                        case 9:
                            //Key2_function(1);
                            set_LED(LED_Mixer, off);
                            break;
                        }
                        KeyFunction_State[i] = 0;
                    }
                    */
                }
            }
            mcp0_oldStatus = mcp0_Status;
        }
    }
}

// read Touchscreen --------------------------------------------
void read_ts (void)
{
    if (ts_action)
        {
            ts_action = false;

            uint8_t ts_status = ft6336u.read_td_status();
            uint8_t ts_ID_1 = ft6336u.read_touch1_id();         // 1 = 1st touch, 2 = 2nd touch
            uint8_t new_ts_event_1 = ft6336u.read_touch1_event();   // 0 = No touching, 1 = touch
           
            if (ts_status >= 0 && ts_ID_1 == 0)
            {
                if (old_ts_event_1 == 0 && new_ts_event_1 == 2)
                {
                    uint16_t ts_ypos_1 = 319 - ft6336u.read_touch1_x(); // Display rotated 180 degrees
                    uint16_t ts_xpos_1 = ft6336u.read_touch1_y();       // and x/y exchange

                    Serial.println(new_ts_event_1);
                    Serial.print("ts_xpos : ");
                    Serial.println(ts_xpos_1);
                    Serial.print("ts_ypos : ");
                    Serial.println(ts_ypos_1);

                    // ts_windows
                    if (ts_xpos_1 >= 0 && ts_xpos_1 <= 109 && ts_ypos_1 >= 0 && ts_ypos_1 <= 50)
                    {
                       
                        if (Menu_page != 1)
                        {
                            //Serial.println("Patch");
                            Menu_page = 1;
                            draw_page_1();
                            clr_Menu_LEDs();
                            set_LED(LED_Patch, on);
                        }
                       
                    }
                    else if (ts_xpos_1 >= 110 && ts_xpos_1 <= 227 && ts_ypos_1 >= 0 && ts_ypos_1 <= 50)
                    {
                     
                        if (Menu_page != 2)
                        {
                            // Serial.println("OSC");
                            Menu_page = 2;
                            draw_page_2();
                            clr_Menu_LEDs();
                            set_LED(LED_OSC, on);
                        }
                    }
                    else if (ts_xpos_1 >= 228 && ts_xpos_1 <= 340 && ts_ypos_1 >= 0 && ts_ypos_1 <= 50)
                    {
                     
                        if (Menu_page != 3)
                        {
                            //Serial.println("Mixer");
                            Menu_page = 3;
                            draw_page_3();
                            clr_Menu_LEDs();
                            set_LED(LED_Mixer, on);
                        }
                    }
                    else if (ts_xpos_1 >= 341 && ts_xpos_1 <= 479 && ts_ypos_1 >= 0 && ts_ypos_1 <= 50)
                    {
                       
                        if (Menu_page != 4)
                        {
                            //Serial.println("Filter");
                            Menu_page = 4;
                            draw_ENV();
                            clr_Menu_LEDs();
                            set_LED(LED_ENV, on);
                        }
                    }
                   

                    old_ts_event_1 = new_ts_event_1;
                }
                else if (old_ts_event_1 == 2 && new_ts_event_1 == 1)
                {
                    old_ts_event_1 = 0;
                    Serial.println("no touch");
                }
            }
        }

}

// read Encoder ------------------------------------------------
void read_enc(void)
{
 
    if (enc_action)
    {
        uint8_t encID = 0;
        unsigned long interruptTime = millis();
        unsigned long timeDiff = interruptTime - lastInterruptTime;
        static uint8_t Del_value;
        static uint8_t Akt_value;
        static uint8_t Dec_value;

        enc_action = false;

        //tft.waitUpdateAsyncComplete();

        // Encoder Acceleration
        // If the rotation is fast (< 50 ms), accelerate
        if (timeDiff < 100)
        {
            accelerationFactor = 5;
        }
        else
        {
            accelerationFactor = 1;
        }

        // Check MCP23017 GPIO for activity
        uint16_t changesBits = old_GPIO1 ^ current_GPIO1;

        // Encoder1 Clk Pin status
        if (changesBits == enc1_A)
        {
            currentClkState[0] = bitRead(current_GPIO1, 8);
            encID = 0;
        } // Encode1 DT Pin status
        else if (changesBits == enc1_B)
        {
            Enc_B[0] = bitRead(current_GPIO1, 9);
            encID = 0;
        }
        // Encoder1 Clk Pin status
        else if (changesBits == enc2_A)
        {
            currentClkState[1] = bitRead(current_GPIO1, 10);
            encID = 1;
        } // Encode2 DT Pin status
        else if (changesBits == enc2_B)
        {
            Enc_B[1] = bitRead(current_GPIO1, 11);
            encID = 1;
        }
        // Encoder3 Clk Pin status
        else if (changesBits == enc3_A)
        {
            currentClkState[2] = bitRead(current_GPIO1, 12);
            encID = 2;
        } // Encode3 DT Pin status
        else if (changesBits == enc3_B)
        {
            Enc_B[2] = bitRead(current_GPIO1, 13);
            encID = 2;
        }

        // print encoder values
        if (currentClkState[encID] != lastClkState[encID])
        {
            if (Enc_B[encID] != currentClkState[encID])
            {
                Enc_Pos[encID] += accelerationFactor;
                if (Enc_Pos[encID] >= 127)
                {
                    Enc_Pos[encID] = 127;
                }
               
            }
            else
            {
                Enc_Pos[encID] -= accelerationFactor;
                if (Enc_Pos[encID] <= 0)
                {
                    Enc_Pos[encID] = 0;
                }
            }

            //tft.setTextColor(ST7735_WHITE);
            //tft.setFont(OpenSans_16);

            if (encID == 0)
            {
                //tft.fillRect(90, 100, 60, 20, ST7735_RED);
                //tft.setCursor(100, 101);
                Del_value = Enc_Pos[encID];
            }
            else if (encID == 1)
            {
                //tft.fillRect(90, 130, 60, 20, ST7735_BLUE);
                //tft.setCursor(100, 131);
                Akt_value = Enc_Pos[encID];
            }
            else if (encID == 2)
            {
                //tft.fillRect(90, 160, 60, 20, tab_color_green);
                //tft.setCursor(100, 161);
                Dec_value = Enc_Pos[encID];
            }
             //tft.waitUpdateAsyncComplete();
            //tft.print(Enc_Pos[encID]);
            tft.fillRect(10,75,456,136, ST7735_BLACK);
            draw_env_grid();
            draw_envelope(Del_value, Akt_value, Dec_value, 64, 64, ST7735_GREEN);
            tft.updateScreenAsyncT4();
           
        }
        lastClkState[encID] = currentClkState[encID];
        old_GPIO1 = current_GPIO1;
        lastInterruptTime = interruptTime;
    }
}

// Setup -------------------------------------------------------
void setup(void)
{
    Serial.begin(9600);

    // init Touchscreen
    ft6336u.begin();
    ft6336u.write_THRESHOLD(0xBB);

    // init CPU Temperature measurement
    InternalTemperature.begin();

    // init io pins
    pinMode(TFT_BL, OUTPUT);       // TFT Backligth
    pinMode(LED_latchPin, OUTPUT); // LED
    pinMode(LED_clockPin, OUTPUT); // LED
    pinMode(LED_dataPin, OUTPUT);  // LED
    pinMode(LED_oePin, OUTPUT);    // LED
    pinMode(PIN_TS_INT, INPUT_PULLUP); // Interrupt Pin Touchscreen
    digitalWrite(TFT_BL, LOW);     // TFT_BL off
    digitalWrite(LED_oePin, LOW);  // LED off

    // init TFT Touchscreen and 300KB FrameBuffer into Teensy4.1 RAM2
    tft.setFrameBuffer(FrameBuffer);
    tft.init(320, 480);
    tft.setRotation(1);
    tft.invertDisplay(true);
    tft.useFrameBuffer(true);
    tft.fillScreen(ST7735_BLACK);
    tft.updateScreenAsyncT4();
    init_LEDs();                // all LEDs on
    LEDs_off();                 // all LEDs off
    digitalWrite(TFT_BL, HIGH); // TFT_Backlight on

    // init Key/Encoder IO Expander
    mcp0.begin_I2C(0x21, &Wire1); // Addr 0x20-0x27
    mcp1.begin_I2C(0x22, &Wire1);

    // init Wire1 for Encoders
    Wire1.begin();
    Wire1.setClock(100000UL); // I2C speed 400KHz

    // init MPC23017(1-3) Expander for Encoders & Keys
    mcp0.setupInterrupts(true, false, LOW);
    for (size_t i = 0; i < 16; i++)
    {
        mcp0.pinMode(i, INPUT_PULLUP);
        mcp0.setupInterruptPin(i, CHANGE);
    }
    mcp0.getCapturedInterrupt();
    mcp0.readGPIOAB();
    mcp0.clearInterrupts();

    mcp1.setupInterrupts(true, false, LOW);
    for (size_t i = 0; i < 16; i++)
    {
        mcp1.pinMode(i, INPUT_PULLUP);
        mcp1.setupInterruptPin(i, CHANGE);
       
    }
   
    mcp1.getCapturedInterrupt();
    mcp1.readGPIOAB();
    mcp1.clearInterrupts();
    delay(10);


    // init Key Interrupt
    attachInterrupt(digitalPinToInterrupt(PIN_KEY_INT), Key_Interrupt, FALLING);

    // init Touchscreen Interrupt
    attachInterrupt(digitalPinToInterrupt(PIN_TS_INT), Touchscreen_Interrupt, FALLING);

    // init Encoder Interrupt
    attachInterrupt(digitalPinToInterrupt(PIN_ENC_INT), Encoder_Interrupt, FALLING);

    // init SDcard and load Boot screen
    tft.setCursor(5, 5);
    tft.setTextSize(2);
    while (!SD.begin(BUILTIN_SDCARD))
    {
        tft.setCursor(5, 5);
        tft.fillScreen(ST7735_RED);
        Serial.println(F("Unable to access SD Card"));
        tft.println(F("Unable to access SD Card"));
        tft.updateScreenAsync();
        delay(1000);
    }

    File dir = SD.open("/");
    int rc;
    rc = (jpeg.open("System/Boot.jpg", myOpen, myClose, myRead, mySeek, JPEGDraw));
    if (rc >= 1)
    {
        Serial.printf("image specs: (%d x %d), %d bpp, pixel type: %d\n", jpeg.getWidth(), jpeg.getHeight(), jpeg.getBpp(), jpeg.getPixelType());
        jpeg.decode(0, 0, 0);
        jpeg.close();
        tft.setTextColor(ST7735_WHITE);
        tft.setTextSize(1);
        tft.setCursor(390,290);
        tft.setFont(OpenSans_10);
        tft.print(F("Version 1.0"));
        tft.updateScreenAsyncT4();
        tft.waitUpdateAsyncComplete();
    }
    else
    {
        tft.setCursor(5, 5);
        tft.setTextSize(2);
        tft.fillScreen(ST7735_RED);
        tft.print(F("Jpeg Image error = "));
        tft.println(jpeg.getLastError(), DEC);
    }

    delay(3000);
    draw_page_1();
    set_LED(LED_Patch, on);

}

void loop()
{

    if (tft.asyncUpdateActive() == false)
   {
        read_keys();
        read_ts();
        read_enc();
   }

}

ST7796S with tft.asyncUpdateActive() and disturbed encoder query

ST7796S without tft.asyncUpdateActive() and good encoder query but disturbed Display draw

Jeannie2_KEY_Panel.jpg
 
Hello

I used the Library https://github.com/h4yn0nnym0u5e/ST7735_t3.git

I suspect a bug in the framebuffer library with ST7796S and DMA. When I draw a small filled rectangle of 100x100 pixels with
updateScreenAsyncT4(true) and DMA, it takes 45ms. When I draw the same rectangle without a framebuffer, the Teensy 4.1 only needs 2.7ms.
A fillscreen(color) takes 45msec with and without DMA :unsure:
 
Back
Top