Why SPI with ESP-IDF works different than ESP-IDF Arduino port (AS5048A)?

Viewed 2248

I have connected AS5048A magnetic encoder to ESP32 dev board (VSPI/SPI3 bus: CS=5, CLK=18, MISO=19, MOSI=23). Using ESP IDF version 4.0.1. Arduino library: https://github.com/espressif/arduino-esp32/tree/idf-release/v4.0

I tested two versions of SPI transmission: a) with and b) without Arduino ESP-IDF library

Arduino version generally works very well with different speeds. Pure IDF struggles a lot to properly receive data. Weirdly, pure IDF version manages to send 0x0000 and 0xFFFF (read angle command with parity and r/w) in SPI mode 3 (Arduino works with mode 1) but other commands fail. Also 0x0000 (null command) returns some trash (0x6000 or something similar instead of 0x00000). So pure IDF version can read properly the angle (?!) with SPI mode 3, but anything else fails (I guess it's some accident since read angle command is 0xFFFF).

I tried every mode and a lot of timing configurations. Trying to look inside Arduino library SPI it uses a different approach to SPI than IDF itself.

Any help/idea? Below are codes.

Arduino implementation is pretty straightforward:

this->settings = SPISettings(3000000, MSBFIRST, SPI_MODE1);
pinMode(this->_cs, OUTPUT);
SPI.begin();

SPI.beginTransaction(this->settings);

digitalWrite(this->_cs, LOW);
uint16_t response = SPI.transfer16(command);
digitalWrite(this->_cs, HIGH);

Pure IDF follows:

spi_host_device_t spi_host = SPI3_HOST;
spi_device_handle_t spi;   
esp_err_t ret;

spi_bus_config_t buscfg;
memset(&buscfg, 0, sizeof(buscfg));
buscfg.mosi_io_num = pinMOSI;
buscfg.miso_io_num = pinMISO;
buscfg.sclk_io_num = pinCLK;
buscfg.quadwp_io_num = -1;
buscfg.quadhd_io_num = -1;
buscfg.max_transfer_sz = 1;
buscfg.flags = SPICOMMON_BUSFLAG_MASTER ; /*| SPICOMMON_BUSFLAG_IOMUX_PINS ;*/
buscfg.intr_flags = 0;

spi_device_interface_config_t devcfg;
memset(&devcfg, 0, sizeof(devcfg));
devcfg.command_bits = 0;
devcfg.address_bits = 0;
devcfg.dummy_bits = 0;
devcfg.mode = (uint8_t) 1; //SPI_MODE;
devcfg.duty_cycle_pos = 0;
devcfg.cs_ena_pretrans = 0;
devcfg.cs_ena_posttrans = (uint8_t) 0; //CS_ENA_POSTTRANS;
devcfg.clock_speed_hz = 300 * 1000; //SPI_HZ;
devcfg.input_delay_ns = 50; // INPUT_DELAY_NS;
devcfg.spics_io_num = pinCS;
devcfg.flags = SPI_DEVICE_NO_DUMMY ;
devcfg.queue_size = 1;
devcfg.pre_cb = 0;
devcfg.post_cb = 0;

ret = spi_bus_initialize(spi_host, &buscfg, 0); // No DMA 
ESP_ERROR_CHECK(ret);
ret =  spi_bus_add_device(spi_host, &devcfg, &spi);
ESP_ERROR_CHECK(ret);

ESP_LOGI(TAG, "AS5048 device initialized on SPI with MISO=%d, MOSI=%d, CLK=%d, CS=%d, HZ=%d", pinMISO, pinMOSI, pinCLK, pinCS, SPI_HZ);

and transfer (cmd is uint16_t with parity and r/w bits) :

esp_err_t ret;
spi_transaction_t t;

spi_device_acquire_bus(spi, portMAX_DELAY);

memset(&t, 0, sizeof(t));       
t.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA  ; 
*((uint16_t*)t.tx_data) = SPI_SWAP_DATA_TX(cmd, 16);
t.length = 16;                   

//ret =  spi_device_transmit(spi, &t);
ret =  spi_device_polling_transmit(spi, &t);
if(ret != ESP_OK)               
    ESP_LOGE(TAG, "spi_device_transmit() failed");

spi_device_release_bus(spi);
uint16_t response = SPI_SWAP_DATA_RX(*((uint16_t*)t.rx_data), 16);   
1 Answers

I just pulled up the datasheet on the component and noted that it states that the SPI WireMode is 1.

There is also a table that mentions some other items that I've included here. The Time between CSn falling edge and CLK rising edge is specified as 350ns and the minimum serial clock period is 100ns, so you'll need to wait up to 4 clocks if you're running at 10MHz. I don't think this is an issue at the speeds you're running, but wanted to note it.

Your configuration for Arduino shows you at 3MHz and the CS line being driven by software prior to the transmission, which likely handles your setup time, and your ESP-IDF code has you running Mode 3, but at 300KHz, which should give you well over the required 350ns setup time (docs show 1 spi clock delay after CS is driven, more on this later). The clock on Mode 3 is inverted from Mode 1, which means that the SPI will be attempting to read the data at the same time that the data lines are transitioning. My guess is that you're running into a bit of a logic race and it's "working" under certain circumstances because of the input_delay_ns parameter may be creating just enough delay to where the data state is changing just prior to the sample.

Using the wrong SPI timing mode can drive you mad because things almost work and you sometimes get the right results (also, running at too high of a speed for the device, or for your bus layout can cause you the same sort of pain). To quickly rehash, Modes are a matrix of clock polarity and phase. Modes 1 and 3 have inverted polarity from each other and sample on either the rising or the falling edge and when you use the wrong timing, you can be setting the value at the same time the clock is changing state.

I commend you for using ESP-IDF and getting away from Arduino, the "Visual Basic" of the embedded world. If you want to try and approach the 10MHz max speed of the device, you'll also need to work with the SPI ctrl2 register (0x14). According to the documentation, bits 0-3 (default value is 1) specifies the delay between the CS line being driven and the data transmission start.

Related