[SOLVED] Can't get SPI to work on Arduino UNO (in a simulation)

Thread Starter

abraar_sameer

Joined May 29, 2017
37
Hello everyone,

I just learnt how the SPI protocol works, and wanted to try it out on a simulator before doing it on hardware. So, I wrote some code for communicating between two UNOs and tried to run it on Tinkercad.

The master sends an integer '10' and the slave sends an integer '20' back (well, at least that's what I want them to do). Then both of them print their received values over Serial. The SS pin (pin 10) on the master is set as an output and the one on the slave is hooked to ground (as an input), as explained in this application note.

But the problem is, it does not seem to work. Looks like it gets stuck in this line :

while(!(SPSR & (1 << SPIF)));

I guess this means that a transaction never takes place. What's wrong?

Here are the codes for the two devices:
C++:
//Master device
byte val;

void setup()
{
 
  SPI_initMaster();
  Serial.begin(9600);
  Serial.println("Ready...");
}

void loop()
{
  val = SPI_transact(10);
  Serial.println(val);
}

void SPI_initMaster(){
  DDRB |= (1<<PB2)|(1<<PB3)|(1<<PB5); //Configure MOSI, SCK and SS pins as output
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);  //Enable SPI as Master, Prescaler = 16
}

byte SPI_transact(byte data){
  SPDR = data;
  while(!(SPSR & (1 << SPIF)));
  return SPDR;
}
C++:
//Slave device
byte val;

void setup()
{
  SPI_initSlave();
  Serial.begin(9600);
  Serial.println("Ready...");
}

void loop()
{
  val = SPI_transact(20);
  Serial.println(val);
}
 
void SPI_initSlave(){
  DDRB |= (1<<PB4); //Configure MISO as output
  SPCR = (1<<SPE);  //Enable SPI as Slave
}

byte SPI_transact(byte data){
  SPDR = data;
  while(!(SPSR & (1 << SPIF)));
  return SPDR;
}

Here is the Tinkercad simulation. SPI demo - circuit.png
 

JohnInTX

Joined Jun 26, 2012
4,787
+1
EDIT: removed bad advice.

SPI usually has 4 wires, MOSI, MISO, SCK and SS/ (slave select). Are you missing one of these?

.
 
Last edited:

Thread Starter

abraar_sameer

Joined May 29, 2017
37
+1
Also, it looks like you have MISO on the master connected to MISO on the slave. Same with MOSI to MOSI. Your picture should show two wires crossing
MOSI on master to MISO on the slave and
MISO on master to MOSI on the slave.
Nope. SPI is not like serial where you cross the TX and RX connections. MOSI goes to MOSI, MISO goes to MISO.
 

bertus

Joined Apr 5, 2008
22,305
Hello,

The connections of SPI depend on the used mode:

Different Configuration Modes of SPI Bus

  1. Typical SPI bus
  2. Daisy chained SPI bus

In typical SPI bus mode, only one master device can control multiple independent slave devices. However, an independent chip select signal is required for each slave device which is provided by the master device as shown here. The obvious pitfall of this configuration is that the number of chip select pins required with the master devices should be equal to the number of slave devices we want to use. This is also known as an independent slave configuration.


Typical SPI bus mode

The Daisy chained SPI bus configuration is an improved version of the above-given model. It improves the previous configuration mode by reducing the drawback of typical SPI bus mode. In this mode, slave devices act like cooperative devices instead of independent devices. But the constraint of GPIO pins makes it difficult to implement typical SPI bus method for embedded systems applications like with microcontrollers. Therefore, another method is daisy-chained mode which propagates data through devices connected in the chain or in series as shown in the figure.
Daisy chained SPI bus mode

In this configuration, only one chip select signal from the master device controls all slave devices and also all slave devices works on the same clock signal. But only the first slave device gets data from master device directly, all other slave devices receive data on their input pin from the subsequent slave device as shown in the figure above. More information about the daisy-chained configuration is available on this link. Some devices come with Daisy chained and some with typical SPI mode configuration:

https://microcontrollerslab.com/introduction-to-spi-communication-protocol/

Bertus
 

Thread Starter

abraar_sameer

Joined May 29, 2017
37
+1
EDIT: removed bad advice.

SPI usually has 4 wires, MOSI, MISO, SCK and SS/ (slave select). Are you missing one of these?

.
Slave select. I'm not using it for I just have a single slave. The link I provided in the question says what to do with those pins. Slave SS pin hooked to ground, and master SS pin set as output internally.

I'll post after I test it on two real devices. Maybe tomorrow.
 

Thread Starter

abraar_sameer

Joined May 29, 2017
37
Hello guys,

I just tried it on two real devices, and everything works just fine!:)

Exact same code and configuration. It seems like the simulator doesn't really simulate all the internals of the chip completely. By the way, this was the first time I tried simulating uC code instead of trying it on a real device. I won't completely rely on simulators again. That's a good lesson after hours of frustration trying to check every single line of code to find a little mistake.

Thanks a lot.
 

jpanhalt

Joined Jan 18, 2008
11,087
Slave select. I'm not using it for I just have a single slave. The link I provided in the question says what to do with those pins. Slave SS pin hooked to ground, and master SS pin set as output internally.

I'll post after I test it on two real devices. Maybe tomorrow.
Just a word of caution. If tying !SS(!CS) to ground works, fine. I often keep !CS low for long periods, but be aware of the caveat in this post: https://electronics.stackexchange.com/questions/443839/spi-with-chip-select-to-ground .
Most SPI devices use the CS as a framing signal as well as for bus sharing, so you can't get away with just tying it to GND. From a quick look at the datasheet this appears to be the case for the MCP23S17.
Same idea is presented in Post #2 here with concurrence by others later in the thread: https://www.microchip.com/forums/m874569.aspx?high=3-wire+spi

Another 3-wire SPI connection scheme is this (source Microchip):
1603793142449.png

In my experience, one often sees MISO simply not connected to the device, e.g., in the case of an SPI display that cannot be read.
 

djsfantasi

Joined Apr 11, 2010
9,188
while(!(SPSR & (1 << SPIF)));
Did you get it to work with the above line of code? Because it looks funky...

The syntax of the << operator is
<< Syntax:
 variable << number_of_bits;
The line you referenced is
Code:
number_of_bits << variable;
I’d have to test your syntax. As written, I’m not sure what it does, particularly in a simulator.
 

nsaspook

Joined Aug 27, 2009
13,554
Just a word of caution. If tying !SS(!CS) to ground works, fine. I often keep !CS low for long periods, but be aware of the caveat in this post: https://electronics.stackexchange.com/questions/443839/spi-with-chip-select-to-ground .
Same idea is presented in Post #2 here with concurrence by others later in the thread: https://www.microchip.com/forums/m874569.aspx?high=3-wire+spi

Another 3-wire SPI connection scheme is this (source Microchip):
View attachment 220753

In my experience, one often sees MISO simply not connected to the device, e.g., in the case of an SPI display that cannot be read.
While resistors 'work' on 3-wire SPI connection bit-Banged pins, using the SPI hardware with module output pin disable like on the pic24 is better because you can direct connect the pins for much better signal integrity, higher transfer speeds (the resistors act as low pass filters.) and better coding efficiency using existing hardware.

page 216: http://ww1.microchip.com/downloads/en/DeviceDoc/39969b.pdf
bit 11 DISSDO: Disable SDOx Pin bit(2)
1 = SDOx pin is not used by the module; pin functions as I/O
0 = SDOx pin is controlled by the module

C:
/*
 * PIC32: changed to use hardware SPI module and plib_gpio pin functions
 * IMU SPI is half-duplex so we short pic32 SPI TX to RX signals and toggle TX pin state to PORT pin mode input during SPI receive
 */
void imu_SPIreadBytes(unsigned char csPin, unsigned char subAddress, unsigned char *dest, unsigned char count)
{
    int i;

    // To indicate a read, set bit 0 (msb) of first byte to 1
    unsigned char rAddress = 0x80 | (subAddress & 0x3F);

    // Mag SPI port is different. If we're reading multiple bytes, 
    // set bit 1 to 1. The remaining six bytes are the address to be read
    if ((csPin == __pinM) && count > 1) rAddress |= 0x40;

    SPI2CONbits.DISSDO = 0; // half-duplex switching, SPI TX ON
    if (csPin == __pinM)
        csPin_m_Clear();
    else
        csPin_ag_Clear();

    SPI2_Write(&rAddress, 1);
    SPI2CONbits.DISSDO = 1;
    for (i = 0; i < count; i++) {
        SPI2_Read(&dest[i], 1); // half-duplex switching, SPI TX OFF, PORT pin mode input
    }
    if (csPin == __pinM)
        csPin_m_Set();
    else
        csPin_ag_Set();

}

/*
 * PIC32: changed to use hardware SPI module and plib_gpio pin functions
 */
void imu_SPIwriteByte(unsigned char csPin, unsigned char subAddress, unsigned char data)
{
    uint8_t tmp = subAddress & 0x3F;

    SPI2CONbits.DISSDO = 0; // half-duplex switching, SPI TX ON, PORT pin mode OFF
    if (csPin == __pinM)
        csPin_m_Clear();
    else
        csPin_ag_Clear();
    
    SPI2_Write(&tmp, 1);
    SPI2_Write(&data, 1);
    if (csPin == __pinM) 
        csPin_m_Set();
    else
        csPin_ag_Set();
    
}
 
Top