ST7796 Teensyduino support

Sorry, as I mentioned I am not an Audio person... But keep wondering if the Audio System is the issue, maybe the fix
can be or should be done in the Audio system?

That is if the issue, is the Audio runs out of buffered data while redrawing, can you not increase the size of the buffer.
And if there is not already a way to give a hint to the Audio system, that SPI might be consumed for a period of time,
maybe there should be, and maybe at that time, it could preload more data...

Just a thought...
 
The Audio library, and specifically AudioPlaySdWav, is certainly “the” issue in the testing here. It doesn’t do enough buffering, it says it’s using SPI even if it isn’t, and it accesses the filesystem inside the audio interrupt. As previously noted, I have a PR in for a complete replacement for it. Don’t hold your breath, though…

However, it remains the case that having display functionality hogging the SPI bus for many milliseconds at a time for a single operation is far from ideal. It’s not just audio it impacts, but anything else that’s trying to share the same bus, e.g. snazzy ADC chips (think I’ve seen that as a recent application…). And it’s not reasonable to ask the user to split their display accesses - just drawing a single big character is enough to cause problems!

So I’m having a bit of fun trying to make the ST7796 behave in a more neighbourly fashion, mainly because it’s what I happen to have wired up right now. The overall concept seems to be working fine, it’s controllable within (what I think are) sensible parameters, i.e. execution time between breaks in the transaction, and putting it in where needed is more tedious than challenging.

To come back to your original point, the audio is just a very convenient way of checking my changes are effective; it’s really obvious when either the audio or display are corrupted. It would work just as well to have some other time-critical device on the SPI bus, I just don’t happen to have one.
 
SetBitRate doesn't help but I did edit library code for : ST7735_SPICLOCK 24000000

Then the note about RECT for clearing so edited to redraw prior value in BLACK.

It is NOW showing MIN=6ms not 7 shown below

AUDIO plays fine without issues.

Indeed, SPI speed is limiting factor. Bumping FCPU was just to see it wasn't helped much. Doing it again with current sketch give no improvement
min=6 and max=37

1747598837447.png
 
#define ST7735_SPICLOCK 40000000
min=4 and max = 22 : Audio playback is good
> Clearing with fillRect at 40MHz gives good audio and: min=8 and max=25
1747600128158.png


github updated with display of TEMP {dynamic} and F_CPU {static}
 
Last edited:
am not an Audio person..
same here.

Odd thing is that swapping display for tutorial 3_03 led to broken audio not present with the older smaller ILI display even at the same 'drawn' resolution.
SPI used for display is not used for the SDIO file reading - though it is reported that the Audio library 'pings' SPI even though unused?
Somehow this doesn't show using the ILI but the library edited for the ST display gives horrid playback.
The common edit to add periodic End/Begin on spiTransaction during fillRect() operation helped 'large bars' - but then text/Font using small Rects lost pixels when that was triggered.

A quick edit (bbig) to skip the End/Begin on small Rects showed both (Bars & Text) to work for display while not breaking Audio playback.
Alternate 'time in Rect' edit works as effectively and just as efficiently in the tested sketch.

So either of those would be better than the existing PR alone - not sure which is best as a real fix?
 
I think the reason the code worked with the ILI9341 is that the only problematic part is the fillRect(), but that already had a transaction break built into it, whereas pretty much nothing else time-consuming does.

I'm working through the ST7796 code using time-based breaks. Everything is now in, and I'm trying to get a decent "torture test" to show (a) I haven't broken anything (like the text @defragster mentioned), and (b) audio playback is OK - emphasis that this is an easy metric, not that I think the audio doesn't need fixing! I did find another thread specifically referencing the audio issue, and mentioning the use of threads, which would be another use case where long SPI blocking is an issue. To that end, I've added an optional call to yield() in the transaction break - by default it's disabled, to preserve original functionality, but if enabled would potentially allow a task switch? I might explore that...

One thing I haven't got working is asynchronous updates from the frame buffer. I expect I've done something dumb, and haven't done a proper search for an example, so no immediate need for advice ... just thought I'd mention it :)
 
>> forgot to hit POST earlier ...
Updated prior PR to include the bbig test from prior post since it was broken for text as submitted:: https://github.com/PaulStoffregen/ST7735_t3/pull/37
Did a second PR to add commented 40 MHz : https://github.com/PaulStoffregen/ST7735_t3/pull/38

In testing here both 'fixes' have the same NET effect as the @h4yn0nnym0u5e edit with less code change, since the code is foreign to me.
@h4yn0nnym0u5e - if your more systemic edits seem complete then perhaps offer as an alternate PR? Same result in what was posted so far.

Tested against this Audio Playing sketch for #include <ST7796_t3.h> display usage:
 
Hi @defragster

If you're so inclined, you might want to try my "work in progress" torture test.
C++:
// Advanced Microcontroller-based Audio Workshop
//
// http://www.pjrc.com/store/audio_tutorial_kit.html
// https://hackaday.io/project/8292-microcontroller-audio-workshop-had-supercon-2015
//
// Part 3-3: Add a TFT Display

// Pick your TFT here:
//#include <ILI9341_t3.h>
#include <ST7796_t3.h>

/*
 * Pick an update mode:
 * 0 = immediate, 1 = frame buffer
 * 2 = async frame buffer
 */
#define UPDATE_MODE 0

#if defined ST77XX_BLACK // see which TFT we're using
#include <st7735_t3_font_Arial.h>
#else
#include <font_Arial.h> // from ILI9341_t3
#define ST7735_BLACK ILI9341_BLACK
#define ST7735_RED ILI9341_RED
#define ST7735_YELLOW ILI9341_YELLOW
#define ST7735_GREEN ILI9341_GREEN
//#define ST7735_BLACK ILI9341_BLACK
#endif // defined ST77XX_BLACK

//#include <Adafruit_FT6206.h>

#include <Audio.h>

#define COUNT_OF(a) ((int)(sizeof a / sizeof a[0]))

///////////////////////////////////
// copy the Design Tool code here
///////////////////////////////////
// GUItool: begin automatically generated code
AudioPlaySdWav           playSdWav1;     //xy=136,65
AudioAnalyzePeak         peak2;          //xy=348,219
AudioAnalyzePeak         peak1;          //xy=358,171
AudioOutputI2S           i2s1;           //xy=380,92
AudioConnection          patchCord1(playSdWav1, 0, i2s1, 0);
AudioConnection          patchCord2(playSdWav1, 0, peak1, 0);
AudioConnection          patchCord3(playSdWav1, 1, i2s1, 1);
AudioConnection          patchCord4(playSdWav1, 1, peak2, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=155,192
// GUItool: end automatically generated code


// Use these with the Teensy 4.x and Audio Shield Rev D or D2
// these are h4yn0nnymou5e pin assignments - you may need to change them
#define TFT_DC       9
#define TFT_CS      22
//#define TFT_CS      10
#define TFT_RST    255  // 255 = unused, connect to 3.3V

#define LED_PWM  4 // used to set brightness of LED backlight


#if defined ST77XX_BLACK
ST7796_t3 tft = ST7796_t3(TFT_CS, TFT_DC);
#else
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);//, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
#endif // defined ST77XX_BLACK

// Use this with the Teensy 3.5 & 3.6 & 4.1 SD card
#define SDCARD_CS_PIN    BUILTIN_SDCARD

#define SQ 80
DMAMEM uint16_t save1[SQ*SQ], save2[SQ*SQ];

void makeSave(uint16_t* sv, int n)
{
  uint16_t bg = ST77XX_BLACK;
  const char* msg;
 
  switch (n)
  {
    default:
      tft.setTextColor(ST77XX_RED);
      msg = "on";
      break;

    case 1:
      tft.setTextColor(ST77XX_GREEN);
      bg = ST77XX_BLUE;
      msg = "off";
      break;         
  }

  tft.fillRect(0,0,SQ,SQ,bg);
  tft.setFont(Arial_24);
  tft.setCursor(25,25);
  tft.print(msg);
  tft.readRect(0,0,SQ,SQ,sv);
  //delay(500);
}


void setup() {
  pinMode(LED_PWM, OUTPUT);
  //analogWrite(LED_PWM, 64);
  digitalWrite(LED_PWM,1);
 
  Serial.begin(9600);
  delay(100);
  //tft.setClock(16000000);
    // Setup the LCD screen
#if defined ST77XX_BLACK
  tft.init(320, 480);
  tft.setRotation(1);       // Rotates screen to match the baseboard orientation
#else 
  tft.begin();
  tft.setRotation(1);       // Rotates screen to match the baseboard orientation
#endif // defined ST77XX_BLACK

  //tft.invertDisplay(true);  // LCD requires colors to be inverted

  tft.fillScreen(ST7735_BLACK);
  tft.setTextColor(ST7735_YELLOW);
  tft.setFont(Arial_24);
  //tft.setTextSize(3);
  tft.setCursor(40, 8);
  //tft.println("Peak Meter");

  tft.useFrameBuffer(true);
  makeSave(&save1[0],0);
  makeSave(&save2[0],1);
  tft.useFrameBuffer(false);
  tft.freeFrameBuffer();

 
  AudioMemory(10);
//sgtl5000_1.setAddress(HIGH);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.3);
  while (!(SD.begin(SDCARD_CS_PIN)))
  {
      Serial.println("Unable to access the SD card");
      delay(500);
  }
  //tft.setMaxTransaction(100000);  // deliberately break SD playback!
  tft.setMaxTransaction(2000);      // default is 1000, but this should be OK
  tft.enableYieldInMidTransaction(true); // does a bonus yield() if a mid-transaction break occurs

  switch (UPDATE_MODE)
  {
    default:
      tft.useFrameBuffer(true);
      break;

    case 0:
      break;     
  }
 
  delay(100);
}

elapsedMillis msecs;

/*
 * We need to check a number of functions which can result
 * in long-running transactions
 */
elapsedMicros checkMicros;
uint16_t colours[] = {ST7735_RED, CL(255,128,0), ST7735_YELLOW, ST7735_GREEN, ST7735_BLUE, ST7735_MAGENTA, ST7735_WHITE};

uint16_t nextColour(void)
{
  static int idx;
  uint16_t result = colours[idx];

  if (++idx >= COUNT_OF(colours))
    idx = 0;

  return result;   
}

//========================================================================
uint32_t check_(void)
{
  checkMicros = 0;

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_fillRect(void)
{
  checkMicros = 0;

  tft.fillRect(0,0,SQ,SQ,nextColour());

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_drawChar(void)
{
  tft.fillRect(SQ+96,0,96,SQ,nextColour());
  checkMicros = 0;

  tft.setFont();
  tft.setTextSize(8);
  tft.setTextColor(nextColour());
  tft.setCursor(SQ+96,0);
  tft.print("qb");

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_drawChar_bg(void)
{
  checkMicros = 0;

  tft.setFont();
  tft.setTextSize(8);
  tft.setTextColor(nextColour(),ST77XX_BLACK);
  tft.setCursor(SQ,0);
  tft.print("t8");

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_drawFontChar(void)
{
  tft.fillRect(SQ+96,SQ,96,90,nextColour());
  checkMicros = 0;

  tft.setFont(Arial_60);
  tft.setTextColor(nextColour());
  tft.setCursor(SQ+96,SQ);
  tft.print("qb");

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_drawFontChar_bg(void)
{
  checkMicros = 0;

  tft.setFont(Arial_60);
  tft.setTextColor(nextColour(),0x38E7);
  tft.setCursor(SQ,SQ);
  tft.print(" t8 ");

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_writeRect(void)
{
  checkMicros = 0;
  static int which;

  uint16_t* wr = (which&16)?save1:save2;
  tft.writeRect(0,SQ,SQ,SQ,wr);
  which++;// = !which;

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_writeSubImageRect(void)
{
  tft.fillRect(0,SQ*2,SQ,SQ,nextColour()); // don't count this
 
  checkMicros = 0;
  static int which;
  int off = 8;

  uint16_t* wr = (which&32)?save1:save2;
  tft.writeSubImageRect(off,SQ*2+off,SQ-off*2,SQ-off*2,
                        off, off, SQ, SQ,
                        wr);
  which++;// = !which;

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_writeSubImageRectBytesReversed(void)
{
  tft.fillRect(0,SQ*3,SQ,SQ,nextColour()); // don't count this
 
  checkMicros = 0;
  static int which;
  int off = 8;

  uint16_t* wr = (which&8)?save1:save2;
  tft.writeSubImageRect(off,SQ*3+off,SQ-off*2,SQ-off*2,
                        off, off, SQ, SQ,
                        wr);
  which++;// = !which;

  return checkMicros;
}

//------------------------------------------------------------------------
uint32_t check_updateScreen(void)
{ 
  checkMicros = 0;
  tft.updateScreen();
  return checkMicros;
}

//========================================================================
#define RUN_CHECK(n) Serial.printf("Check " #n ": %d\n", check_##n())

uint32_t lastCheck;
elapsedMicros asyncTime;
bool asyncStarted;

void loop()
{
  if (playSdWav1.isPlaying() == false)
  {
    Serial.println("Start playing");
    //playSdWav1.play("SDTEST1.WAV");
    //playSdWav1.play("SDTEST2.WAV");
    //playSdWav1.play("SDTEST3.WAV");
    playSdWav1.play("SDTEST4.WAV");
    delay(10); // wait for library to parse WAV info
  }

  if (millis() - lastCheck >= 100)
  {
    lastCheck = millis();

    Serial.println("========================");
    RUN_CHECK(fillRect);
    RUN_CHECK(drawChar_bg);
    RUN_CHECK(drawChar);
    RUN_CHECK(drawFontChar_bg);
    RUN_CHECK(drawFontChar);
    RUN_CHECK(writeRect);
    RUN_CHECK(writeSubImageRect);
    RUN_CHECK(writeSubImageRectBytesReversed);
    
    switch (UPDATE_MODE)
    {
      default:
        break;

      case 1:
        RUN_CHECK(updateScreen);
        break;
        
      case 2:
        Serial.printf("Async start was %sOK\n",tft.updateScreenAsync()?"":"not ");
        asyncTime = 0;
        asyncStarted=true;
        break;
    }
  }

  if (asyncStarted && !tft.asyncUpdateActive())
  {
    asyncStarted = false;
    Serial.printf("Async update took %dus\n",(uint32_t) asyncTime);
  }
}
You can see which methods I've set up tests for - more to do here, particularly for palette-mapped images and other font types. It'll need revision to port back to the ILI9341, as it uses quite a lot of the ST7796 area. Not really worth you expending a lot of effort on, unless you really want to!

I may well cave in and do a PR alongside yours, if I get to a point where I think it has merit and I've tested it "enough". Will of course cross-refer so if it catches Paul's eye he doesn't waste time wrestling with incompatible solutions.
 
decent "torture test"
will watch for this. The coding done so far in a couple of sketches has not shown any issues with Audio and either of the lib edits we've done.
>> INCOMING CROSS POST :)

On thing might be making mods for the standard examples - looking like 5 have 'test' in their name.
 
?? New code posted?

sketch_may19a:146: error: 'class ST7796_t3' has no member named 'enableYieldInMidTransaction'
146 | tft.enableYieldInMidTransaction(true); // does a bonus yield() if a mid-transaction break occurs
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Did a comment of two added func()'s and it looks to run the same on the PR#37 version.

Not that your version shouldn't evolve and get approved - just hard to tell what the expected behavior is - and if it fixes a current issue.

Can the examples have an #ifdef way to pull any new func()'s for side by side testing?

Just commented two lines and it runs on the PR#37 version with good sound
 
Is this needed? : digitalWrite(LED_PWM,1);
It hits a bright RED LED on this MINI board. Not sure where backlight is on this board just now.
 
Agree ... I need to provide a justification sketch for showing the issues existing before and fixed after the code changes. The torture test may form the basis, but needs to be cut down to the bare essentials for the initial sketch, although something close the the whole thing is needed for proof of proper workings.

Is this needed? : digitalWrite(LED_PWM,1);
It hits a bright RED LED on this MINI board. Not sure where backlight is on this board just now.
No, not if your backlight is permanently on. If it's configured to a pin, then set LED_PWM to reference it. My screen has a transistor driver, so the PWM actually does work to vary the backlight brightness.
 
Example working here ... but my day at 4:14 AM is done.

Okay, I'll just kill that PWM ON as the RED LED is annoying here. Was hoping it would work there is removed.

Also, your rotate is 180° off :)

T:\T_Drive\tCode\libraries\ST7735_t3-dev-break-transactions\src\ST7735_t3.cpp: In member function 'void ST7735_t3::drawGFXFontChar(unsigned int)':
T:\T_Drive\tCode\libraries\ST7735_t3-dev-break-transactions\src\ST7735_t3.cpp:3957:97: warning: 'x' may be used uninitialized [-Wmaybe-uninitialized]
3957 | midTransaction(x0, (y >= _displayclipy1) ? y : _displayclipy1, x+w-1, y+h-1);
| ~^~
 
If you're so inclined, you might want to try my "work in progress" torture test.
I built this and also verified that it works OK as well. Out of curiosity, I increased the SPICLOCK from 16MHz to 40MHz and still works OK.

The speed improvement tracks almost exactly with the increase in SPI speed. SPI is definitely the limitation, as expected from Defragsters earlier comments. If you are running on a hand-wired LCD setup, it may not like the 40MHz clock.
1747686683290.png
 
Great, thanks for checking that out @KenHahn. Despite having some highly dodgy crimped connectors, and about 100mm wires, I have had 40MHz working. I then reverted to 16MHz - after all, I'm actually interested in holding up the SPI bus for long enough to test my midTransaction() function.

I'm fairly sure the asynchronous code is borked in some fashion - I flipped to @KurtE's ILI9341 code and that was OK with the same function calls. Except ... of course, DMA holds the SPI bus for a whole screen fill, so it completely stuffs the audio! I think it could be made to work by careful choice of interrupt levels, and only DMAing a small area at a time, but I'm not going down that rabbit hole. I'm not.
 
built this
@KenHahn Where's the source for that?

I see these working but not giving that result display:
and
 
It is the serial output from h4yn0nnym0u5e torture test he posted in #58.
Ah - Yes - it was scrolling so fast I never really studied it:

========================
Check fillRect: 6501
Check drawChar_bg: 6470
Check drawChar: 2180
Check drawFontChar_bg: 11027
Check drawFontChar: 3633
Check writeRect: 6535
Check writeSubImageRect: 4212
Check writeSubImageRectBytesReversed: 4182

posted as used here on github with above two examples:
 
======================== And at 40 MHz:
Check fillRect: 2625
Check drawChar_bg: 2596
Check drawChar: 916
Check drawFontChar_bg: 4459
Check drawFontChar: 1714
Check writeRect: 2633
Check writeSubImageRect: 1700
Check writeSubImageRectBytesReversed: 1699

And a bit faster - asking for 80 MHz

======================== at: :: #define ST7735_SPICLOCK 80'000'000
Check fillRect: 1413
Check drawChar_bg: 1481
Check drawChar: 524
Check drawFontChar_bg: 2410
Check drawFontChar: 1108
Check writeRect: 1448
Check writeSubImageRect: 942
Check writeSubImageRectBytesReversed: 910

Other sketches at "80 MHz":
Speedtest.ino displays 281 FPS
GraphicsTest works
MINI DEMO works with Audio
And GOOD AUDIO: https://github.com/Defragster/Audio...uppSD/P3_03_TFT_DispBig/P3_03_TFT_DispBig.ino
1747697529885.png
 
Paging @KurtE :D ...

I had an inspiration, and lied about the screen resolution, which made the asynchronous update spring into life. The truth is 320x480, but if I say 320x240 it works OK. I'd have a crack at fixing it myself, but the DMA code makes my head spin :eek:
 
Maybe @KurtE and/or @mjs513 will see this and offer insight.

This 4" display with more area and resolution is quite nice and has been running the example for 10 hours it seems since posted above at "80 MHz" - not sure what it really resolves to something faster than 40 for sure based on the numbers if not all of 80! And it has a very readable off center wide viewing angle as was noted before as well.
 
Back
Top