ST7796 Teensyduino support

Making the change pointed out there was a begin(_clock) argument in the reference code that did not have a variable for in the ST7796 lib - so it was left out, like in the begin() before the loop. Extending the transaction code would add more complexity.
I'm not sure I fully understand what you're saying here ...

There will be plenty of libraries which don't make use of all the SPI features that maybe they should; I don't see that as a reason for not adding features to the SPI library that could then be used in them. It would be a separate exercise to trawl through e.g. all the display libraries to see where there are opportunities to use it. I'm sure you're right that fillRect() is used internally, which is all the more reason to try to get it as good as possible.

I wouldn't propose adding more complexity to the existing begin/endTransaction code - midTransaction would simply conditionally call them, and we'd rely on the optimiser to make it work according to the selected optimisation level.
Not having the spurious SPI coded in audio would be best when needed less often given SDIO SD and other FS options - but that is a choice for PJRC to offer a better way without resulting in code bloat or breakage.
I agree, but as noted by @KenHahn in another thread, in response to one of my posts, it's not completely trivial to do that, and could require fairly wide-ranging changes to other libraries in order to be done "properly". The underlying issue is that there's no way of finding out anything about the hardware used by any given filesystem; the whole FS API would need changing to enable that... It's slightly simplified by the fact that AudioPlaySdWav can't play from anything but the SD filesystem, but you're still up against the fact that you can't tell if that's using SPI or SDIO from within AudioPlaySdWav.

I would love to think there is indeed "a choice for PJRC to offer...", but that really needs Paul to engage and encourage at the very least. I don't expect him to do the work (he's got a lot on), but guidance during discussions and implementation, and some indication that any resulting PR will be included in a reasonably timely manner is needed.
 
Just out of interest, I've tried adding drawing a number using a large font size (default font, size 9) to the SD playback demo, and sure enough, that can make audio output glitchy, too. I calculate that at 3,888 pixels for drawing one character, compared to the worst-case level bar of 9,600 pixels, so it makes sense.

I then started trying to break that up into smaller SPI chunks by putting end+begin transaction calls in, but it's clearly not as simple as the fillRect code - I completely trashed the screen output!

I don't really understand the display driver code, but it kind of looks as if the output rectangle size is sent to the display, then filled by writing sequential pixels to it. It appears that you need to redo this rectangle definition if you start a new SPI transaction, and I clearly haven't quite managed to achieve that. Someone Skilled in the Art may well be able to do this in a trice!
 
Sorry, a lot of this is out of my Usage patterns/Interest. That is I almost never use Audio. In the cases where my project that I was playing
with needed some sound, it usually more like an indicator that a button was pressed or battery running low... So probably not much help.
My normal suggestion in cases like this, is to try to avoid it. That is if you are using an SD on SPI, have it on a different SPI bus. But...

Currently I am busy tearing my few strands of hair out, playing with Zephyr. Hoping that any changes made to libraries does not
impact the speed of them in the other cases.

I am assuming that the Audio uses the Interrupt mask stuff... That is buried within beginTransaction and endTransaction.
Wish there were better mechanisms to know if there are others waiting for SPI. Like waiting on an Event or the like... But...

Some issues may come up with simply adding beginTransactions/endTransactions without other changes as well.
For example if the code is currently outputting all of the pixels at once it is probably filling up the hardware FIFO buffers.
You need to first make sure that the buffers have fully completed their output, before you can change the state of the CS pin,
or things will get clobbered. And probably have to make sure you drop the CS line before you call endTransaction, as calling
endTransaction, may instantly go into another SPI transaction and the display would continue to see those transfers as
belonging to it.

Also I have seen with some displays, if your have code that does something like:
<caset> x0 x1 <paset> y0 y1 <write memory>

The writes to memory stop as soon as the CS line is un asserted. So in those case you will need to potentially
repeat some or all of the full sequence.

As for knowing if the SD is SPI or SDIO - I think at one point I had a hack where you could go to
the SD object. I don't remember all of the details, but it was something like:

SD.sdfs there is a method like card() and/or vol...
I think there then was some method on the card object where you might be able to ask it for the pin used for CS
and if was not a valid pin number you know that it is SDIO... Again I am fuzzy on what I tried back then.

And I know the subject has come up before about wanting the ability to ask a FS more details about it. Things like:
what type is it. sizes: not sure if sector size and cluster size, or preferred read or write sizes...

Sorry I know I am sort of scattered here, busy with some other non-computer related.

Good luck!
 
Understood, @KurtE - we can't all be interested in everything! It is a very common use-case for synth / audio projects to add a display, at which point people get ambitious, and then it all stops working. Unless of course they don't use the SPI-related audio stuff, which is most of it apart from SD playback and possibly the external delay.

Yes, the ST77xx driver does use the <caset> x0 x1 <paset> y0 y1 <write memory> pattern, which is what I broke. I now have it working; as you say, it needs a bit more wrapping to ensure the FIFO is clear before end, then to begin and restore the (updated) target rectangle before resuming character drawing.

Your hack might work very nicely for the existing AudioPlaySdWav, as it only supports SD anyway (the clue is in the name...). But then clever so-and-sos come along and ask "why can't it be made filesystem-agnostic?" so they can play from e.g. LittleFS in any of its guises, and the whole can ends up with an extra helping of worms! I had a Modest Proposal to dump that responsibility on the user, so they could tell the object to claim SPI or not, based on their knowledge of which filesystem was in use. That would at least sort the SPI/SDIO issue, and if they absolutely insist on using SPI for both display and filesystem, then they'll have to be careful, or use a different SPI bus for each, or use a modified library.

So far that proposal has, to mix a metaphor, fallen on stony ground, but who knows what the future might hold?
 
One thing to note if anyone's trying to reproduce any of this audio+display stuff ... the demo linked a few posts back uses SDTEST3.WAV, but this is heavily overloaded / clipping and sounds pretty poor even when everything is working fine. SDTEST4.WAV is better...
 
Bad Fix :: Someone noted that killing chip select CS terminates the transfer. Just looked at screen last night:
you can tell which is which ...
1747071576829.png
 
Yes, you can’t just end+begin a transaction in the middle of drawing a character, you need to finish cleanly, then reset to the new smaller target rectangle when you resume.

I proved to my own satisfaction that it could be made to work, in theory, but not rigorously enough to make a PR.
 
p#31 added notes:
Only diff is removing the End/Begin edit of the PR.
This is the DEMO sketch that comes on the Mini device. And there is NO AUDIO playing during this text output. It is not just on this screen.
For testing only the tutorial BarGraph sketch was observed to work with the added End/Begin edit.
 
Note sure what's going on in posts #31 and #33 - not enough information. I have no idea what is "the DEMO sketch that comes on the Mini device" :unsure:. Maybe post [a link to] the code?
 
Drefragster has to limit his typing, so I will try to answer your question.

The Mini Platform that Defragster is referring to is this one: https://protosupplies.com/product/mini-platform-teensy41/
The demo code is simply the example software towards the bottom of that page that shows basic usage of the hardware pieces.

I had sent Defragster a setup that including an Audio Tutorial Adapter as shown here so that he could go through the original PJRC audio tutorial as a test case as he has been helping to evaluate the new product. It was when he got to part 3-3 where audio bars are drawn on the LCD that he (re)discovered the issue with streaming audio off SDIO and updating a display that sits on SPI0 at the same time.

Mini Platform Setup.jpg


The patch in the fillRect() routine
Code:
          if (y > 1 && (y & 1)) {
            endSPITransaction();
            beginSPITransaction();
          }
that fixed the issue with drawing the audio bars was subsequently discovered to also corrupt just drawing text to the screen in the original demo software with no audio playing, just basic text printing.

It looks like drawing text uses fillRect() to erase whatever is on the screen before drawing the new text, so perhaps that is causing this new issue.

My problem is that I have already brought in quite a few of these systems (to beat the new tariffs) when this SDIO/SPI0 conflict issue was discovered. The issue is a bit of a boundary case, so not a complete show stopper for most applications, but it would be nice to get it resolved in some fashion.

Prototypes of this system used the ILI9341 LCD instead of the ST7796 and it does not have this same issue so I believe there were enough changes in that library to work around the issue, but unfortunately I can't follow the logic.
 
Yes, keyboard mouse to be minimized here.

Did a quick KLUDGE adjustment that clears up the display and allows the audio compromise to function.

Picked "100" as a guess for "H*W" (assuming fonts or other use small - and 10 rows of 240 were no problem) and it was enough for common text as shown in p#31 as boundary for the end/beginSPI hack.
> Bars draw in tutorial sketch with good audio playback
> Test in DEMO sketch

Code:
// fill a rectangle
void ST7735_t3::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
    x+=_originx;
    y+=_originy;
    int bbig=0;
    if ( w*h > 100 ) bbig=1;

...

            writedata16_last(color);       
        if (bbig && y > 1 && (y & 1)) {
          endSPITransaction();
          beginSPITransaction();
        }
         }
        endSPITransaction();
    }
}
 
Yes, keyboard mouse to be minimized here.
Ah, sympathies… maybe an opportunity for a Teensy project :unsure: ?

Maybe the ILI9341 and ST77xx have different rules for forgetting the output area that’s been configured with setAddr()? I thought the code only doing end+begin every other y was to save time, but perhaps it’s a chip restriction. I haven’t looked at either datasheet, just stumbled around in the code looking for stuff to hack at.

Either way, it seems that re-setting the output area after every begin is the way to go. I’ll take a look later.

Does my strategy of making the mid-transaction end+begin time based seem sensible? I’m using micros(), assuming that’s fast to execute, and it gets around doing loads of unnecessary stuff if the area is tall and thin. I’m guessing nobody much cares how many pixels get written, as long as there’s an interrupt opportunity every 1ms or so. Also … it insulates you from any changes to SPI bus speed.
 
Catch-22 on coding a project to limit coding? Left hand getting better at mouse - but keyboard gets both hands. Especially bopping around lines in the IDE.
Micros takes over 35 clocks to read (not awful) - if the ARM_CYC_CNT could work it reads in 3 clocks - but that runs current CPU MHz.

The p#36 math intrusion seems low cost? The single if(w*h) test and no recurring time check? And effective count of pixels to push? The 'bbig' test against 100 was effective on the two sketches at hand - one with 'Arial_12'. Would have to try on a larger font { 100 works fine with Arial_24 ) - seems the audio break was only over ~2,000 as breaking the 40 wide bars into 4 sets of 10 was effective, but doing one at 15 wide broke the sound - not sure if it was total rect pixels or width addressing? Assuming it was time of total pixels 15* (180? or 240?)

And the Audio BARS seemed to draw fine - even with the END/BEGIN inserted as it was? Though with fast redraw maybe it was hiding some artifacts?

NOTE: Quick test with Arial_24 and bbig test of 10 did not show obvious broken text as above. Seems 100 should catch most 'internal use' as tested.
 
Last edited:
Indeed! I've long since switched to a LH vertical mouse, due to my RH index finger end joint giving up after years of clicking. Hopeful I don't get any other issues (they don't run in the family) - I'd hate to have to give up playing sax ... though others might welcome the respite!

Good call on ARM_DWT_CYCCNT - it's easy enough to calculate the required scaling once at loop start. It just seemed to me that time is the essential variable here, and rather inelegant to have the overhead of 100 end+begin calls for a 2x200 rectangle.
 
Seems there is a 48 size font - not much fits on the screen [4"480x320] but bbig test against 10 for h*w stopped the broken text.
 
Did a quick KLUDGE adjustment that clears up the display and allows the audio compromise to function.
Good work on that. I just tried your fix on the 2 test examples we have been using and it looks good to me on first pass. I will try to spend some time this afternoon on some other test cases.
 
I've just pushed my time-based variant on this - see https://github.com/h4yn0nnym0u5e/ST7735_t3/tree/dev/break-transactions. My test code
is this - it looks a bit messy on-screen, but SD playback seems to work, unless you deliberately corrupt it by setting a very high allowed transaction length, e.g. tft.setMaxTransaction(100000);
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>

#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>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

///////////////////////////////////
// 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
#define TFT_DC       9
#define TFT_CS      22
//#define TFT_CS      10
#define TFT_RST    255  // 255 = unused, connect to 3.3V
#define TFT_MOSI    11
#define TFT_SCLK    13
#define TFT_MISO    12

#define LED_PWM  4

// Use these with the Teensy 3.2 and Audio Shield Rev C
//#define TFT_DC      20
//#define TFT_CS      21
//#define TFT_RST    255  // 255 = unused, connect to 3.3V
//#define TFT_MOSI     7
//#define TFT_SCLK    14
//#define TFT_MISO    12

#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 these with the Teensy Audio Shield
//#define SDCARD_CS_PIN    10
//#define SDCARD_MOSI_PIN  7   // Teensy 4 ignores this, uses pin 11
//#define SDCARD_SCK_PIN   14  // Teensy 4 ignores this, uses pin 13

// Use these with the Teensy 3.5 & 3.6 & 4.1 SD card
#define SDCARD_CS_PIN    BUILTIN_SDCARD
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used

// Use these for the SD+Wiz820 or other adaptors
//#define SDCARD_CS_PIN    4
//#define SDCARD_MOSI_PIN  11
//#define SDCARD_SCK_PIN   13

void setup() {
  pinMode(LED_PWM, OUTPUT);
  //analogWrite(LED_PWM, 64);
  digitalWrite(LED_PWM,1);
 
  Serial.begin(9600);
  delay(500);
  //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");
 
  AudioMemory(10);

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.3);
//  SPI.setMOSI(SDCARD_MOSI_PIN);
//  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    while (1) {
      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
  delay(1000);
}

elapsedMillis msecs;

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 (msecs > 15) {
      msecs = 0;
    if (peak1.available() && peak2.available()) {
      float leftNumber = peak1.read();
      float rightNumber = peak2.read();
      Serial.print(leftNumber);
      Serial.print(", ");
      Serial.print(rightNumber);
      //Serial.println();


      // draw the vertical bars
      // biggest single draw possible is
      // 240x40 = 9,600 pixels, which
      // is enough to cause issues with SD playback
#if defined ST77XX_BLACK
  const int barWid  =  40;
  const int barMax  = 240;
  const int barBase = 280;
#else 
  const int barWid  =  60;
  const int barMax  = 160;
  const int barBase = 200;
#endif // defined ST77XX_BLACK
      uint32_t maxMicros;
      elapsedMicros em = 0;
      int height = leftNumber * barMax;
      tft.fillRect(10, barBase - height, barWid, height, ST7735_GREEN);
      maxMicros = em; em = 0;
      tft.fillRect(10, barBase - barMax, barWid, barMax - height, ST7735_BLACK);
      if (em > maxMicros) maxMicros = em; em = 0;
      height = rightNumber * barMax;
      tft.fillRect(110, barBase - height, barWid, height, ST7735_GREEN);
      if (em > maxMicros) maxMicros = em; em = 0;
      tft.fillRect(110, barBase - barMax, barWid, barMax - height, ST7735_BLACK);
      if (em > maxMicros) maxMicros = em; em = 0;
      // a smarter approach would redraw only the changed portion...
/*
*/

      // draw numbers underneath each bar
      tft.setFont(Arial_14);
      tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
      
      tft.fillRect(60, barBase+4, 40, 16, ST7735_BLACK);
      tft.setCursor(60, barBase+4);
      tft.print(leftNumber);
      
      tft.fillRect(140, barBase+4, 40, 16, ST7735_BLACK);
      tft.setCursor(140, barBase+4);
//      tft.print(rightNumber);
      tft.print(msecs);
      
      // tft.fillRect(140+80, barBase+4, 100, 16, ST7735_BLACK);
      // print HUGE max time - also causes problems!
      tft.setCursor(140+0*60, 84);
      tft.setTextSize(9);
      tft.setTextColor(ST7735_RED, ST7735_BLACK);
#if defined ST77XX_BLACK
      //tft.setFont(nullptr);
      tft.setFont(Arial_96);
#else     
      tft.setFontAdafruit();
#endif // defined ST77XX_BLACK
      tft.print(maxMicros);
      tft.setTextColor(ST7735_RED, 0x38E7);
      if (maxMicros<10000) tft.print(' ');
      uint32_t maxTrans = tft.maxTransactionLengthSeen / (F_CPU / 1'000'000);
      Serial.printf("; maxMicros = %d; maxTransaction = %d %s\n",maxMicros,maxTrans, maxTrans > 2500?"*****":"");
      tft.maxTransactionLengthSeen = 0;

      msecs = 0;

    }
  }
}
 
@h4yn0nnym0u5e I downloaded your library and compiled your example. It works as expected. Also confirmed that changing to setMaxTransaction(100000) does break it as expected.

I then compiled the Defragster version of the sketch and it works and also breaks at 100000. Compiled the Mini Platform demo with your library and it also works OK. Nothing breaks at 100000 since it isn't doing any big transfers during audio playback. Overall looks promising to me.
h4yn0nnym0u5e example.jpg
 
That's good news. At some point I'll look at the TODO items where I think a mid-transaction break might be needed; it'll need a suitable torture test devised to show the changes have been effective without breaking anything, so won't necessarily be very quick to implement...
 
Gave a LIKE to p#42 and 43 as the custom lib fix worked here on Ken's Mini as it did for him.

It worked with the offered 'bbig' h*w count edit as well - though the BIG FONT overlapping the bar
 

Shifted Tutorial Bar example bars - made them 50 wide instead of 40 and wrote 2 96 font digits in sequence showing last millis between updates with audio playing no problem.
Action photo with blur and redraw...
4 big pairs are cycles of the re-entry draw ms.(3rd being updated this pass) The yellow on red text is : tft.maxTransactionLengthSeen / (F_CPU / 1'000'000)
>> Quick test versus the bbig math test shows similar msecs draw times.
>> Audio good and no breaks in drawing noticed.
1747556779690.png
 
Yes, sorry, my test did end up a bit messy on the screen!

Is your bbig method working OK for a drawChar() of large fonts with both foreground and background? I was finding that the blanked top and bottom were taking long enough to interrupt audio. It's a pity they're not done with fillRect(), for consistency, but there you go ... but the real pain was the setAddr() needing to be re-done when resuming mid-drawing. That's all now dealt with in the new midTransaction() method, one "just" needs to figure out the new correct area bounds, if required.
 
Oh, and yes, I think if you're measuring in milliseconds the possibly increased overhead of the bbig approach won't really show up; if you draw tall rectangles, and measure the time in microseconds, you're more likely to see it.
 
Yes, bbig version shows same resulting screen image - the DrawChar uses fillRect() for larger areas where the bbig fix was located.
> Char draw rect's are small and don't break audio, but the end&begin without bbig would break the character draw even small font as shown.
p#46 github updated
top two 96 font shows last two ms of draw time
3rd is MIN draw time : BOTH versions take min 22ms for total draw [bar updates and all digits drawn]
4th is MAX draw time : BOTH versions take max 63ms for total draw
FUN NOTE: ST7735_t3-dev-break-transactions running at 816 MHz gets min=22 faster and max 1ms less at max=62 - audio plays fine
 
OK, I've had a quick play. You're getting good results because you're blanking the draw area yourself, then drawing characters using a transparent background. (This approach is needed anyway because of wanting to pack huge characters in a small area.) This seems to use only calls to fillRect(), so that's the only place the mid-transaction end+begin needs to be.

My demo was using the opaque-background routines, which are the ones not using fillRect() anywhere, and do need their own mid-transaction breaks if audio playback is to be clean. Whether the opaque-background code is sensible to use is moot, given it destroys quite a lot of screen area! Also, it seems to result in longer minimum draw times: obviously blanking the unused descender area costs more time than filling the whole rectangle and then overwriting some of that drawing with character data - interesting. It looks as if the huge and horrible piece of code ensuring every pixel is written only once is completely wasted ... I could be wrong, though.

I'm pretty sure the draw time is hugely dominated by the SPI bus speed. My display, despite some rather dodgy wiring, can be run at 24MHz, which gives me min/max times of 15/42 ms (600MHz Fcpu).
 
Back
Top