Electronic – STM32 SPI Peripheral bug

flashmemorynon-volatile-memoryspistm32

I'm using STM32F051R8T6 on STM32F0-Discovery board and M25P80 flash IC as a peripheral.
I've got the SPI configured in 3-line (+ 1 software chip select line), full-duplex mode and I'm using polling to send and receive data. I've had a lot of issues trying to get SPI to work properly and what it all boiled down to is this series of tests executed consecutively, which exposed a bug I can't explain.

Step 1: Request a signature from the peripheral. Signature is 0x13, works 99% of the time.

Step 2: Enable writing to the peripheral by issuing a command to it.

Step 3: Read the contents of the status register to make sure that previous command made it through.

Since the peripheral operates in full-duplex mode, but data is never sent in both directions at the same time, input buffer needs to have the same dimensions as the output buffer and data written to it has some offset.

Problem arises at the last step. If only a single byte is requested to be read, SPI doesn't register data being transmitted to it! If more bytes are requested (by using 1 + X as the fourth function parameter), contents of the input buffer are following:

@ 1 + 3:
0x00 0x00 0x02 0x13

@ 1 + 4:
0x00 0x00 0x02 0x13 0x00

@ 1 + 5:
0x00 0x00 0x02 0x13 0x02 0x00

@ 1 + 6:
0x00 0x00 0x02 0x13 0x02 0x00 0x02

@ 1 + 7:
0x00 0x00 0x02 0x13 0x02 0x00 0x02 0x02

And here are the signals on MISO line (red) and SCK (yellow) when reading 1 + 3 bytes in total. First packet is the second step. As expected, nothing is transmitted by the flash IC. Second packet is step no. 3. Peripheral is silent during the first byte and then outputs 0x02 on all following bytes, but only the third bytes is read successfully. And then it fails to read bytes 4 and 6 (all further bytes are read correctly).

enter image description here

So I don't know how to explain the fact that data being read doesn't fully correspond to what I'm seeing on the oscilloscope. An the weirdest thing is – there's a 0x13 byte being read during the third step! If first step is removed, this byte is being read as 0x00. Seems like a memory corruption on STM32 side, but I can't find a reason for it since buffers are sanitized with memset() and SPI's internal buffer is flushed with HAL_SPI_FlushRxFifo(&hspi1).

Here's the code:

uint8_t command;
uint8_t inBuffer[16];
uint8_t outBuffer[16];

/* Step 1: Check if IC is alive */
{
  memset(inBuffer, 0x00, 16);
  memset(outBuffer, 0x00, 16);
  command = OPCODE_RES;
  outBuffer[0] = command;

  spi_select(M25P80);
  HAL_SPI_TransmitReceive(&hspi1, outBuffer, inBuffer, (1 + 3) + 1, TIMEOUT);
  spi_deselect(M25P80);

  HAL_SPI_FlushRxFifo(&hspi1);
}

for (volatile uint32_t i = 0; i < 200; i++);

/* Step 2: Enable writing */
{
  memset(inBuffer, 0x00, 16);
  memset(outBuffer, 0x00, 16);
  command = OPCODE_WREN;
  outBuffer[0] = command;

  spi_select(M25P80);
  HAL_SPI_Transmit(&hspi1, outBuffer, 1, TIMEOUT);
  spi_deselect(M25P80);

  HAL_SPI_FlushRxFifo(&hspi1);
}

for (volatile uint32_t i = 0; i < 200; i++);

/* Step 3: Make sure writing is enabled */
{
  memset(inBuffer, 0x00, 16);
  memset(outBuffer, 0x00, 16);
  command = OPCODE_RDSR;
  outBuffer[0] = command;

  spi_select(M25P80);
  HAL_SPI_TransmitReceive(&hspi1, outBuffer, inBuffer, 1 + 1, TIMEOUT);
  spi_deselect(M25P80);

  HAL_SPI_FlushRxFifo(&hspi1);
}

Some details about STM's SPI API. In a call like:

HAL_SPI_TransmitReceive(&hspi1, outBuffer, inBuffer, (1 + 3) + 1, TIMEOUT);

&hspi1 - peripheral handle  containing its settings and configurations
outBuffer - pointer to a memory location with data to be sent
inBuffer - pointer to a memory location to which received data is written
(1 + 3) + 1 - number of bytes to receive/transmit; 
              in this case 1 - command byte, 3 - dummy bytes, 1 - received signature byte
TIMEOUT - a value in the range of 10000 which indicates at which point transmission must be 
aborted if failed to finish

EDIT 1:
SPI Initialisation code as generated by STM32CubeMx:

hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLED;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLED;
HAL_SPI_Init(&hspi1);

EDIT 2:
Now that I've lowered the speed down to 488 b/s, everything works fine. I'll stick with this hack for a while.

Best Answer

In my opinion you should use the Standard Peripheral Library for STM because is more stable and does not hide you the low level stuff which gives you the more controlable environment.Also you need to be careful at the CS pin timing, many devices require to hold that value a while until you start the transmission.