Arduino as SPI slave?

Rob,
How's this look as an SPI Sniffer?
My only concern is the very first rising edge - may have to delay the edge a bit until after the 1st Qa high transition so there's no glitch to start.

Oooo, like I said before, you've got way too much time on your hands :slight_smile:

It will take me a while to have a good look, first thoughts though

Why not just use Qd as the FIFO clock? I think that will get rid of all the ORd inverters.

EDIT: Ignore for the moment, I don't think I looked close enough.


Rob

I think you could use SPI CLK directly into the SR and counter (gated by SS) and Qc to write the FIFO.

As the FIFO writes on the falling edge it will write just before the 9th clock pulse (when Qc goes low) so as long as the propagation delay of the SR isn't so fast (and the Qc signal slow) that it violates the FIFO hold time (unlikely) you should be OK.

There is a potential race condition with Qc probably happening at the same time as the SR OPs change. maybe the SR could use an inverted version of SPI CLK so it's running out of phase.

Just thinking out aloud, pity we don't have a white board.


Rob

Yeah, Qd only works every other byte.

I thought the open collector NAND was a pretty good solution. If all SS & the Qx are low, then the output is controlled by the SS clock.
Thinking maybe run SPI Clock thru couple stages of spare 74LS32 to move it out past the Qa transition.

FIFO writes in with W going high. Got to treat it like a latch.

"Data set-up and hold times must be adhered to, with respect to the rising edge of the Write Enable (W)."

See Figure 3.

Qd only works every other byte.

Yep, I changed it to Qc.

WRT the W/ signal, I just read the first line in the data sheet

WRITE ENABLE ( W ) — A write cycle is initiated on the falling edge of this
input

And figured it wrote on the falling edge, but then there's the next sentence

Data set-up and hold times must be adhered to,with respect to the rising edge of the Write Enable

So yes it's rising edge, so wouldn't an inverted Qc still work?


Rob

Just realized I had the W/ pulse drawn wrong - the '06 output goes to the FIFO, SPI Clk goes to the shift register clock.

The data has to be out of the shift register 12-15-18nS before the Write pulse goes high.
Data takes 10nS to go thru shift register after the clock. So I think Qc/ would go high too early unless I delay it.

I'm gonna dig up a couple parts & give it a try.
Not sure if I have a LS161, think I have some LS193's aound.

I love these kind of circuits having made a few logic analyzers over the years, but is all this circuitry really necessary?

An AVR could easily work as a FIFO, as I said in a previous post as long as it doesn't have to do anything else it could sample at full speed I'm sure. So using a 1284 will give a FIFO of up to 16k bytes. Then transfer the data to the host Arduino or just make it standalone.

Any Arduino could to this, so I don't think I understand the original problem :), I'll review the posts.


Rob

Rob,
Here's what I was able to confirm using 74LS163A, 74LS04, 74F32.
The 2 signals are the Clear into the '163 (bottom) and the W/ at the 'F32 pin 11.
I had to run it as discrete writes like this

int SS = 10;  // need for SPI
byte byte1 = 0x55;
byte pin13 = 13;

void setup(){
pinMode (SS, OUTPUT);
pinMode (pin13, OUTPUT);
}

void loop (){
byte x=0;
byte y=0;
digitalWrite(SS,LOW); 
while (x<33){
digitalWrite(pin13, y);
y=1-y;
x=x+1;}
digitalWrite(SS,HIGH);
}

Any time I tried to go faster, or to set my scope to faster than 10uS/div, I got so much aliasing I couldn't make out anything.
I varied between x = 17 for 8 clock pulses to the counter, adn 33 for 16 pulses.
The final low going pulse out of the last OR gate was confirmed to match up with the high portion of the 8th clock pulse in the 1 and 2 byte simuation.
The wired-Or wasn't working too well, had too low of a pullup resister so the signal didn't come up sharp. Tried the '32 instead of hunting for another resistor to see if I could get a cleaner edge, and did, and then fooled around seeing how fast I could go.
Was not able to run with actual SPI clock pulses and capture data with this USP scope. Need something better.
I don't see any glitches where I thought they might occur, so I think the routing of the clock thru the gates is gonna take care of that.

Now to order some FIFOs. Newark UK (and US) has them, $20 handling charge!

Circuit might be overkill to monitor the SPI bus, but the software solutions Nick G was trying didn't seem to be working.
This way we can sample for as much as the FIFO will allow. IDT makes 64K parts, if they can be located.
I think the biggest I was was 8K.

Here we go - 64K x 9!

1 part is Cheaper than 2 8kx9 parts from Newark/Farnell UK and without the extra $20 handling fee.

If I put a pair on, I could do high speed audio sampling and let the SdFat library store it to SD card.

Getting some ideas ...

or to set my scope to faster than 10uS/div, I got so much aliasing I couldn't make out anything.

Then you need a way better scope, or even better a logic analyzer.

Here we go - 64K x 9!

Jeez, do they still make PLCCs?


Rob

Back to the base question. What about using a 74HC595 shift register and a 74HC93 counter without FIFO? Looking at my oscilloscope measures the clock speed is 4 MHz and one byte arrives every 6,5 us. Since the shift register is triggered at the positive egde and the counter at negative edge I'm thinking one could use the same clock signal (SCLK) and setup the 7493 as a mod-8 counter. The only additional logic needed would be ~Qb & ~Qc & ~Qd & ~SS connected to the shift register latch clock and to an interrupt pin on the Arduino. Reading parallell inputs every 6,5 us wouldn't be a problem I guess? Otherwise polling could be done in a tight loop.

Wiring ~SS to shift register reset and to MR1 and MR2 on the counter would reset both the counter and shift register on SS high -> low.

Only problem is that this occupies 9 input pins on the Arduino. My goal is to monitor the heater controller and I'll need some of those pins. I'm thinking maybe using two Arduinos, one for monitoring SPI and one for alarm, onewire and hall sensor input.

Everything you describe is already present in the CPU, it's called an SPI interface. I can't see how moving the logic outside will help.

I think you can record bytes every 6.5uS but I haven't tried I admit. This is the fastest code I can think of

byte buffer [100];
byte *buffer_end = buffer + sizeof(buffer) + 1;
byte *pos = buffer;
byte mask = (1<<SPIF);  // assume this will wind up in a reg and so will be faster than a literal
byte temp;

do {
  while(!(SPSR & mask));    
  temp = SPSR; // dummy read to setup a clear of SPIF
  *pos++ = SPDR;  // get the data and clear SPIF
} while (pos < buff_end)

That will record 100 bytes then exit. After that you print the array to the serial port.

If that (or something similar) doesn't do the trick then it doesn't doesn't matter how many Arduinos you have you need either a faster processor, write the code in assembler, or use some hardware like what Crossroads has designed.


Rob

Graynomad:
Everything you describe is already present in the CPU, it's called an SPI interface. I can't see how moving the logic outside will help.

The AVR running as a slave samples the clock signal in software. That is the reason to why the 4 MHz clock signal is missed. The clock is not high long enough for the AVR to recognize it as a clock pulse. This could be solved using an external shift register?

as a slave samples the clock signal in software.

Not sure where you get that from, AFAIK its all done with hardware.

However there is a speed limitation and unfortunately the data sheet is a bit ambiguous here

To ensure
correct sampling of the clock signal, the minimum low and high periods should be:
Low periods: Longer than 2 CPU clock cycles.
High periods: Longer than 2 CPU clock cycles.

So does that mean 3 clock cycles?

Bottom line is that without some really good code you will never know if it's the hardware or the software that's not working.

This could be solved using an external shift register?

That's true if the SPI can't use a 4MHz clock.

Only problem is that this occupies 9 input pins on the Arduino. My goal is to monitor the heater controller and I'll need some of those pins

So this is needed permanently on the final application is it?


Rob

@rob,
Yes, better 'scope or logic analzer would certainly help. I bet wirewrapping this up would help also, signals are pretty crappy looking with jumper wires all over a breaboard.

@torgil,
What you describe in #39 is what I have working in #36 (just control signals so far). You can do it without the FIFO - capture serially in one shift register, move in parallel to a 2nd, and read it in serially with SPI command, that way its hardware all the way thru and not software sampling.
Have to check the control signals, make sure the parallell load signal is seperate from the clock out signal.
Still have to know when to bring the data in - so use the same parallel load signal to the 2nd shift register to create an interrupt to read the data?
I think interrupts have the same issue of having to be valid in time to be clocked in.

Here's some test code ...

Master:

// Written by Nick Gammon
// September 2011

#include <SPI.h>
#include "pins_arduino.h"

void setup (void)
{
  SPI.begin ();

  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  
}  // end of setup

void loop (void)
{
  delay (2000); 

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  for (byte i = 0; i < 255; i++)
    SPI.transfer (i);

  // disable Slave Select
  digitalWrite(SS, HIGH);

}  // end of loop

This sends out 255 bytes (where each byte is itself, ie. 0, 1, 2, 3, 4, 5 etc.).

Slave:

// Written by Nick Gammon
// September 2011

#include "pins_arduino.h"

byte buf [256];
byte pos;

void setup (void)
{
  Serial.begin (115200);   // debugging

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

}  // end of setup

// main loop - wait for flag set in interrupt routine
void loop (void)
{

    Serial.println ("Starting ...");
    pos = 0;
    
    while (pos < 255)
    {
    /* Wait for reception complete */ 
    while(!(SPSR & (1<<SPIF))) {} 
    buf [pos++] = SPDR;
    }
    
    // check valid
    Serial.println ("Checking ...");
    for (byte i = 0; i < 255; i++)
      if (buf [i] != i)
        {
        Serial.print ("Error at position ");
        Serial.print (i, DEC);
        Serial.print (" got ");
        Serial.println (buf [i], DEC);
        return;  
        }
        
    Serial.println ("Passed.");        
}  // end of loop

Now to save CPU cycles the slave does not use interrupts. It uses a tight loop waiting for the byte to appear in the hardware register.

Once 255 bytes have arrived it checks that each byte is itself (eg. 0, 1, 2, 3, 4, 5 ...).

My results with the above code are:

Starting ...
Checking ...
Passed.
Starting ...
Checking ...
Passed.
Starting ...
Checking ...
Passed.
Starting ...
Checking ...
Error at position 198 got 199
Starting ...
Checking ...
Error at position 0 got 254
Starting ...
Checking ...
Passed.
Starting ...
Checking ...
Error at position 153 got 154
Starting ...
Checking ...
Error at position 0 got 254
Starting ...
Checking ...
Error at position 0 got 254
Starting ...
Checking ...
Passed.
Starting ...
Checking ...
Passed.
Starting ...
Checking ...
Passed.
Starting ...
Checking ...
Passed.

So it seems to sometimes send all 255 bytes without error. But sometimes not. And this is with clock/8.

With this instead:

  SPI.setClockDivider(SPI_CLOCK_DIV16);

... it ran without errors.

That is way cool Nick!

Thanks!

I was wondering why the div8 code sometimes worked and sometimes not, and then I guessed ... interrupts! (timer interrupts).

So changing the tight loop to this:

    noInterrupts ();

    while (pos < 255)
      {
      /* Wait for reception complete */ 
      while(!(SPSR & (1<<SPIF))) 
         {} 
      buf [pos++] = SPDR;
      }

    interrupts ();

Now it works without errors even with clock/8 speed.

Further testing: also at SPI_CLOCK_DIV4. But not at SPI_CLOCK_DIV2.

Checking with the logic analyzer, SPI_CLOCK_DIV4 is a clock speed of 4 MHz (from one leading edge of the clock to the next).