Skip to content

STM32F4 SPI 3-wire mode doesn't work correctly #14774

@vznncv

Description

@vznncv

Description of defect

Method int SPI::write(const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length) doesn't work correctly with SPI 3-wire mode.

Actual behavior:

First byte of the rx_buffer after int SPI::write(const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length) method invocation contains last byte of the previous transaction, when SPI is configured in the 3-wire mode. Actual bytes are shifted to next positions. The last byte are "moved" to the next read transaction.

Expected behavior:

rx_buffer contains bytes only from current transaction.

Target(s) affected by this defect ?

STM32F411CEU6 (custom board https://github.com/vznncv/TARGET_BLACKPILL_F411CE)

(probably all STM32F4 boards)

Toolchain(s) (name and version) displaying this defect ?

arm-none-eabi-gcc (GNU Arm Embedded Toolchain 9-2020-q2-update) 9.3.1 20200408 (release)

What version of Mbed-os are you using (tag or sha) ?

mbed-os-6.11.0

What version(s) of tools are you using. List all that apply (E.g. mbed-cli)

mbed-cli v1.10.5

How is this defect reproduced ?

Example of BMX160 sensor usage in 3-wire mode with STM32F411CEU6:

/**
 * SPI 3 Wire demo with BMX160 sensor.
 *
 * sensor: https://wiki.dfrobot.com/BMX160_9_Axis_Sensor_Module_SKU_SEN0373
 * board: https://github.com/vznncv/TARGET_BLACKPILL_F411CE/tree/v0.1.4
 * mbed-os version: 6.11.0
 */

#include "mbed.h"

#include "stm32f4xx_hal.h"

//
// Hardware pins
//
#define BMX160_SDX PB_15 // SPI MOSI pin (STM32 SPI2 instance)
#define BMX160_SCX PB_13 // SPI CLK pin (STM32 SPI2 instance)
#define BMX160_CSB PA_8 // SPI SSEL pin (STM32 SPI2 instance)

// enable/disable SPI 3 wire rx buffer cleanup
#define ENABLE_SPI_3WIRE_FIX 0

// helper macro to check return code
static char app_error_msg_buf[256];
#define CHECK_RET_CODE(expr) do {                                                                         \
    int err = expr;                                                                                       \
    if (err != 0) {                                                                                       \
        snprintf(app_error_msg_buf, 256, "expression \"%s\" has returned error code %i", #expr, err);     \
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, MBED_ERROR_CODE_UNKNOWN), app_error_msg_buf); \
    }                                                                                                     \
} while(0);

//
// hardware interfaces
//
DigitalOut user_led(LED1, 1);
SPI bmx_spi(BMX160_SDX, NC, BMX160_SCX);
DigitalOut bmx_ssel(BMX160_CSB, NC);

/**
 * Write to BMX160 register.
 *
 * @param reg register address
 * @param value register value
 * @return 0 on success, otherwise non-zero value
 */
int bmx_register_write(uint8_t reg, uint8_t value)
{
    bmx_ssel.write(0);

    uint8_t out_data[2] = {reg, value};
    bmx_spi.write((char *)out_data, 2, nullptr, 1);

    bmx_ssel.write(1);

    // log results
    printf("| write to  bmx160 register 0x%02X | -> 0x%02X |\n", reg, value);

    return 0;
}

/**
 * Read from BMX160 register.
 *
 * @param reg register address
 * @param value register value
 * @return 0 on success, otherwise non-zero value
 */
int bmx_register_read(uint8_t reg, uint8_t *value)
{
#if ENABLE_SPI_3WIRE_FIX
    // cleanup SPI input buffer explicitly
    SPI_TypeDef *spi = (SPI_TypeDef *)SPI2_BASE;
    volatile uint32_t data;
    if (spi->SR & SPI_SR_RXNE) {
        printf("| 1. SPI RX buffer isn't empty. Clear it   |\n");
        data = spi->DR;
    }
    if (spi->SR & SPI_SR_RXNE) {
        printf("| 2. SPI RX buffer isn't empty. Clear it   |\n");
        data = spi->DR;
    }
    UNUSED(data);
#endif

    bmx_ssel.write(0);

    uint8_t out_data[1] = {(uint8_t)(reg | 0x80)};
    uint8_t in_data[1];
    bmx_spi.write((char *)out_data, 1, (char *)in_data, 1);
    *value = in_data[0];

    bmx_ssel.write(1);

    // log results
    printf("| read from bmx160 register 0x%02X | <- 0x%02X |\n", reg, *value);

    return 0;
}

int main()
{
    uint8_t reg_value;

    // initialize drivers
    printf("====== Init ======\n");
    // configure spi mode
    bmx_spi.format(8, 3);

    // startup delay for BMX160 sensor
    ThisThread::sleep_for(100ms);

    // create rising edge on the SSEL pin to switch BMX160 to SPI mode
    bmx_ssel.write(0);
    ThisThread::sleep_for(1ms);
    bmx_ssel.write(1);
    ThisThread::sleep_for(1ms);

    // write to IF_CONF register to active 3-wire SPI interface
    CHECK_RET_CODE(bmx_register_write(0x6B, 0x01));

    // read accelerometer X axis offset register value (default value must be 0)
    for (int i = 0; i < 3; i++) {
        CHECK_RET_CODE(bmx_register_read(0x71, &reg_value));
    }
    // read IF_CONF register. It should contain 0x01, that we have set before
    for (int i = 0; i < 3; i++) {
        CHECK_RET_CODE(bmx_register_read(0x6B, &reg_value));
    }
    // read CHIP_ID register. It should contain D8.
    for (int i = 0; i < 3; i++) {
        CHECK_RET_CODE(bmx_register_read(0x00, &reg_value));
    }
    // read accelerometer X axis offset register value (default value must be 0)
    for (int i = 0; i < 3; i++) {
        CHECK_RET_CODE(bmx_register_read(0x71, &reg_value));
    }


    printf("====== Blink demo ======\n");
    while (true) {
        ThisThread::sleep_for(500ms);
        user_led = !user_led;
    }

    return 0;
}

Output (first read register attempt returns incorrect value):

====== Init ======
| write to  bmx160 register 0x6B | -> 0x01 |
| read from bmx160 register 0x71 | <- 0x00 |
| read from bmx160 register 0x71 | <- 0x00 |
| read from bmx160 register 0x71 | <- 0x00 |
| read from bmx160 register 0x6B | <- 0x00 |
| read from bmx160 register 0x6B | <- 0x01 |
| read from bmx160 register 0x6B | <- 0x01 |
| read from bmx160 register 0x00 | <- 0x01 |
| read from bmx160 register 0x00 | <- 0xD8 |
| read from bmx160 register 0x00 | <- 0xD8 |
| read from bmx160 register 0x71 | <- 0xD8 |
| read from bmx160 register 0x71 | <- 0x00 |
| read from bmx160 register 0x71 | <- 0x00 |
====== Blink demo ======

Example of BMX160 sensor usage in 3-wire mode with STM32F411CEU6 with explicit RX buffer cleanup of SPI2 that fixes problem:

/**
 * SPI 3 Wire demo with BMX160 sensor.
 *
 * sensor: https://wiki.dfrobot.com/BMX160_9_Axis_Sensor_Module_SKU_SEN0373
 * board: https://github.com/vznncv/TARGET_BLACKPILL_F411CE/tree/v0.1.4
 * mbed-os version: 6.11.0
 */

#include "mbed.h"

#include "stm32f4xx_hal.h"

//
// Hardware pins
//
#define BMX160_SDX PB_15 // SPI MOSI pin (STM32 SPI2 instance)
#define BMX160_SCX PB_13 // SPI CLK pin (STM32 SPI2 instance)
#define BMX160_CSB PA_8 // SPI SSEL pin (STM32 SPI2 instance)

// enable/disable SPI 3 wire rx buffer cleanup
#define ENABLE_SPI_3WIRE_FIX 1

// helper macro to check return code
static char app_error_msg_buf[256];
#define CHECK_RET_CODE(expr) do {                                                                         \
    int err = expr;                                                                                       \
    if (err != 0) {                                                                                       \
        snprintf(app_error_msg_buf, 256, "expression \"%s\" has returned error code %i", #expr, err);     \
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, MBED_ERROR_CODE_UNKNOWN), app_error_msg_buf); \
    }                                                                                                     \
} while(0);

//
// hardware interfaces
//
DigitalOut user_led(LED1, 1);
SPI bmx_spi(BMX160_SDX, NC, BMX160_SCX);
DigitalOut bmx_ssel(BMX160_CSB, NC);

/**
 * Write to BMX160 register.
 *
 * @param reg register address
 * @param value register value
 * @return 0 on success, otherwise non-zero value
 */
int bmx_register_write(uint8_t reg, uint8_t value)
{
    bmx_ssel.write(0);

    uint8_t out_data[2] = {reg, value};
    bmx_spi.write((char *)out_data, 2, nullptr, 1);

    bmx_ssel.write(1);

    // log results
    printf("| write to  bmx160 register 0x%02X | -> 0x%02X |\n", reg, value);

    return 0;
}

/**
 * Read from BMX160 register.
 *
 * @param reg register address
 * @param value register value
 * @return 0 on success, otherwise non-zero value
 */
int bmx_register_read(uint8_t reg, uint8_t *value)
{
#if ENABLE_SPI_3WIRE_FIX
    // cleanup SPI input buffer explicitly
    SPI_TypeDef *spi = (SPI_TypeDef *)SPI2_BASE;
    volatile uint32_t data;
    if (spi->SR & SPI_SR_RXNE) {
        printf("| 1. SPI RX buffer isn't empty. Clear it   |\n");
        data = spi->DR;
    }
    if (spi->SR & SPI_SR_RXNE) {
        printf("| 2. SPI RX buffer isn't empty. Clear it   |\n");
        data = spi->DR;
    }
    UNUSED(data);
#endif

    bmx_ssel.write(0);

    uint8_t out_data[1] = {(uint8_t)(reg | 0x80)};
    uint8_t in_data[1];
    bmx_spi.write((char *)out_data, 1, (char *)in_data, 1);
    *value = in_data[0];

    bmx_ssel.write(1);

    // log results
    printf("| read from bmx160 register 0x%02X | <- 0x%02X |\n", reg, *value);

    return 0;
}

int main()
{
    uint8_t reg_value;

    // initialize drivers
    printf("====== Init ======\n");
    // configure spi mode
    bmx_spi.format(8, 3);

    // startup delay for BMX160 sensor
    ThisThread::sleep_for(100ms);

    // create rising edge on the SSEL pin to switch BMX160 to SPI mode
    bmx_ssel.write(0);
    ThisThread::sleep_for(1ms);
    bmx_ssel.write(1);
    ThisThread::sleep_for(1ms);

    // write to IF_CONF register to active 3-wire SPI interface
    CHECK_RET_CODE(bmx_register_write(0x6B, 0x01));

    // read accelerometer X axis offset register value (default value must be 0)
    for (int i = 0; i < 3; i++) {
        CHECK_RET_CODE(bmx_register_read(0x71, &reg_value));
    }
    // read IF_CONF register. It should contain 0x01, that we have set before
    for (int i = 0; i < 3; i++) {
        CHECK_RET_CODE(bmx_register_read(0x6B, &reg_value));
    }
    // read CHIP_ID register. It should contain D8.
    for (int i = 0; i < 3; i++) {
        CHECK_RET_CODE(bmx_register_read(0x00, &reg_value));
    }
    // read accelerometer X axis offset register value (default value must be 0)
    for (int i = 0; i < 3; i++) {
        CHECK_RET_CODE(bmx_register_read(0x71, &reg_value));
    }


    printf("====== Blink demo ======\n");
    while (true) {
        ThisThread::sleep_for(500ms);
        user_led = !user_led;
    }

    return 0;
}

Output (all register values are corrected):

====== Init ======
| write to  bmx160 register 0x6B | -> 0x01 |
| read from bmx160 register 0x71 | <- 0x00 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x71 | <- 0x00 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x71 | <- 0x00 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x6B | <- 0x01 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x6B | <- 0x01 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x6B | <- 0x01 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x00 | <- 0xD8 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x00 | <- 0xD8 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x00 | <- 0xD8 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x71 | <- 0x00 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x71 | <- 0x00 |
| 1. SPI RX buffer isn't empty. Clear it   |
| read from bmx160 register 0x71 | <- 0x00 |
====== Blink demo ======

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions