Skip to content

Improve SPI interface (GIT8266O-397) #723

Open
@zub2

Description

@zub2

Environment

  • Development Kit: esp8266ex
  • IDF version: d6ec931
  • Development Env: Make
  • Operating System: Linux
  • Power Supply: external 3.3V

Problem Description

There are several issues that make the SPI driver difficult to use:

spi_trans (drivers/spi.c) requires 32bit-aligned buffers for miso and mosi data

This is not a big deal when the buffer is allocated for SPI, but when a longer buffer is sent piecewise, the necessary alignment becomes a burden. Consider the following scenario (SPI master):

uint16_t cmd = SOME_CMD;
uint32_t addr = SOME_ADDR;

spi_trans_t t = {};
t.cmd = &cmd;
t.bits.cmd = 16;
t.addr = &addr;
t.bits.addr = 32;

unsigned size;
uint8_t * data = getData(&size);
while (size > 0)
{
    waitForDeviceReady();
    const unsigned transferSize = min(size, getDeviceFreeBufferSize());

    t.mosi = (uint32_t*)data;
    t.bits.mosi = transferSize * 8;
    spi_trans(HSPI_HOST, t);

    data += transferSize;
    size -= transferSize;
}

... unless each time a multiple of 4 bytes is transferred, this explodes due to wrong alignment in spi_master_trans(). So the only solution currently is for user code to keep copying the data to a properly aligned buffer before it's passed to spi_trans(). So it's 1 copy before spi_trans() is called and another copy of the same data inside spi_master_trans().

The same issue happens with miso data.

Furthermore, making the type uint32_t* makes life difficult in all situations when the user doesn't want to transfer an array of uint32_ts. An uint8_t* or just void* would be more obvious. There is no data structure on this level - it's just bytes, or in fact just bits, as bits.mosi is actually the number of bits to transfer. So why force multiples of 4 bytes while the actual transfer size is bits?

I see that the actual SPI data buffer is defined as uint32_t (in spi_struct.h). I was not successful in finding any details on how should the HW buffer be accessed. But looking at how esp-open-rtos handles this (esp_spi.c in esp-open-rtos), it seems that a simple memcpy() (from a source address w/o any necessary alignment) is sufficient. It just seems that the last write has to make sure it ends on a multiple of 4 bytes. Alternatively, even if the SPI data buffer had to be accessed as uint32_t, it could still be handled inside spi_master_trans().

The size of spi_trans_t is 20 bytes yet spi_trans() accepts it by value

This means it's copied to stack. And then spi_trans() calls spi_trans_static() which calls one of spi_master_trans() or spi_slave_trans(). If the calls are not inlined, that's 3 unneeded copies of spi_trans_t. I think spi_trans(), spi_trans_static(), spi_master_trans() and spi_slave_trans() should accept a pointer to the spi_trans_t to spare stack space.

Making spi_trans_t cmd and addr pointers makes the usage for SPI master more difficult

When cmd or addr are an expression, a local variable has to be created. I understand why these were made pointers - for SPI slave. But why not just store the received values in the SPI slave scenario inside the spi_trans_t and let the caller then take the values and process them as needed? Of course this would only work if the spi_trans_t is passed via pointer, so that spi_slave_trans() can modify these.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions