Description
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_t
s. 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.