Game Instance


Let the games begin

Serial Peripheral Interface

the Arduino way

I wish the story would have been over the first time. However the subject wouldn't go well into that good night. Not yet.

It seems that Arduino IDE committee decided to provide support for SPI master only. If you want to write code for slave, you're pretty much on your own. It would be superficial thinking that they haven't considered or tried it until now. They surely did but then at some point something must have gone haywire and aborted the plans. Why, you may ask? I still don't fully know, but I'm about to find out.

I haven't been completely honest with you. There is a valid approach that became the de facto solution for the slave issue. It comes from the community (Gammon et al). However, nothing officially from the Arduino, not for the latest version, currently 1.8.0. The skeptic in me needed more so I kept digging.

Documentations on the subject helped shaping the set of functioning steps. Here they are.

SPI communication steps

1. Master chooses a clock (CLK) frequency, the phase and the bit shift order based on prior knowledge of the slave it is about to talk to.

2. Master pulls down (issues a LOW level on the) corresponding SS pin to select its favorite slave.

3. Waits a certain time that's specific to the slave, also known as setup time.

4. Charges up its data shift register and waits for a specific amount of time for its slave to do the same.

5. The master ticks the CLK for a number of cycles (equal to the register size) during which both the master and slave registers are fired bit by bit onto the corresponding lines of the bus (MOSI and MISO).

    * The data bits from the master and slave are not sent simultaneously. They're issued on different clock phases either by the master or by the slave avoiding combinational hazard. Basically one listens for the other in the first half of cycle and the other way around on the last half.
    * The last CLK cycle marks the completion of the exchange of register contents.

6. The master takes its time to process the received data register and grants a known amount of time for the same to happen on the slave.

7. The process repeats from point 4 for as long as master has data to be sent or expected to arrive.

The traits

The logic above reveals the master as being a good guy, despite its pompous name, that pays attention to its slave's moods and limitations. The underlined fragments indicate it. In fact, the master needs to so that data transfer is kept in check. This also emphasizes the well defined hierarchy described in the previous article on SPI.

It all seems clear and straightforward, the way we like it, but following those steps again we actually see that they talk about the master as the one doing most of the work. What a great guy! The slave just waits for selection and clock signals to do something, leaving the hard work of synchronization on the master's shoulders. That begs yet another question: how does the slave react to the data arrival?

The interrupt

A while ago I sketched the externally triggered interrupts. A short article that put in perspective the way controllers react to stimuli. A valid answer for the previous question can come from there. The slave could endlessly wait for the master to send data but it would be inefficient. It may execute other tasks but upon interrupt, it would handle arriving data with top priority.

So, what would trigger that interrupt in this case? Is it the SS assertion? The first CLK or a word transmission? What? Well, the slave select line could trigger the interrupt but would also be inefficient. The transmission time could be limited to a small percentage out of the SS line selection making it impractical. The two remaining candidates seem promising. The first, trigger on the first clock cycle seems the obvious choice. It can make the MCU stand at attention for the word transmission, which is also known as the word size. Great, isn't it? Believe it or not, there's an efficiency penalty in that as well. The master could arbitrary decide on a reduce clock frequency, after all it is the master. That interrupt service routine will run for longer to receive the same amount of data. You get the point.

The solution

The only plausible solution, and the one available for ATMega328, is triggering the data register read upon word transfer completion. It is also the one proposed by Gammon. When the internal interrupt routine starts, the data register has just been overwritten with data from the master. After storing the word from the register, the routine can write new data onto the same register. That data will be transfered on the next transaction.

More or less intentional, one thing remained out of this discussion. The fact that the slave Arduino MCU, say ATMega328, has built-in SPI capability. Configured as slave, such a chip is wired internally to store data from the MOSI line when a clock signal is applied on the SCK pin. Nothing need be done for this to happen. The same internal wiring also provides us with the SPI_STC_vect interrupt that signals data arrivals.

The SPI Slave code

in this example is supposed to receive a message from the master at ANY clock speed, and reply with the message: "Me, slave!".

byte readData[32];
char writeData[] = "Me, slave!";
byte sizeWD = sizeof(writeData);
volatile byte iw = 0, ir = 0, ow = 0;

void setup() {
	// 
	Serial.begin(9600);
	// set "slave out" as output
	pinMode(MISO, OUTPUT);
	// sets SPI in slave mode
	SPCR |= _BV(SPE);
	// turns on interrupts
	SPCR |= _BV(SPIE);
}

void loop() {
	// 
	bool bData = false;
	if (ir != iw) {
		//
		bData = true;
		Serial.print("Received data: \"");
	}
	for (; ir != iw; ir ++) {
		//
		Serial.print((char)readData[ir]);
	}
	if (bData) {
		// 
		Serial.println("\"");
	}
	delay(250);
}

ISR (SPI_STC_vect) {
	// read from the data register
	readData[iw ++] = SPDR;
	// write onto the data register
	SPDR = writeData[ow ++];
	if (ow > sizeWD) {
		//
		ow = 0;
	}
}

The code should be self-explanatory and where it isn't, the comments should give you a hint. Note that there is no SPI API reference anywhere in the code. No SPI.h included, no SPISettings(), no SPI.begin(). Nothing that's mentioned in the Arduino documented pages. This is as low as it gets without touching assembler. Man vs. machine. Observe that there's no frequency reference either, leading to believe that this can receive and transmit messages at the maximum speed supported by the MCU.

The Master code

is old school Arduino. It's got the SPI.h include, the initialization, the SS assert, the SPI.transmit(). It's well within our comfort zone.

#include <SPI.h>

SPISettings conf(4000000, MSBFIRST, SPI_MODE0);
byte readData[32];
char writeData[] = "I'm master!";

void setup() {
	// 
	SPI.begin();
	Serial.begin(9600);
}

void loop() {
	// 
	SPI.beginTransaction(conf);
	digitalWrite(10, LOW);
	// slave setup time
	delayMicroseconds(10);
	byte i = 0;
	for (; i < sizeof(writeData); i ++) {
		// data exchange
		readData[i] = SPI.transfer(writeData[i]);
		// slave interrupt extra time
		delayMicroseconds(20);
	}
	digitalWrite(10, HIGH);
	SPI.endTransaction();
	Serial.print("Received: \"");
	for (byte j = 0; j < i; j ++) {
		// 
		Serial.print((char)readData[j]);
	}
	Serial.println("\"");
	delay(2000);
}

The master in this example send "I'm master!" every 2 seconds and reads the reply. For the above slave: "Me, slave!". The CLK frequency was limited to 4MHz. Anything higher than that corrupts the transmitted data. The reason for this is simple and comes from the ATMega328 datasheet. It seems that the SPI SCK frequency is limited to half that of the MCU clock frequency. Obeying Nyquist's rule, the slave will need to sample that SCK signal with a frequency twice as big, which was already halved. Alas, the SPI SCK frequency needs to be 1/4 of the MCU's clock frequency. 16MHz/4 = 4MHz.

The conclusion

There's a certain appeal in the low-level slave code, I know that, but I fear that wasn't the reason for being left as is. All facts indicate that the simplicity and flexibility of the solution made futile the attempt to include it in the higher level Arduino SPI lib.
That also transpires few guidelines from the Arduino inner philosophy:
* an Arduino compatible board should communicate with devices other than, er, another Arduino board.
* should it communicate with other devices, it must be the master.