4
\$\begingroup\$

The following code configures and enables SPI2 as slave on my STM32F303RE board, writes 0xAA, 0xBB, 0xCC, 0xDD bytes to DR register and loops in a while(1):

/* Enable clocks for GPIOB (SPI2 pins) and SPI2 peripheral. */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

/* SPI pin mappings. */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource12, GPIO_AF_5); /* SPI2_NSS */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_5); /* SPI2_SCK */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_5); /* SPI2_MISO */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_5); /* SPI2_MOSI */

GPIO_InitTypeDef gpio_init_struct =
{
    .GPIO_Mode = GPIO_Mode_AF,
    .GPIO_OType = GPIO_OType_PP,
    .GPIO_PuPd = GPIO_PuPd_DOWN,
    .GPIO_Speed = GPIO_Speed_50MHz
};

/* SPI NSS pin configuration. */
gpio_init_struct.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOB, &gpio_init_struct);
/* SPI SCK pin configuration. */
gpio_init_struct.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOB, &gpio_init_struct);
/* SPI MISO pin configuration. */
gpio_init_struct.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOB, &gpio_init_struct);
/* SPI  MOSI pin configuration. */
gpio_init_struct.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOB, &gpio_init_struct);

SPI_InitTypeDef spi_init_struct =
{
    .SPI_Direction          = SPI_Direction_2Lines_FullDuplex,
    .SPI_Mode               = SPI_Mode_Slave,
    .SPI_DataSize           = SPI_DataSize_8b,
    .SPI_CPOL               = SPI_CPOL_Low,
    .SPI_CPHA               = SPI_CPHA_1Edge,
    .SPI_NSS                = SPI_NSS_Hard,
    .SPI_BaudRatePrescaler  = SPI_BaudRatePrescaler_2,
    .SPI_FirstBit           = SPI_FirstBit_MSB,
    .SPI_CRCPolynomial      = 7
};

SPI_I2S_DeInit(SPI2);
SPI_Init(SPI2, &spi_init_struct);
SPI_CalculateCRC(SPI2, DISABLE);
SPI_TIModeCmd(SPI2, DISABLE);
SPI_NSSPulseModeCmd(SPI2, DISABLE);

SPI_Cmd(SPI2, ENABLE);
SPI_SendData8(SPI2, (uint8_t) 0xAA);
SPI_SendData8(SPI2, (uint8_t) 0xBB);
SPI_SendData8(SPI2, (uint8_t) 0xCC);
SPI_SendData8(SPI2, (uint8_t) 0xDD);

while(1) { }

With a master that requests 2 bytes per chip-select, the master receives:

0xAA 0xBB
0xCC 0xDD
0xAA 0xAA -----> TXFIFO should be empty here, why not "0x00 0x00"?
0xAA 0xAA
0xAA 0xAA
0xAA 0xAA
0xAA 0xAA
0xAA 0xAA
0xAA 0xAA
......... (0xAA 0xAA infinite times)

I would have expected that the master receives "0x00 0x00" after TXFIFO becomes empty. Why do I get "0xAA 0xAA" continuously instead? I couldn't find something that would point to such behavior in the manual.

UPDATE 1

Waiting for the transactions to finish just before the while(1) and writing zeros on the SPI afterwards, like this:

while(SPI_GetTransmissionFIFOStatus(SPI2) != SPI_TransmissionFIFOStatus_Empty) { }
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) != RESET) { }

#define ZEROS_CNT   (1)
for(int i = 0; i < ZEROS_CNT; i++)
    SPI_SendData8(SPI2, 0);

while(1) { }

renders the following master behavior for different values of ZEROS_CNT:

ZEROS_CNT = 0 => master receives after TXFIFO is empty: 0xAA infinitely
ZEROS_CNT = 1 => master receives after TXFIFO is empty: 0x00 1 times, followed by 0xBB infinitely
ZEROS_CNT = 2 => master receives after TXFIFO is empty: 0x00 2 times, followed by 0xCC infinitely
ZEROS_CNT = 3 => master receives after TXFIFO is empty: 0x00 3 times, followed by 0xDD infinitely
ZEROS_CNT >= 4 => master receives after TXFIFO is empty: 0x00 infinitely

It looks as if the SPI peripheral has some kind of history of what was written in the TXFIFO and when it becomes empty, it sends bytes from that history.

UPDATE 2

It behaves the same irrespective of how many bytes the master requests in a single chip-select. I have tried requesting 1, 2, 4 and 5 @ a time.

\$\endgroup\$
12
  • \$\begingroup\$ This looks quite odd. Did you try to disable the SPI after sending 0xdd? \$\endgroup\$ Oct 10, 2016 at 15:09
  • \$\begingroup\$ Hi Vladimir, yes, another odd thing is that once disabled, it will send "0x00 0x00" - but if it is re-enabled, it will start sending "0xAA 0xAA" again...two workarounds for this would be either disabling it until I have something new to send -OR- resetting the peripheral entirely (SPI_I2S_DeInit(SPI2)). However I'd like to know why it behaves this way (and by what logic) and if I could avoid doing a full-fledged reset (which also requires re-configuring the peripheral - kinda superfluous). \$\endgroup\$ Oct 10, 2016 at 15:16
  • \$\begingroup\$ Would you care adding the link to the relevant appnote/datasheet/whatever? I can't look into it right now but it might be back in my mind tonight. \$\endgroup\$ Oct 10, 2016 at 15:17
  • \$\begingroup\$ You mean the STM32 reference manual? For my STM32F303RE board, here it is: st.com/content/ccc/resource/technical/document/reference_manual/… \$\endgroup\$ Oct 10, 2016 at 15:20
  • \$\begingroup\$ Have you tried waiting for the received data to arrive? \$\endgroup\$
    – domen
    Oct 10, 2016 at 15:31

2 Answers 2

4
\$\begingroup\$

I figured this out and decided to make this answer more comprehensive by creating some animations. First of all, there are 2 facts to keep in mind that determine the logic behind TXFIFO's behavior:

  • As the manual states, TXFIFO is of size 32-bit = 4 bytes - let those bytes be B1|B2|B3|B4 in that order.
  • FACT #1: When the master requests a byte from the slave, the popped and returned byte is B1 but it isn't actually removed from TXFIFO ---> the TXFIFO contents are left-rotated, and not left-shifted. This results in TXFIFO looking like B2|B3|B4|B1 after this pop, instead of B2|B3|B4|00 (which is I think what @ogrenci meant with his answer).
  • FACT #2: When TXFIFO becomes empty, for some reason a master request receives the 1st byte that is in TXFIFO @ that moment instead of 0x00 as I would have expected.

RXFIFO most probably behaves the same way.

The algorithm for both push/pop, written in C# is as following:

public class TXFIFO
{
    public byte[] data;
    byte push_position = 1;
    byte occupied = 0;

    public TXFIFO()
    {
        data = new byte[4];
    }

    public byte Push(byte v)
    {
        // write
        data[push_position - 1] = v;
        // push_position
        if (push_position < 4) push_position++;
        else push_position = 1;
        // occupied
        if (occupied < 4) occupied++;
        return v;
    }

    public byte Pop()
    {
        // read
        if (occupied == 0) return data[0];
        byte v = data[0];
        // rotate left once
        for (int i = 1; i < 4; i++)
            data[i - 1] = data[i];
        data[3] = v;
        //push_position
        if (push_position > 1) push_position--;
        else push_position = 4;
        //occupied
        if (occupied > 0) occupied--;
        return v;
    }

    public byte GetOccupied()
    {
        return occupied;
    }
}

And here are 5 animations that illustrate the originally described ZEROS_CNT scenarios (see question's UPDATE 1). Note that, to make the point clearer, instead of inserting zeroes, I inserted 0x01-0x02-..to..-ZEROS_CNT values here.

ZEROS_CNT = 0:

enter image description here

ZEROS_CNT = 1:

enter image description here

ZEROS_CNT = 2:

enter image description here

ZEROS_CNT = 3:

enter image description here

ZEROS_CNT = 4:

enter image description here

ZEROS_CNT = 5:

enter image description here

...and so on...

As previously mentioned in the question, a workaround for having 0x00 sent when TXFIFO is empty would be keeping the SPI peripheral disabled when TXFIFO is empty until new data is written in DR, which is what I ended up doing, after understanding what is going on.

\$\endgroup\$
7
  • \$\begingroup\$ In fact, the data is probably not really copied around in the buffer but two indexes are maintained for the start and end of the buffered data. When start and end point to the same element of the buffer, that can mean either of two things: Either the buffer is completely full, or it is completely empty. No way to determine which one it is without maintaining another flag somewhere. See Circular Buffer. \$\endgroup\$
    – JimmyB
    Oct 11, 2016 at 15:50
  • \$\begingroup\$ @m.Alin thanks! I hope they make things clearer. JimmyB not sure what you're trying to say - the SPI does retain information telling you what the current occupancy level of the FIFOs are.. \$\endgroup\$ Oct 11, 2016 at 15:51
  • \$\begingroup\$ @JimmyB ooh, I understand. Yes, you're most probably right. It would be too inefficient to keep shifting elements to the left when doing a pop. Anyway, I think the results of pop/push operations above would render the same values. I guess one could try and understand how that shift schematic in the SPI block diagram actually works @ electronic level to really find out how these operations are done internally (the data structure used). \$\endgroup\$ Oct 11, 2016 at 15:56
  • \$\begingroup\$ Just wanted to provide a simple explanation as to why a certain element seems to remain in the buffer. Indeed, if the device knows when the buffer is empty, the SPI circuitry could/should use that bit of information to provide consistent output (0x00) instead of "random" garbage. \$\endgroup\$
    – JimmyB
    Oct 11, 2016 at 15:56
  • \$\begingroup\$ @JimmyB yes, thank you for the input and sorry, I didn't understand what you were trying to suggest the first time ;) \$\endgroup\$ Oct 11, 2016 at 16:05
0
\$\begingroup\$

The values after 4th byte are not sourced from TX buffer of slave. Examine the SPI block diagram. They are originally sent as dump data by master when the SCI is cycled and they return from the shift register of slave and appear as valid data. When master pulls data, that is what expected to be overridden, but doesn't in this case because slave doesn't push data into TX buffer and get the shift register loaded.

\$\endgroup\$
2
  • \$\begingroup\$ Hi ogrenci, did you mean "sent as dump data when the SPI is cycled by Raspberry PI"? \$\endgroup\$ Oct 10, 2016 at 18:55
  • \$\begingroup\$ Hi @CorneliuZuzu. I mean Raspberry PI. I edited the answer. \$\endgroup\$
    – Ayhan
    Oct 10, 2016 at 19:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.