-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
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, ®_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, ®_value));
}
// read CHIP_ID register. It should contain D8.
for (int i = 0; i < 3; i++) {
CHECK_RET_CODE(bmx_register_read(0x00, ®_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, ®_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, ®_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, ®_value));
}
// read CHIP_ID register. It should contain D8.
for (int i = 0; i < 3; i++) {
CHECK_RET_CODE(bmx_register_read(0x00, ®_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, ®_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 ======