S. Marsanne

Debugging SPI Quick Checklist

Discussion created by S. Marsanne Employee on Dec 15, 2016

SPI is one of the oldest serial bus along with RS232 (UART).

SPI comes with 4 wire mode with easy signal names:

NSS = Chip Slave select (in multi-drop bus scheme, one pin per slave is needed)

MISO = Master Input pin, Slave Ouput Pin (cleaver name! better than what is RX/TX RS232 pin between devices)

MOSI = Master Output pin, Slave Input Pin 

SCK = the clock pin. Here it's 1 bit per clock period. The clock polarity and phase is programmable... 4 combinations.

 

After all these years, SPI is still popular because it's push-pull output:

- Can go up to 40 Mbps (if the bus capacitive load permits)

- Relative low power (I2C consumes more power because "0" bits consume current in the pull-ups)

- Easy to level shift without speed penalty (because of monodirectional signals)

- SPI is the best candidate for DMA transfer in order to minimize delays between transferred bytes

 

SPI exists in 2 architecture modes

- parallel (multiple slaves sharing the same MISO/MOSI/SCK with separate NSS for each)

 

 

- serial (usually external DACs or ADCs) (multiple slaves daisy chaining MISO/MOSI loop back to master as ring configuration, one or no NSS)

 

 

SPI popular slave devices are sensors/mems, flash/eeprom memories and some small displays.

For memories, the SPI evolved into QSPI for higher bandwidth, 1, 2, or 4 data lines, SDR (1 bit per clock period), DDR (1 bit per clock edge), which allows XiP (execute directly in place, without dumping to SRAM and execute)

 

For normal devices, to reduce pincount, MISO+MOSI got combined for 3 wire interface mode, with bi-directional data line.

Level shifting would limit the bandwidth in this case. (~8 Mbps)

 

Then the next step in reducing pincount was to embed the NSS signal within the data line... which is I2C bus. (lower speed)

 

Note: About STM32 SPI HW peripheral:

The SPI has a newer generation peripheral which includes a TX and RX FIFO to serialize data to be sent/received by SPI.

It can also send chunks of 4 to 16 bits quantities (8 or 16 previously).

 

With the FIFO, the way to write/read DR is now meaningful: Writing an 8 bit quantity is different than writing 16 bit quantity in DR, while before the MSB was "dropped". If the SPI is sending in 8 bit mode, writing 0x1234 in the DR will result in 16 clocks (2x8 bits) queued in the TX FIFO.

For the RX FIFO, the meaning of the RXNE is programmable as well.

 

 

========================

In case it helps, here a pseudo code to do SPI 3 wires by GPIO bit banding (not intended for speedy communication):

 


#define SPIIO_SDA GPIOI,GPIO_Pin_3
#define SPIIO_CLK GPIOI,GPIO_Pin_1
#define SPIIO_NSS GPIOI,GPIO_Pin_0

#define SPIIO_Set_CLK_Low GPIO_ResetBits(SPIIO_CLK)
#define SPIIO_Set_CLK_High GPIO_SetBits(SPIIO_CLK)
#define SPIIO_Set_CLK_Out GPIO_Configure_Port_Pin_Status(SPIIO_CLK,GPIO_Mode_Out_PP_Low_Fast)

#define SPIIO_Set_SDA_Low GPIO_ResetBits(SPIIO_SDA)
#define SPIIO_Set_SDA_High GPIO_SetBits(SPIIO_SDA)
#define SPIIO_Set_SDA_In GPIO_Configure_Port_Pin_Status(SPIIO_SDA,GPIO_Mode_In_PU_No_IT)//GPIO_Mode_In_PU_No_IT)
#define SPIIO_Set_SDA_Out GPIO_Configure_Port_Pin_Status(SPIIO_SDA,GPIO_Mode_Out_PP_Low_Fast)
#define SPIIO_Get_SDA GPIO_ReadInputDataBit(SPIIO_SDA)

#define SPIIO_Set_NSS_High GPIO_SetBits(SPIIO_NSS)
#define SPIIO_Set_NSS_Low GPIO_ResetBits(SPIIO_NSS)
#define SPIIO_Set_NSS_Out GPIO_Configure_Port_Pin_Status(SPIIO_NSS,GPIO_Mode_Out_PP_High_Fast)

//===== 8>< ~~~~ debug beg
//===== 8>< ~~~~ debug end




// no compile optimisation and SYSCLK = 96 MHz
// 0 => 1.5 Mbps
// 1 => 1.2 Mbps
// 2 => 950 kbps
// 3 => 850 kbps
// 4 => 750 kbps (*) default for STM32F437
// 5 => 700
// 6 => 600
// 7 => 550
// 8 => 500 kbps
// 9 => 450 kbps
// 10 => 400 kHz
// 30 => ???

u8 SPI_DELAY = 1;
void SPIIO_SetDelay(u8 delay)
{
SPI_DELAY = delay;
}

u8 SPIIO_GetDelay(void)
{
return SPI_DELAY;
}




void SPIIO_Test(void)
{
u8 buf[8];

while(1) {

Wait_ms(1);

SPIIO_Start();
SPIIO_SendByte(0xB4);
SPIIO_SendByte(0x00);
SPIIO_SendByte(0x07);
SPIIO_Dummy();
buf[0] = SPIIO_ReadByte();
buf[1] = SPIIO_ReadByte();
buf[2] = SPIIO_ReadByte();
buf[3] = SPIIO_ReadByte();
buf[4] = SPIIO_ReadByte();
buf[5] = SPIIO_ReadByte();
buf[6] = SPIIO_ReadByte();
buf[7] = SPIIO_ReadByte();
SPIIO_Stop();
};
}

void SPIIO_Start(void)
{
SPIIO_Set_NSS_High;
SPIIO_Set_NSS_Out;

SPIIO_Set_CLK_Low;
SPIIO_Set_CLK_Out;

SPIIO_Set_SDA_Low;
SPIIO_Set_SDA_Out;

NOPs(SPI_DELAY+4);

SPIIO_Set_NSS_Low; // selected

NOPs(SPI_DELAY);

}

void SPIIO_Stop(void)
{

//MCU_I2CIO_Init(); // restore the I2C bus type (because NSS=1 means I2C mode, while NSS=0 means SPI 3 wire mode...
NOPs(SPI_DELAY+4);

SPIIO_Set_NSS_High; // raise

NOPs(SPI_DELAY+40);
}


void SPIIO_SendByte(u8 byte)
{
// bit 7
SPIIO_Set_CLK_Low;
if(byte & 0x80)
SPIIO_Set_SDA_High;
else
SPIIO_Set_SDA_Low;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 6
SPIIO_Set_CLK_Low;
if(byte & 0x40)
SPIIO_Set_SDA_High;
else
SPIIO_Set_SDA_Low;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 5
SPIIO_Set_CLK_Low;
if(byte & 0x20)
SPIIO_Set_SDA_High;
else
SPIIO_Set_SDA_Low;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 4
SPIIO_Set_CLK_Low;
if(byte & 0x10)
SPIIO_Set_SDA_High;
else
SPIIO_Set_SDA_Low;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 3
SPIIO_Set_CLK_Low;
if(byte & 0x08)
SPIIO_Set_SDA_High;
else
SPIIO_Set_SDA_Low;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 2
SPIIO_Set_CLK_Low;
if(byte & 0x04)
SPIIO_Set_SDA_High;
else
SPIIO_Set_SDA_Low;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 1
SPIIO_Set_CLK_Low;
if(byte & 0x02)
SPIIO_Set_SDA_High;
else
SPIIO_Set_SDA_Low;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 0
SPIIO_Set_CLK_Low;
if(byte & 0x01)
SPIIO_Set_SDA_High;
else
SPIIO_Set_SDA_Low;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_Low;

}

u8 SPIIO_ReadByte(void)
{
u8 byte = 0;

SPIIO_Set_SDA_In; //added for devices which don't have write to read dummy turnaround byte transition in SPI3W mode

// bit 7
SPIIO_Set_CLK_Low;
NOPs(SPI_DELAY);
if(SPIIO_Get_SDA)
byte |= 0x80;

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 6
SPIIO_Set_CLK_Low;
NOPs(SPI_DELAY);
if(SPIIO_Get_SDA)
byte |= 0x40;

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 5
SPIIO_Set_CLK_Low;
NOPs(SPI_DELAY);
if(SPIIO_Get_SDA)
byte |= 0x20;

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 4
SPIIO_Set_CLK_Low;
NOPs(SPI_DELAY);
if(SPIIO_Get_SDA)
byte |= 0x10;

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 3
SPIIO_Set_CLK_Low;
NOPs(SPI_DELAY);
if(SPIIO_Get_SDA)
byte |= 0x08;

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 2
SPIIO_Set_CLK_Low;
NOPs(SPI_DELAY);
if(SPIIO_Get_SDA)
byte |= 0x04;

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 1
SPIIO_Set_CLK_Low;
NOPs(SPI_DELAY);
if(SPIIO_Get_SDA)
byte |= 0x02;

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

// bit 0
SPIIO_Set_CLK_Low;
NOPs(SPI_DELAY);
if(SPIIO_Get_SDA)
byte |= 0x01;

SPIIO_Set_CLK_High;
NOPs(SPI_DELAY);

SPIIO_Set_CLK_Low;
return byte;
}

u8 SPIIO_Dummy(void)
{
SPIIO_Set_SDA_In;
return SPIIO_ReadByte();
}

Outcomes