Understand SPI Clock behavior in case of multiple Slaves

Status
Not open for further replies.

Zorglub

Member
Hi,

I currently working to a midi controller project based on Teensy 3.5. I try to connect 8 tft screens (this one base on ST7789 driver) using two hardware SPI ports from the teensy (so 4 screens by SPI port). To do so, I use "ST7789_t3" and "spi4teensy3" libraries. So far, I made a demo using my prototyping board and I don't understand the way the SCKL port is working.

All the SPI pins works perfectly "daisy chained" which means that I connected the pin (CS, DC/RS, MOSI but not Reset - i use the autoreset function of the screen - and MISO - I don't use the SD port) from the teensy to the first screen, the first screen to the second one,... (T->1->2->3->4) but not the SCLK pin. If I do so, the first screen in the chain does not initialize even if the 3 remaining one are working correctly. if I disconnect the SCLK of the last screen (T->1->2->3), the first screen initializes but obviously I still miss one screen.
iedy to connect all the SCLK pins of the screen directly to the SCLK pin of the teensy (aka "parallel chain", T->1 // T->2 // T->3 // T->4), none of the screens was initialized. The only way to get the 4 screens working is a strange combination:
- I connect the teensy SCLK pin to the second screen
- I connect the SCLK pin of the second screen to the first and third one in parallel.
- I connect the last screen SCLK to the third one in daisy chained.
(so T->2->3->4 // 2->1)

This behaviour seems to be linked to loading effect but I don't understand it, especially knowing that each TFT screen use 74HC4050D chip to buffer all the pins so the loading should be quite low.

Obviously I tried to reduce the SPI clock (6000000 to 24000000), and even the functions setClockDivider() but no change so far.

I need now to design my PCB but as I don't understand the behaviour of the SPI Clock, I feel a little bit uncomfortable. Does someone understand it and could explain to me what is going on?

Thanks in advance.
 
Obviously I tried to reduce the SPI clock (6000000 to 24000000), and even the functions setClockDivider() but no change so far.

Calling SPI.setClockDivider() in setup() will not have any effect. Each library sets the speed with SPI.beginTransaction() every time it communicates with the display. Any setting done with setClockDivider() will be overridden & lost the first time those libraries use the displays.

SPI.setClockDivider() is an old legacy function from the very early days of Arduino, before SPI.beginTransaction() and SPI.endTransaction(). These transaction functions were added because so many projects need to communicate with different SPI chips needing different settings. Today almost all libraries use the modern transaction functions. The days when you would just set one speed for all libs has long since passed. Maybe we should add a deprecated warning to those old functions?

To change the SPI clock speed, you need to dig into the library code and find the SPISettings it uses with SPI.beginTransaction().


I need now to design my PCB but as I don't understand the behaviour of the SPI Clock, I feel a little bit uncomfortable. Does someone understand it and could explain to me what is going on?

I've read your message twice and I still don't have a clear idea of how you've connected these displays. Maybe a diagram or photos would help?

I can tell you the common approach is to connect SCK, MOSI and MISO in common to all displays. The other signals are usually connected separately, so 2 pins used for CS & DC on the 1st displays, 2 different pins on Teensy used for those signals to the 2nd display, and so on.
 
I can tell you the common approach is to connect SCK, MOSI and MISO in common to all displays. The other signals are usually connected separately, so 2 pins used for CS & DC on the 1st displays, 2 different pins on Teensy used for those signals to the 2nd display, and so on.
There are some devices that support daisy chaining SPI communications (still with parallel clocking), but those should have their own documentation that explains the connections and protocols clearly (e.g. they may, in effect, double or more the response transfer sizes). No idea if that display controller would be such.

What comes to my mind is that maybe the idea here was to drive the bits in long series (chained) to every display, a bit like RGB LED matrix serial control. However, I think the idea how SPI works makes that unnecessarily tricky way, and I wonder if the full loop latencies become issue (since every device latency adds together).
 
You're right, it will be easier to understand with diagrams.

Here what I called "Daisy Chain":
Daisy-Chain.jpg
If I disconnect TFT#4, TFT#1 is working (but not #4 obviously). All the other daisy chained pin (DC, MOSI) are connected this way and working. I made a mistake in my first post, obviously CS is not daisy chained but each TFT have specific CS pins from the Teensy 3.5

Here what I called "Parallel Chain":
Parallel Chain.jpg
No working screen.

And here the "Mixed Chain" where all the TFT screens are working:
Mixed Chain.jpg
 
Last edited:
Calling SPI.setClockDivider() in setup() will not have any effect. Each library sets the speed with SPI.beginTransaction() every time it communicates with the display. Any setting done with setClockDivider() will be overridden & lost the first time those libraries use the displays.

I modified my local instance of the library at the SPI setup step to include this SPI.setClockDivider(), but, as you said, maybe this does not working. At least I seen no change with or without.

I can tell you the common approach is to connect SCK, MOSI and MISO in common to all displays. The other signals are usually connected separately, so 2 pins used for CS & DC on the 1st displays, 2 different pins on Teensy used for those signals to the 2nd display, and so on.

I "daisy chained" DC and it seems to works correctly. Using separated CS seems enough to control separately the screens, at least with the "mixed chain" for SCLK. I could try to have a separate DC to see if this change the behaviour but I don't see the interaction of DC with the SCLK so far.
 
If I understood those diagrams right, none of that is daisy chaining, per se. They are just physically different ways to have the same logical connection.
It might be about the quality of the clock signal in different configurations; reflections, loading, etc. (Same applies to data lines)
In that sense, any arrangement that works is ok. However, it might be working barely even on that "mixed chain" version. That is, in a little bit noisier environment even that might start to fail.

Heh, adafruit, on that product page: "Of course, we wouldn't just leave you with a datasheet and a "good luck!" Yet, the product page doesn't have even that datasheet. (Have to dig to the end of the "Learn" article to find the details.)
 
If I understood those diagrams right, none of that is daisy chaining, per se. They are just physically different ways to have the same logical connection.

I agree, "Daisy chain" is a poor way to describe the connection but english is not my native tongue and I failed to find a better expression, sorry.

It might be about the quality of the clock signal in different configurations; reflections, loading, etc. (Same applies to data lines)
In that sense, any arrangement that works is ok. However, it might be working barely even on that "mixed chain" version. That is, in a little bit noisier environment even that might start to fail.

Heh, adafruit, on that product page: "Of course, we wouldn't just leave you with a datasheet and a "good luck!" Yet, the product page doesn't have even that datasheet. (Have to dig to the end of the "Learn" article to find the details.)

As I said, I'm currently prototyping to see if the concept is correct. So I agree that my prototyping board could be noisy by nature. But I already used these screens in the past with the same prototyping board with other controllers and didn't have this issue (but the controller was less advanced than the teensy). Does that means that the teensy is sensible to noise? Because I plan to use not multilayered PCB in my final stage but simply etched PCB (cheaper for my very limited run) so and I'm not sure that it will be very less noisy.
 
Just "chained from input to input" might be closer to correct description (and English is not my native, either, and thus, I can't pull out a better term). It is the "daisy" in front that has the more specific meaning (where the signals actually go through the devices).
The version you call "parallel chain" is afaik better described as "star" (comes around more often with how ground connections should be made).

Prototyping boards themselves aren't noisy, but they may well be less protected against noises. A more common problem is simply a bad connection; the wire feels to be in there, but it actually doesn't make good electric connection. However, in this case it could be a combination of bad quality connection, signal reflections, noises, etc.

As long a PCB doesn't need too many connections in too little space, dual-layered board can do good connections for higher frequency signals, too. Single-layer board gets more challenging, but even with that one can "add" a ground layer for noise protection with a bit of creativity with cardboard/tape and foils (such tricks used to be "production level solutions" long time ago).
 
Here what I called "Daisy Chain":
View attachment 24605
If I disconnect TFT#4, TFT#1 is working (but not #4 obviously).

You're probably experiencing "signal quality" issues. It's a common problem when transmitting fast digital signals along long wires to multiple devices.


But I already used these screens in the past with the same prototyping board with other controllers and didn't have this issue (but the controller was less advanced than the teensy). Does that means that the teensy is sensible to noise?

Teensy's SPI port is capable of running at much higher clock speeds than most other microcontrollers. This can manifest as signal quality problems in 2 ways.

1: The faster clock gives less timing margin. Basically, the data signal must change and become stable before SCK goes from low to high (the tech term is "setup time") and may also need need to remain stable for a while afterward (called "hold time"). As everything gets faster, those times become shorter. But the display doesn't change. It still requires some amount of setup and hold time to properly recognize incoming data. If those displays have a 74HC4050 buffer, the speed of those buffer chip may also be contributing to the problem.

2: Long wires can cause problems in multiple ways. The first and most obvious is Teensy simply has 4X as many pins to drive, plus the wires, which can cause the waveforms to change from high to low and low to high more slowly. The less obvious issue is a variety of problems due to the length of the wires and the highest frequency present in the waveform. A faster board like Teensy 3.5 will have more high frequency content, partly from the higher clock speed, but also from the fact it changes low to high and high to low more rapidly. In electrical engineering terminology, this is called "transmission line effect". Especially if the wires are long, the signals can "reflect" and effectively bounce back and forth along the wire, which further causes the signals to be unstable for a brief time. Remember, at high clock speed you have less setup time, so you need those signals to become stable as quickly as possible.

There are a few things you can do. All of them are much simpler if you only need to transmit data. So as a first step, I'd disconnect the MISO signal. If your application can work without data from the displays back to Teensy, plan on leaving that signal unused. Then you only need to worry about transmitting from Teensy to those displays.

The type and arrangement of wires matters. Keeping the GND wire as close as possible to SCK and MOSI is the most important thing you can do with wiring. If you look at cables used for ethernet, USB and other high speed communication you'll see pairs of wires are twisted together. On a PCB, the best arrangement is to use 4 layers with a dedicated GND plane layer directly underneath all the high speed signals. On a 2 layer PCB you would want to route a GND signal next to SCK and another next to MOSI (or perhaps 1 between those 2) and make sure it connects all the way from Teensy to the display without taking a physically different path than those 2 signals.

The next level involves impedance matching. With signals like ethernet, you get a reliable spec for the wire's "characteristic impedance" and then you use a resistor with that number of ohms on both the transmitter and receiver. Sadly, you can't do anything nearly that good with ordinary digital signals because the receiving circuit expects the full original transmit voltage, and also because you don't always know the characteristic impedance of the wires or PCB traces.

The normal approach is to add a resistor in series with the transmitter. Place it as reasonably close to Teensy's pin as possible and connect the long wire to the resistor, not directly to Teensy. If you use wires with a clearly specified impedance, of course use a resistor matching your wire. But if you don't have a clear spec, 100 ohms is a pretty good guess if you've kept the GND wire close to the signals. The good news is even an imperfectly matched resistor & wire still gives a huge improvement over using no impedance matching resistor at all.

So as a 2nd step (first is arranging your wires so GND is as close as possible to SCK & MOSI), try adding 100 ohm resistors in-line with SCK and MOSI.

The other more extreme thing you can do is add 4 buffers for each signal to drive the 4 wires, perhaps each with its own resistor. That really adds up when you transmit 4 signals to 4 locations. But maybe 16 buffers and 16 resistors is worthwhile? They're cheap parts and if you use a chip like 74HC245, you get 8 buffers per chip.
 
As as a matter of technical terminology, this type of connection is called a "bus". The same data is transmitted from Teensy to all displays. Per-display control signals tell each display whether to use the data signal or ignore the data signal when it is intended for a different display.

The term "daisy chain" normally means a very different type of connection, where data from Teensy is transmitted to the first display, then that first display transmits a data signal to the 2nd display, which is then responsible for sending data to the 3rd display, and so on. This type of connection is common with addressable LEDs like WS2812 "NeoPixel" and APA102. JTAG is the other commonly used protocol which normally operates this way.

Daisy chain operation is theoretically possible with SPI, by connecting each chip's MISO pin to the next chip's MOSI pin, but this is rarely used in practice. SPI almost always is connected in a bus fashion with a separate chip select signal to each device.

Use the word "bus".
 
Thank you very much Paul for all this valuable and very precise information, I learn a lot! It would be great to add them to the page mentioned before for not advanced user like me, especially regarding the PCB design for such high frequency protocol. I'm more audio or Midi oriented so it's the first time I have to work with such protocol (my previous midi controller has no TFT screen).

Anyway, I will try the different way you mentioned and give some feedback.
 
Status
Not open for further replies.
Back
Top