Many Peripherals: CS, Daisy Chain and Tri-State

Put several peripherals on one SPI bus without contention, run devices of different modes, and know when a daisy chain is the better wiring.

Lesson
P7.4
Time
30 min
Runs on
Your browser
Check
4 questions

Two flash chips shared one SPI bus with separate selects, and reads from either returned garbage only when the other was powered. One chip's firmware drove MISO even when deselected, an option meant for a point-to-point link. The logic analyser showed clean edges and wrong bytes.

MISO Fans In: Only the Selected Peripheral DrivesThree peripherals share CLK, MOSI and MISO; each has its own select. The driver view lanes oe[0..2] show who drives MISO.
MISO fans in: only the selected peripheral drivesThree peripherals share CLK, MOSI and MISO; each has its own select. The driver view lanes oe[0..2] show who drives MISO. component Controller CLK, MOSI, CS0..CS2 component p0 selected: drives MISO component p1 deselected: releases component p2 deselected: releases component MISO one driver at a time oe[0] = 1oe[1] = 0oe[2] = 0
  1. Correct

    p0 is selected and drives; p1 and p2 hold their outputs at z. The controller reads p0 alone.

  2. The twin

    p1 keeps driving although deselected. Wherever p0 drives 1 and p1 drives 0 the line is x: p0's A5h reads x0x00x0x.

Read the diagram as text
  • Controller: CLK, MOSI, CS0..CS2
  • p0: selected: drives MISO
  • p1: deselected: releases
  • p2: deselected: releases
  • MISO: one driver at a time
  • p0 → MISO: oe[0] = 1
  • p1 → MISO: oe[1] = 0
  • p2 → MISO: oe[2] = 0
  • MISO → Controller

Arrows are drivers onto MISO. CLK and MOSI fan out from the controller to all three and are not drawn.

Deselected Means Released

MISO fans in: every peripheral can drive it, so exactly one may at a time. A deselected device must put its output at high impedance, within 7 ns of CS rising for the flash (tSHQZ).

With p1.drive_when_deselected, both p0 and p1 drive MISO. Wherever they disagree the simulator shows x, and p0's A5h reads x0x00x0x. On a board the same clock shows a mid-rail voltage, two hot drivers, and whatever each receiver makes of it.

Measured: the read of p0 with p1 driving. The monitor logs X for conflicting driven values; the checker names the rule.
SPI m0 02/XX
SPI m0 00/XX
SPI CHECK miso_released margin 0.000 ns
p0 DATA read as x0x00x0x

That is why the analyser in the story showed clean bits: its threshold settled the fight one way, the controller's the other. The simulator's x is the honest answer, and the checker names it: miso_released.

Modes Are per Device

p1 read in mode 0

p1.cpha = 1;
sel = 1;
cs_lo(); xfer(8'h02, received); xfer(8'h00, received); cs_hi();

Reads 1dh for 3Ah, with same_edge races.

Controller switched to mode 1 for p1

set_mode(0, 1);
sel = 1;
cs_lo(); xfer(8'h02, received); xfer(8'h00, received); cs_hi();

Reads 3ah.

Devices of different modes can share a bus, because the mode is agreed per device. p1 is a mode-1 device. Written in mode 0, its data still lands; read in mode 0, 3Ah comes back as 1dh, with same_edge races. With the controller switched to mode 1 for p1's frames it reads 3ah.

The Daisy Chain

A Daisy Chain: Three 8-bit Registers, One 24-bit Shift RegisterOne select for all three. MISO of each device feeds MOSI of the next, and the last device drives the controller's MISO.
A daisy chain: three 8-bit registers, one 24-bit shift registerOne select for all three. MISO of each device feeds MOSI of the next, and the last device drives the controller's MISO. component Controller sends 11h, 22h, 33h component p0 ends up with 33h component p1 ends up with 22h component p2 ends up with 11h component MISO last device tocontroller MOSIq0q1
  1. One 24-clock frame

    The first byte sent travels furthest: after 24 clocks 11h sits in p2, 22h in p1, 33h in p0.

  2. What comes back

    Meanwhile the old contents shift out of p2 into the controller, oldest first.

Read the diagram as text
  • Controller: sends 11h, 22h, 33h
  • p0: ends up with 33h
  • p1: ends up with 22h
  • p2: ends up with 11h
  • MISO: last device to controller
  • Controller → p0: MOSI
  • p0 → p1: q0
  • p1 → p2: q1
  • p2 → MISO
  • MISO → Controller

Arrows are the path of the bits. Measured: p0 33, p1 22, p2 11.

With one select and each MISO feeding the next MOSI, the three 8-bit registers form one 24-bit shift register. A 24-clock frame updates all three at once. The first byte sent travels furthest.

Measured on the chain bench. With the middle device not chained, p2 receives p1's own reply instead of the data.
SPI m0 11/3c
SPI m0 22/3c
SPI m0 33/3c
held: p0 33, p1 22, p2 11
SPI m0 44/11
SPI m0 55/22
SPI m0 66/00
held: p0 66, p1 22, p2 00
One select per deviceDaisy chain
Pinsthree wires plus one select per devicethree wires plus one select
Clocks to update one device16 here: command and data24: every device, every time
Addressingby selectby position in the chain
One dead deviceonly that device is lostthe whole chain stops
MISOshared: every deselected device must release itpoint to point: no sharing

Daisy chains suit identical devices updated together, such as LED drivers or DAC banks.

Where You Will Meet It

Every board with more than one SPI device has this bus. A bench for it reads each device with the others both released and misbehaving, switches modes between frames, and checks MISO for x on every clock.

Asked in interviews: several SPI devices
  1. How do you connect several SPI peripherals?

    Share CLK, MOSI and MISO; give each peripheral its own active-low select. Every deselected peripheral must release MISO.

  2. What happens if a deselected peripheral drives MISO?

    Contention: x in simulation wherever the two drivers disagree, a mid-rail level and heat on a board.

  3. Can devices with different SPI modes share a bus?

    Yes. The controller changes CPOL and CPHA between frames, per device.

  4. What is a daisy chain?

    One select for all devices, each MISO wired to the next MOSI, forming one long shift register updated by one long frame.

Try It

The bench has three peripherals, p0 to p2, with one select each; sel picks which select the next cs_lo() pulls low. The checker and the monitor watch the shared lines.

Swap the twin to p2.drive_when_deselected = 1; and set p2.data_reg to a different value: the x bits move to wherever p0 and p2 disagree.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: SPI m0 02/3c SPI m0 00/a5 SPI m0 02/XX SPI m0 00/XX SPI CHECK miso_released margin 0.000 ns p0 DATA read as x0x00x0x SPI CHECK same_edge margin 1.000 ns SPI CHECK same_edge margin 1.000 ns SPI m0 82/1e SPI m0 3a/00 SPI CHECK same_edge margin 1.000 ns SPI CHECK same_edge margin 1.000 ns SPI m0 02/1e SPI CHECK same_edge margin 1.000 ns SPI CHECK same_edge margin 1.000 ns SPI CHECK same_edge margin 1.000 ns SPI m0 00/1d SPI CHECK same_edge margin 1.000 ns SPI m1 02/3c SPI m1 00/3a
Read included file: spi_periph.sv
`timescale 1ns/1ps
// A teaching register device, not the Winbond register map. Writes commit at CS
// rising only when the entire command/data frame ended on a byte boundary.
module spi_periph #(parameter CPOL=0,CPHA=0)(input wire cs_n,sclk,mosi,inout wire miso,output wire oe);
 int cpol=CPOL,cpha=CPHA,wrong_mode=0;
 bit drive_when_deselected=0,late_miso=0,lsb_first=0,loopback=0,chain=0;
 realtime t_clqv=1.0;
 reg [7:0] tx_word=8'h3c,last_rx=0,ctrl=0,data_reg=0,shift=0,response=8'h3c,pending=0;
 reg out=0;int count=0,words=0,frames=0;reg [7:0] command=0;
 wire p=cpol[0]^((wrong_mode&2)!=0),h=cpha[0]^((wrong_mode&1)!=0);
 assign oe=!cs_n||drive_when_deselected;assign miso=oe?out:1'bz;
 function automatic bit txbit(input int i);txbit=chain?shift[7]:response[lsb_first?i:7-i];endfunction
 function automatic [7:0] reg_value(input reg [6:0] addr);
  case(addr)0:reg_value=8'h3c;1:reg_value=ctrl;2:reg_value=data_reg;3:reg_value=frames[7:0];default:reg_value=0;endcase
 endfunction
 always @(negedge cs_n)begin
  count=0;shift=chain?tx_word:0;words=0;command=0;response=tx_word;
  // Preload bit 7 without advancing count. A same-edge 0/3 (or 2/1)
  // pair may sample before the peripheral sees its first launch edge.
  // CPHA=1's leading launch repeats this bit; only sampling advances count.
  out<=#(t_clqv)txbit(0);
 end
 always @(posedge cs_n)begin
  if(words>0)begin
   if(!chain&&!loopback&&count==0&&words>=2&&command[7])begin
    case(command[6:0])1:ctrl=pending;2:data_reg=pending;default:;endcase
   end
   frames++;if(chain)tx_word=shift;
  end
 end
 always @(sclk)if(!cs_n)begin
  if(sclk==(h?p:!p))begin
   shift={shift[6:0],mosi};count++;
   if(count==8)begin
    last_rx=shift;count=0;
    if(!chain)begin
     if(loopback)response=shift;
     else if(words==0)begin command=shift;response=shift[7]?0:reg_value(shift[6:0]);end
     else pending=shift;
    end
    words++;
   end
   if(late_miso)out<=#(t_clqv)txbit(count);
  end else if(!late_miso)out<=#(t_clqv)txbit(count);
 end
endmodule
Read included file: spi_ctrl_tasks.svh
int cpol=0,cpha=0;realtime half_period=5.0,t_launch=1.0;bit cs_too_early=0,sample_late=0,cs_between_bytes=0;int clocks=0;
task automatic set_mode(input int p,input int h);cpol=p;cpha=h;sclk=p;endtask
task automatic set_period(input real ns);if(ns<=0)$fatal(1,"SPI period must be positive");half_period=ns/2;endtask
task automatic cs_lo(input int id=0);sclk=cpol;#(half_period);cs_n=0;#(half_period);endtask
task automatic cs_hi(input int id=0);#(half_period);cs_n=1;#(half_period);endtask
task automatic xfer(input reg [7:0] value,output reg [7:0] received);
 received=0;
 for(int b=7;b>=0;b--)begin
  if(t_launch>=half_period)$fatal(1,"launch delay must fit half-period");
  if(!cpha)begin
   #(t_launch);mosi=value[b];#(half_period-t_launch);
   if(!sample_late)received[b]=miso;sclk=!cpol;clocks++;
   #(half_period);if(sample_late)received[b]=miso;sclk=cpol;
  end else begin
   #(half_period);sclk=!cpol;clocks++;#(t_launch);mosi=value[b];#(half_period-t_launch);
   if(!sample_late)received[b]=miso;sclk=cpol;
   if(sample_late)begin #(half_period);received[b]=miso;end
  end
  if(cs_too_early&&b==1)cs_n=1;
 end
 if(cs_between_bytes)begin cs_hi();cs_lo();end
endtask
Read included file: spi_checker.sv
`timescale 1ns/1ps
// Mode-generic teaching checker. tCLQV margin uses the configured receiver setup;
// positive margin is a bound, not a guarantee that a particular byte is correct.
module spi_checker(input wire cs_n,sclk,mosi,miso,oe,input wire cpol,cpha);
 int broken=0,samples=0;realtime changed=0,mosi_changed=0,last_sample=-100,last_launch=-100,last_edge=-100,selected=-100,deselected=-100;
 bit enabled=0,sample_late=0;realtime tdvch=1.0,tchdx=2.0,tclqv=1.0,receiver_setup=0.0;
 realtime tslch=3.0,tchsh=3.0,tshch=3.0,tchsl=3.0,tshsl=10.0;
 task automatic bad(input string rule,input real margin);broken++;$display("SPI CHECK %s margin %0.3f ns",rule,margin);endtask
 // Late sampling consumes the old bit just before the next launch. Changes
 // after that launch are legal; its setup bound is checked by tclqv below.
 always @(miso)begin changed=$realtime;
  if(enabled&&!sample_late&&!cs_n&&samples>0&&sclk==(cpha?cpol:!cpol))bad("same_edge",$realtime-last_sample);
 end
 always @(mosi)begin
  mosi_changed=$realtime;
  if(enabled&&!cs_n&&$realtime-last_sample<tchdx)bad("tchdx",$realtime-last_sample-tchdx);
 end
 task automatic timing_margin(input real half_period,input real tclqv_max,input real setup_ns);
  real margin; margin=(sample_late?2:1)*half_period-tclqv_max-setup_ns;
  $display("SPI MARGIN half=%0.3f tCLQV=%0.3f setup=%0.3f margin=%0.3f ns",half_period,tclqv_max,setup_ns,margin);
  if(margin<0)bad("tclqv",margin);
 endtask
 always @(negedge cs_n)begin
  if(sclk!==cpol)bad("cs_clk_idle",0);
  if(enabled&&$realtime-deselected<tshsl)bad("tshsl",$realtime-deselected-tshsl);
  if(enabled&&$realtime-last_edge<tchsl)bad("tchsl",$realtime-last_edge-tchsl);
  selected=$realtime;samples=0;enabled=1;
 end
 always @(sclk)begin
  if(enabled)begin
   if(!cs_n&&$realtime-selected<tslch)bad("tslch",$realtime-selected-tslch);
   if(cs_n&&$realtime-deselected<tshch)bad("tshch",$realtime-deselected-tshch);
  end
  last_edge=$realtime;
  if(!cs_n)begin
   if(sclk==(cpha?cpol:!cpol))begin
    samples++;last_sample=$realtime;
    if($realtime-mosi_changed<tdvch)bad("tdvch",$realtime-mosi_changed-tdvch);
    if(!sample_late&&last_launch>=selected&&$realtime-last_launch<tclqv+receiver_setup)bad("tclqv",$realtime-last_launch-tclqv-receiver_setup);
   end else begin
    if(sample_late&&last_launch>=selected&&$realtime-last_launch<tclqv+receiver_setup)bad("tclqv",$realtime-last_launch-tclqv-receiver_setup);
    last_launch=$realtime;
   end
  end
 end
 always @(posedge cs_n)if(enabled)begin
  if(sclk!==cpol)bad("cs_clk_idle",0);
  if($realtime-last_edge<tchsh)bad("tchsh",$realtime-last_edge-tchsh);
  if(samples%8!=0)bad("byte_boundary",0);
  deselected=$realtime;#1;if(oe)bad("miso_released",0);
 end
endmodule
Read included file: spi_mon.sv
`timescale 1ns/1ps
module spi_mon(input wire cs_n,sclk,mosi,miso,input wire cpol,cpha);
 reg [7:0] a=0,b=0;int count=0,frames=0;bit trace=0,sample_wrong_edge=0;
 always @(negedge cs_n)count=0;
 always @(sclk)if(!cs_n&&sclk==((cpha^sample_wrong_edge)?cpol:!cpol))begin
  a={a[6:0],mosi};b={b[6:0],miso};count++;
  if(count==8)begin frames++;if(trace)$display("SPI m%0d %02h/%02h",2*cpol+cpha,a,b);count=0;end
 end
endmodule

Check Yourself

Predict the outputBench defaults, two peripherals, monitor and checker off. p0's DATA holds F0h; p1 is deselected but drives MISO, and its output rests at 0. The controller reads p0's DATA and prints it with %b. What does it print?
`timescale 1ns/1ps
module tb;
  reg cs_n = 1, sclk = 0, mosi = 0; tri miso; reg [7:0] received;
  `include "spi_ctrl_tasks.svh"              // set_mode, set_period, cs_lo, cs_hi, xfer
  int sel = 0; wire [1:0] cs = cs_n ? '1 : ~(2'b1 << sel);   // one select per peripheral
  wire [1:0] oe;                              // driver view: who drives MISO
  spi_periph #(.CPOL(0), .CPHA(0)) p0 (cs[0], sclk, mosi, miso, oe[0]);
  spi_periph #(.CPOL(0), .CPHA(0)) p1 (cs[1], sclk, mosi, miso, oe[1]);
  initial begin
    set_mode(0, 0);
    p0.data_reg = 8'hf0;
    #10;
    p1.drive_when_deselected = 1;
    sel = 0; cs_lo(); xfer(8'h02, received); xfer(8'h00, received); cs_hi();
    $display("%b", received);
    $finish;
  end
  initial begin #100000; $fatal(1, "watchdog"); end
endmodule
Read included file: spi_periph.sv
`timescale 1ns/1ps
// A teaching register device, not the Winbond register map. Writes commit at CS
// rising only when the entire command/data frame ended on a byte boundary.
module spi_periph #(parameter CPOL=0,CPHA=0)(input wire cs_n,sclk,mosi,inout wire miso,output wire oe);
 int cpol=CPOL,cpha=CPHA,wrong_mode=0;
 bit drive_when_deselected=0,late_miso=0,lsb_first=0,loopback=0,chain=0;
 realtime t_clqv=1.0;
 reg [7:0] tx_word=8'h3c,last_rx=0,ctrl=0,data_reg=0,shift=0,response=8'h3c,pending=0;
 reg out=0;int count=0,words=0,frames=0;reg [7:0] command=0;
 wire p=cpol[0]^((wrong_mode&2)!=0),h=cpha[0]^((wrong_mode&1)!=0);
 assign oe=!cs_n||drive_when_deselected;assign miso=oe?out:1'bz;
 function automatic bit txbit(input int i);txbit=chain?shift[7]:response[lsb_first?i:7-i];endfunction
 function automatic [7:0] reg_value(input reg [6:0] addr);
  case(addr)0:reg_value=8'h3c;1:reg_value=ctrl;2:reg_value=data_reg;3:reg_value=frames[7:0];default:reg_value=0;endcase
 endfunction
 always @(negedge cs_n)begin
  count=0;shift=chain?tx_word:0;words=0;command=0;response=tx_word;
  // Preload bit 7 without advancing count. A same-edge 0/3 (or 2/1)
  // pair may sample before the peripheral sees its first launch edge.
  // CPHA=1's leading launch repeats this bit; only sampling advances count.
  out<=#(t_clqv)txbit(0);
 end
 always @(posedge cs_n)begin
  if(words>0)begin
   if(!chain&&!loopback&&count==0&&words>=2&&command[7])begin
    case(command[6:0])1:ctrl=pending;2:data_reg=pending;default:;endcase
   end
   frames++;if(chain)tx_word=shift;
  end
 end
 always @(sclk)if(!cs_n)begin
  if(sclk==(h?p:!p))begin
   shift={shift[6:0],mosi};count++;
   if(count==8)begin
    last_rx=shift;count=0;
    if(!chain)begin
     if(loopback)response=shift;
     else if(words==0)begin command=shift;response=shift[7]?0:reg_value(shift[6:0]);end
     else pending=shift;
    end
    words++;
   end
   if(late_miso)out<=#(t_clqv)txbit(count);
  end else if(!late_miso)out<=#(t_clqv)txbit(count);
 end
endmodule
Read included file: spi_ctrl_tasks.svh
int cpol=0,cpha=0;realtime half_period=5.0,t_launch=1.0;bit cs_too_early=0,sample_late=0,cs_between_bytes=0;int clocks=0;
task automatic set_mode(input int p,input int h);cpol=p;cpha=h;sclk=p;endtask
task automatic set_period(input real ns);if(ns<=0)$fatal(1,"SPI period must be positive");half_period=ns/2;endtask
task automatic cs_lo(input int id=0);sclk=cpol;#(half_period);cs_n=0;#(half_period);endtask
task automatic cs_hi(input int id=0);#(half_period);cs_n=1;#(half_period);endtask
task automatic xfer(input reg [7:0] value,output reg [7:0] received);
 received=0;
 for(int b=7;b>=0;b--)begin
  if(t_launch>=half_period)$fatal(1,"launch delay must fit half-period");
  if(!cpha)begin
   #(t_launch);mosi=value[b];#(half_period-t_launch);
   if(!sample_late)received[b]=miso;sclk=!cpol;clocks++;
   #(half_period);if(sample_late)received[b]=miso;sclk=cpol;
  end else begin
   #(half_period);sclk=!cpol;clocks++;#(t_launch);mosi=value[b];#(half_period-t_launch);
   if(!sample_late)received[b]=miso;sclk=cpol;
   if(sample_late)begin #(half_period);received[b]=miso;end
  end
  if(cs_too_early&&b==1)cs_n=1;
 end
 if(cs_between_bytes)begin cs_hi();cs_lo();end
endtask
Read included file: spi_checker.sv
`timescale 1ns/1ps
// Mode-generic teaching checker. tCLQV margin uses the configured receiver setup;
// positive margin is a bound, not a guarantee that a particular byte is correct.
module spi_checker(input wire cs_n,sclk,mosi,miso,oe,input wire cpol,cpha);
 int broken=0,samples=0;realtime changed=0,mosi_changed=0,last_sample=-100,last_launch=-100,last_edge=-100,selected=-100,deselected=-100;
 bit enabled=0,sample_late=0;realtime tdvch=1.0,tchdx=2.0,tclqv=1.0,receiver_setup=0.0;
 realtime tslch=3.0,tchsh=3.0,tshch=3.0,tchsl=3.0,tshsl=10.0;
 task automatic bad(input string rule,input real margin);broken++;$display("SPI CHECK %s margin %0.3f ns",rule,margin);endtask
 // Late sampling consumes the old bit just before the next launch. Changes
 // after that launch are legal; its setup bound is checked by tclqv below.
 always @(miso)begin changed=$realtime;
  if(enabled&&!sample_late&&!cs_n&&samples>0&&sclk==(cpha?cpol:!cpol))bad("same_edge",$realtime-last_sample);
 end
 always @(mosi)begin
  mosi_changed=$realtime;
  if(enabled&&!cs_n&&$realtime-last_sample<tchdx)bad("tchdx",$realtime-last_sample-tchdx);
 end
 task automatic timing_margin(input real half_period,input real tclqv_max,input real setup_ns);
  real margin; margin=(sample_late?2:1)*half_period-tclqv_max-setup_ns;
  $display("SPI MARGIN half=%0.3f tCLQV=%0.3f setup=%0.3f margin=%0.3f ns",half_period,tclqv_max,setup_ns,margin);
  if(margin<0)bad("tclqv",margin);
 endtask
 always @(negedge cs_n)begin
  if(sclk!==cpol)bad("cs_clk_idle",0);
  if(enabled&&$realtime-deselected<tshsl)bad("tshsl",$realtime-deselected-tshsl);
  if(enabled&&$realtime-last_edge<tchsl)bad("tchsl",$realtime-last_edge-tchsl);
  selected=$realtime;samples=0;enabled=1;
 end
 always @(sclk)begin
  if(enabled)begin
   if(!cs_n&&$realtime-selected<tslch)bad("tslch",$realtime-selected-tslch);
   if(cs_n&&$realtime-deselected<tshch)bad("tshch",$realtime-deselected-tshch);
  end
  last_edge=$realtime;
  if(!cs_n)begin
   if(sclk==(cpha?cpol:!cpol))begin
    samples++;last_sample=$realtime;
    if($realtime-mosi_changed<tdvch)bad("tdvch",$realtime-mosi_changed-tdvch);
    if(!sample_late&&last_launch>=selected&&$realtime-last_launch<tclqv+receiver_setup)bad("tclqv",$realtime-last_launch-tclqv-receiver_setup);
   end else begin
    if(sample_late&&last_launch>=selected&&$realtime-last_launch<tclqv+receiver_setup)bad("tclqv",$realtime-last_launch-tclqv-receiver_setup);
    last_launch=$realtime;
   end
  end
 end
 always @(posedge cs_n)if(enabled)begin
  if(sclk!==cpol)bad("cs_clk_idle",0);
  if($realtime-last_edge<tchsh)bad("tchsh",$realtime-last_edge-tchsh);
  if(samples%8!=0)bad("byte_boundary",0);
  deselected=$realtime;#1;if(oe)bad("miso_released",0);
 end
endmodule
Read included file: spi_mon.sv
`timescale 1ns/1ps
module spi_mon(input wire cs_n,sclk,mosi,miso,input wire cpol,cpha);
 reg [7:0] a=0,b=0;int count=0,frames=0;bit trace=0,sample_wrong_edge=0;
 always @(negedge cs_n)count=0;
 always @(sclk)if(!cs_n&&sclk==((cpha^sample_wrong_edge)?cpol:!cpol))begin
  a={a[6:0],mosi};b={b[6:0],miso};count++;
  if(count==8)begin frames++;if(trace)$display("SPI m%0d %02h/%02h",2*cpol+cpha,a,b);count=0;end
 end
endmodule
Spot the bugTwo copies of a one-bit MISO driver share a line: one selected and driving 1, one deselected. Each option is the driver's output statement. Which one breaks the shared line?
ConceptWhy does simulation show x where the board's logic analyser showed clean bits?
ConceptA chain of three devices receives 11h, 22h, 33h in one 24-clock frame. Which device ends up holding 11h?

Remember

  • Each peripheral has its own select; CLK, MOSI and MISO are shared.
  • A deselected peripheral must release MISO (z); contention is x in simulation and a hot pin on a board.
  • Modes are agreed per device; the controller switches CPOL and CPHA between frames.
  • A daisy chain is one long shift register on one select: one frame updates every device, and the first byte goes furthest.

Practice