serial-bench one bench, five serial buses - read-only archive

serial-bench / SPI / msg #3

A SPI daisy chain where the last word came back short

From
Ivo Kallas <ivo@rawsocket.cloud>
Date
Tue, 16 Sep 2025 09:00:00 +0300
Subject
A SPI daisy chain where the last word came back short
Message-ID
<20250916.0900.spidaisy@rawsocket.cloud>
In-Reply-To
<20250708.0900.spicpha@rawsocket.cloud> · A mode mismatch reads like a one-bit shift, not like noise
List-Id
serial-bench.rawsocket.cloud
X-Bus
SPI
X-Topic
SPI, mode 0, 8 MHz · chip-select timing, propagation

Three identical 16-bit ADCs on one SPI bus, wired as a daisy chain so that one chip-select line clocked all three. Two of the three readings were perfect. The third - always the one physically last in the chain - came back with its two most significant bits replaced by whatever had been in the register the cycle before. The failure was completely repeatable, which on a SPI bus usually means timing rather than noise.

In a daisy chain the devices share clock and chip select, and each device's data output feeds the next device's data input. To read all three you clock 48 bits out while chip select is low: the first device's word shifts through the second and third before it reaches the master. The master's job is to hold chip select low long enough for every bit to make that journey, and to not raise it until the last bit has arrived.

The clocks that go missing

Each device adds a propagation delay from its clock edge to its output changing - about 30 ns on these parts. With three in series the first device's data reaches the master 90 ns after the clock edge that launched it, on top of the half-bit the master already waits before sampling. At 8 MHz a bit is 125 ns, so 90 ns of accumulated delay is most of a bit cell, and the master was raising chip select exactly on the 48th falling edge - before the last two bits of the farthest device had propagated through the two devices in front of it.

The giveaway was which bits were wrong. A noisy bus corrupts bits at random positions; this always lost the top two bits of the last device, because those are the last to be clocked out and therefore the last to arrive. Raising chip select early simply cut the tail off the shift.

CSSCLKMISO chip select low - 48 clocks clk 1 clk 47–48 · · · · · · · · · · CS rises here bits 15,14 arrive after - lost 90 ns of propagation through three shift registers pushes the last bits past the chip-select edge
Fig. 1 - the dashed red bits are the two that belong to the last device in the chain. They are still propagating through the two devices ahead of it when chip select goes high, so the master never clocks them in and reads the previous cycle's bits in their place.

The propagation budget

Where the last device's final bit arrives, against the chip-select release, at 8 MHz
QuantityValueIn a 125 ns bit
Clock period125 ns1.00
Output delay per device30 ns0.24
Three devices in series90 ns0.72
Master sample offset (half bit)62 ns0.50
Last bit valid after 48th edge+90 ns0.72 late

The fix is embarrassingly small: hold chip select low for one extra half-bit after the 48th clock - in practice, add a short guard before raising it, or clock one or two trailing dummy bits and discard them. The driver now waits 150 ns after the final edge before releasing chip select, which at 90 ns of propagation leaves 60 ns of margin. Slowing the clock to 4 MHz also «fixed» it, for the same misleading reason it does on a mode mismatch: a 250 ns bit swallows the 90 ns delay, so the wrong behaviour hides rather than goes away.

The thing I now draw before wiring any daisy chain: the total clocks are devices × word length, and chip select must stay low for that many clocks plus the accumulated output delay of the whole chain. The per-device delay is tens of nanoseconds and feels negligible until you multiply it by the chain length and compare it to the bit cell. A two-device chain at 8 MHz is fine; a five-device chain is not, without slowing down or adding guard time.