SPI HAL blocking transfer function unable to set write_fill to 9 or more bits

In the spi_api.h header, the master transfer cannot support a default fill value that holds a value larger than an 8 bit value because it is a char.

This can lead to issues where a value beyond 8 bits will not be sent correctly as a fill character.

This is possibly not an issue if you always send the same number of words as received (or you go to that trouble yourself) but it does lead to an inconsistency with how the drivers are called from the C++ level down to the vendor code.

Here is the function declaration (found here):

int spi_master_block_write(spi_t *obj, const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length, char write_fill);

As you can see, there is no way to send out a larger value.

To be fair, I do not know if this is a true problem. I ran into this when working on a PR to add support for SPI words that are not modulo-8 in size to the ST drivers. As far as I can tell, only the first 8 bits would be sent in how this operates if you send out a 9 or more bit number.

My solution, if this would actually not break things, would be to change the type of the write_fill parameter to a uint32_t. I believe this is possible without being a breaking change since write_fill is the last in the parameter list. This would allow the vendor implementation to truncate the default fill parameter as appropriate.

Hello Wiliam,

The number of SPI bits is hardware dependent. On the majority of STM32 microcontrollers (maybe on all) the SPI frame is configurable to 8 bits or 16 bits only by calling the

void SPI::format(int bits, int mode);

function.

However, on LPC1768 it can be set to any number from 8-bits to 16 -bits.

int spi_master_block_write(spi_t *obj, const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length, char write_fill);

As you can see, there is no way to send out a larger value.

It isn’t obvious but actually the function above enables to send also values that are larger than 8-bits. To achieve that the pointer pointing to the given variable shall be cast to char* and the variable’s length shall be passed as number of bytes.

For example in case of LPC1768:

int spi_master_block_write(spi_t *obj, const char *tx_buffer, int tx_length,
                           char *rx_buffer, int rx_length, char write_fill) {
    int total = (tx_length > rx_length) ? tx_length : rx_length;

    for (int i = 0; i < total; i++) {
        char out = (i < tx_length) ? tx_buffer[i] : write_fill;
        char in = spi_master_write(obj, out);
        if (i < rx_length) {
            rx_buffer[i] = in;
        }
    }

    return total;
}

Which in turn calls spi_master_write(obj, out) defined as:

int spi_master_write(spi_t *obj, int value) {
    ssp_write(obj, value);
    return ssp_read(obj);
}

The ssp_write function is then defined as:

static inline void ssp_write(spi_t *obj, int value) {
    while (!ssp_writeable(obj));
    obj->spi->DR = value;
}

When there has been passed sufficient amount of bits to the SPI controller it transmits them on the bus.

Best regards, Zoltan

Hi Zoltan,

Thanks for your response.

However, I am not sure I communicated the issue well enough.

First, while some ST devices are only capable of 8 or 16 bit SPI transfers, a large number are capable of sending out between 4 and 16 bit values. However, the ST implementation forces all to be 8 or 16. This is probably due to historic reasons when there were only 8 or 16 bit transfers available in the SPI peripherals that ST using mbed supported.

I have a PR that I submitted which looks to me like it will go through eventually. It will expand the options available to the ST user to fit the device family. I am currently waiting on a second ST reviewer and possibly an ARM person to review it again, but the latest was tested by @jeromecoutant with success. You can find it here: Add SPI bitwidths to ST targets where supported by pea-pod · Pull Request #14020 · ARMmbed/mbed-os (github.com).

The other issue here is not that if you want a RX only transmission, you only are allowed to send an 8 bit value out at the most as a fill word. The last parameter in the HAL function is char write_fill (note the lack of pointer asterisk). It is used whenever you issue a transfer where the received data is larger than the sent data. The char write_fill in that function declaration isn’t a pointer, it’s a value, and furthermore, it is an 8 bit value.

I am not sure if this is a bug strictly speaking, but it is a limitation. I posted it here because I was not sure if it really qualified as an “issue” under the template guidelines.