Electronic – SPI clocking in extra word – STM32F4

microcontrollernucleospistm32f4

I am attempting to interface with the TLE5012B (datasheet here) using SPI. The master in this case is a Nucleo F411RE, which uses an STM32F4 processor. The sensor communicates in 16-bit words, so I set the chip up in Cube as shown (some other peripherals which won't be used in this question's code are also activated):
CubeMS perpipheral configuration

All seemed to be going well, but a problem arose when I noticed that for some reason, an extra word was being clocked in by SCLK when there was nothing to be received from the data line, resulting in a rogue 0x0000 being stored in the buffer, which messes up future reads.

Here is my code:

#include "app_main.h"
#include "stm32f4xx_hal.h"
#include <stdint.h>

extern SPI_HandleTypeDef hspi2;


uint16_t command = 0x8088;
uint16_t safety = 0;
uint16_t data1[8];

int app_main()
{
    HAL_Delay(1000);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
    HAL_Delay(1);
    HAL_SPI_Transmit(&hspi2, (uint8_t *)(&command), 1, 0xFF);
    HAL_SPI_Receive(&hspi2, (uint8_t*)data1, 8, 0xFF);
    HAL_SPI_Receive(&hspi2, (uint8_t *)(&safety), 1, 0xFF);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
    HAL_Delay(1);

    while (1) {
    }
    return 0;
}

And here is what happens in the SPI, read using a logic analyzer (Lines in descending order are CS, MOSI/DATA, SCLK, CS does drop low/set high outside the zoomed area):

Output 1 zoomed

As can be seen from the code, I should expect to send one word and receive 8 plus a safety word; however, 10 words are being clocked in, with the last being 0x0000 due to no data on the line, which then stays low for some time.

To get a better idea of what was going on, I altered my code slightly:

#include "app_main.h"
#include "stm32f4xx_hal.h"
#include <stdint.h>

extern SPI_HandleTypeDef hspi2;


uint16_t command = 0x8088;
uint16_t safety = 0;
uint16_t data1[8];

int app_main()
{
    HAL_Delay(1000);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
    HAL_Delay(105);
    HAL_SPI_Transmit(&hspi2, (uint8_t *)(&command), 1, 0xFF);
    HAL_Delay(75);
    for(int i = 0; i < 8; i++)
    {
        HAL_Delay(15);
        HAL_SPI_Receive(&hspi2, (uint8_t*)&data1[i*2], 1, 0xFF);
    }
    HAL_Delay(50);
    HAL_SPI_Receive(&hspi2, (uint8_t *)(&safety), 1, 0xFF);
    HAL_Delay(10);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);

    while (1) {
    }
    return 0;
}

This produced the following output:

enter image description here

From a distance, this seems correct; 8 words received back, plus the safety. On zooming in, however, the problem becomes visible; the command transmission is fine:

enter image description here

However, the first received word is not one word but two clocked in at once by SCLK:

enter image description here

While following words are fine:

enter image description here

What this double word appears to be doing is shoving the receive buffer ahead of what's actually being read in, meaning when the safety word is meant to be read, 0x0000 is added into the buffer, as the safety word has already been received and stored.

enter image description here

It could be worth noting that some of the delays are slightly smaller/larger than they're meant to be, but my question is, what could be causing this double-word to be read in? I tried changing the SPI peripheral to 8-bit mode instead of 16; this didn't fix the issue, it merely made the extra word 8 bits instead of 16. Running similar code on an F3 processor gave incorrect results as well. The TLE can't drive the SCLK line so it must be the micro that's doing something wrong, but I can't for the life of me figure out what.

EDIT: I did some fiddling with wires and set the STM into full-duplex with a resistor on the MOSI line. This fixed the problem, but doesn't tell me what caused it in the first place. Does half-duplex have some strange protocol I don't know about?

Best Answer

the basic deal with half-duplex mode is that it's going to clock until the SPE flag in SPI_CR1 is unset. The HAL_SPI_Receive doesn't unset this until its received a byte. By the time it gets around to unsetting this flag, the next transfer has already started.

To properly receive the number of bytes you want, using half-duplex mode, follow the procedure in 20.3.8 of the reference manual. The basic outline is:

  1. Wait for the second to last occurrence of RXNE=1 (n-1) (end of data1)
  2. Wait for one SPI clock cycle and then disable SPE (SPE=0) (at beginning of safety byte rx.)
  3. Then wait for the last RXNE=1 and your last byte is waiting for you in the register. (grab the safety byte).

On a more general note, HALs provided by the manufacturers are pretty good for getting you started, but they generally do not cover all the possible cases. It is better to use it as a starting off point or suggestion especially if it doesn't do exactly what you want it to do.