Doing "SDA reads" on a Teensy (SPI hack certain TFT controllers use)

honey_the_codewitch

Active member
Display controllers like the ST7789 and ST7735 and I think some other controller families have a strange facility they use for reading the frame buffer back off of the device (almost necessary for realistically alpha blending and anti-aliasing)

They basically use the MOSI line as a MISO line as well (usually called SDA in the datasheet for the controller). On the ESP32 family it just works automagically as long as you set the MISO and MOSI lines to the same pin #. On the Teensy you can't do that, because you can't set the pins of the SPI connections.

So my question is, what's the recommended way of doing SDA reads off of such a device?

One thought I had was detaching the pinMatrix? from the SPI if possible, and setting its mode to INPUT and then bitbanging the reads. I'll do it if possible, and if I had to, but obviously this isn't a first resort for a number of reasons, including the lack of DMA capability.

Is there a recommended way of doing this?

I really dislike that the controllers do this. It has caused complications on most platforms I've done it with, the ESP32 being the "easiest" that I've found, because they sort of baked the capability in.

Sorry, I don't have code for this. I can provide ESP32 code, but that's it.
 
I would envision a hardware hack such that MISO and MOSI are connected with a resistor. The SDA pin would be tied to MISO instead of MOSI. The resistor value would need to be low enough such that the SDA pin can be driven through the resistor when writing to the display. During reads the dummy data written would be all ones ( 0xff ) to provide a pullup. The resistor would need to be high enough that the SDA signal can drive it low during reads. I guess I would try a 1k resistor and see if it works.
 
Last edited:
You misunderstood me. I'm not interested in creating a display controller, and if I was I would not use SDA reads.

I need to know how to do this in software with the teensy. I felt I was clear in my post, but maybe you could tell me what part threw you so I can clarify?

Basically I need to know how to cajole the code in SPI.h to interface with a device that ties these lines together.
 
It probably can be done. You need to look at the datasheet of the processor you are using.

SPI library does not have it built in.

If T4.x then look at the LPSPI registers.
Guessing: CFGR1 to do PINCFG
And probably TCR register to control if IN or OUT, but not sure as I have not tried it.

Might at some point try on our ST7735_t3/89 library.
 
My proposal was a hardware solution to your problem and you wouldn't need to make any software changes to the SPI library. I have done this type of thing with UART's before. I do not know if it would work with SPI as the baud rate is much higher, but I thought because of the simplicity it would be worth a try.

The idea is that during writes, the MOSI pin on the Teensy drives both SDA and MISO via a series resistor. During reads, the SDA line drives the Teensy MISO pin directly and MOSI is isolated from the circuit with the resistor.

If you still need a software only solution perhaps someone else can jump in here.
 
I should mention that most of our display libraries do not use SPI library, but custom code
 
KurtE,

I'd prefer to use htcw_gfx due to TrueType support, and the fact that I am the maintainer and I use it professionally, so I can add features and improve things as needed.

It currently works with the Teensy, but the SDA reads are the trick. I don't know how to set that up. I would be really interested to know what you find when/if you get around to tinkering on this in your own library.
 
Sorry it probably won't be anytime soon.

As for reading pixels... Most of our libraries have setup the ability for logical frame buffer, where we write to memory and then tell system to update the screen. So, in those cases, you simply get the data from memory.
 
Yeah, with htcw_gfx of course, you can write to an intermediary bitmap, but it's more work, and kind of bleh when all you want to do is draw some vector fonts anti-aliased to the screen.

Furthermore, while the teensy has 1MB of SRAM it's still prohibitive to require say, a 160kB frame buffer to be available for general draw operations, at least as a requirement. This is especially true in a situation where there's a perfectly readable framebuffer on the controller.

It's a solution, just not a great one.

Oh well. It's workable for now.
 
Some of us may play with this a little soon.

Everything is a tradeoff, especially where I feel like spending my time. As a retired software developer/manager, these days I do things here just for the fun of it.

As for as using up valuable memory. I hear you. Also, depending on which Teensy you are trying to use can make a difference. I am assuming T4.x as you mentioned 1mB... But if T4.1 you could also add up to 16mb of PSRAM to the bottom of it.

You are also trading off memory versus speed. But logically the ::readPixel will work something like:
Code:
digitalWrite(CS, LOW);
digitalWrite(DC, LOW);
SPI.transfer(CASET);
digitalWrite(DC, HIGH);
SPI.transfer16(x0);
SPI.transfer16(x1);
digitalWrite(DC, LOW);
SPI.transfer(PASET);
digitalWrite(DC, HIGH);
SPI.transfer16(y0);
SPI.transfer16(y1);
digitalWrite(DC, LOW);
SPI.transfer(RAMRD);
digitalWrite(DC, HIGH);
<MUCK with MISO/MOSI LINE>
color = SPI.transfer16(0);
<Restore MISO/MOSI LINE>
digitalWrite(CS, HIGH);

So, to read one byte: you transfer something like: 13 bytes over SPI.
Some displays you can short circuit some if same X as before don't output, ditto for same Y

Using the bare bones SPI library performance can really suffer as for example the straightforward implementation:
In between each SPI.Transfer, the SPI Buss is idle, often as much time as it takes to send the previous transfer.

How much mucking you have to do, also depends on things like is the only SPI device using this SPI buss?

This is why most of our drivers have code specific to the underlying hardware, that try to make full use of the SPI FIFO queue to keep the SPI Buss as busy as possible.

There are sort of hybrid solutions that can work, that is instead of reading one pixel at a time you can use the readRect (assuming implemented) and read in whole section of area that may be needed, as each extra pixel read in only costs you one extra word transferred.

Or for example when we added the Adafruit Font text drawing to our libraries. Some of their pixels can overlap with the next or previous character, so
we try to cheat having to read those pixels that are out of the cells bounds, we remember the previous character and check it to see what it output for that pixel...

Now back to playing with Tablets
 
Yeah, I understand the pitfalls of it pretty well. And I realize that using the arduino SPI.h facilities is a performance tradeoff compared to going to the registers directly.

Here's the thing. htcw_gfx has no knowledge of things like SPI, I2C, or any sort of display hardware. No idea. It has no clue.

Those facilities are completely decoupled from my library, and allow me to swap out higher performance drivers and such as I get around to writing them.

For example, on the ESP32 platform, my device drivers will use its hardware SPI registers and DMA capabilities. On other platforms like Teensy, it falls back.

But I can rewrite that layer to support teensy without changing any upstream code. So I'll get to it. For now I just want it to work. I guess SDA reads are off the table for now, which is livable, for now. I run into a similar problem on other platforms.

As far as eliminating some of the traffic during reads, htcw_gfx dynamically handles that by automatically attempting to read rectangles at a time off the display. It does that behind the scenes whenever you use pixels with an alpha channel that isn't 0f or 1f

The tradeoffs you mention are valid, but the real proof as always, comes from profiling, and doing 20mhz reads off an ILI9341 is not prohibitive in the wild, even on an ESP32. It's a lot harder on a machine with say, 300kB of available SRAM to require half of that tied up in a frame buffer, so it all depends on the platform.

htcw_gfx is platform agnostic, and the drivers are written to be frugal with RAM. I could easily write them to be able to keep an offscreen frame buffer, but then I'd still like the option of reading straight off the display hardware, given it's available. Of course, on an ILI934x this is straightforward, even on a teensy. On an ST77xx not so much, due to the SDA read nonsense.
 
Last edited:
Back
Top