7 years, 6 months ago.

[NUCLEO Familly] Setting bit order in SPI

Here is a modification of SPI lib to be able to set bit order in SPI transfert :

It can be done by modifying few libs :

1- Creating the new prototype and little stuff around :

adding to SPI.h

...
class SPI {

public:

    /** Create a SPI master connected to the specified pins
     *
     *  mosi or miso can be specfied as NC if not used
     *
     *  @param mosi SPI Master Out, Slave In pin
     *  @param miso SPI Master In, Slave Out pin
     *  @param sclk SPI Clock pin
     *  @param ssel SPI chip select pin
     */
    SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel=NC);

    /** Configure the data transmission format
     *
     *  @param bits Number of bits per SPI frame (4 - 16)
     *  @param mode Clock polarity and phase mode (0 - 3)
     *
     * @code
     * mode | POL PHA
     * -----+--------
     *   0  |  0   0
     *   1  |  0   1
     *   2  |  1   0
     *   3  |  1   1
     * @endcode
     *  @param order Bit order (0 : MSB First - 1 : LSB First)
     */
    void format(int bits, int mode = 0, int order = 0);

...
protected:
...
    void aquire(void);
    static SPI *_owner;
    static SingletonPtr<PlatformMutex> _mutex;
    int _bits;
    int _mode;
    int _order;
    int _hz;
};

2- Then adding the new parameter defined in the header and modifying the acquire function in SPI.cpp like this :

adding to SPI.cpp

SPI::SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel) :
        _spi(),
#if DEVICE_SPI_ASYNCH
        _irq(this),
        _usage(DMA_USAGE_NEVER),
#endif
        _bits(8),
        _mode(0),
        _order(0),
        _hz(1000000) {
    // No lock needed in the constructor

    spi_init(&_spi, mosi, miso, sclk, ssel);
    aquire();
}

void SPI::format(int bits, int mode, int order) {
    lock();
    _bits = bits;
    _mode = mode;
    _order = order;
    SPI::_owner = NULL; // Not that elegant, but works. rmeyer
    aquire();
    unlock();
}

...

// ignore the fact there are multiple physical spis, and always update if it wasnt us last
void SPI::aquire() {
    lock();
     if (_owner != this) {
        spi_format(&_spi, _bits, _mode, _order, 0);
        spi_frequency(&_spi, _hz);
        _owner = this;
    }
    unlock();
}

3- As you do this, you will have to modify spi_format function prototype in spi_api.h in HAL directory to match the one used in SPI.cpp :

modifications of spi_api.h in HAL directory

/** Configure the SPI format
 *
 * Set the number of bits per frame, configure clock polarity and phase, shift order and master/slave mode.
 * The default bit order is MSB.
 * @param[in,out] obj   The SPI object to configure
 * @param[in]     bits  The number of bits per frame
 * @param[in]     mode  The SPI mode (clock polarity, phase)
 * @param[in]     order The SPI bit order (1)Little or (0)Big Endian - ie : shift direction
 * @param[in]     slave Zero for master mode or non-zero for slave mode
 */
void spi_format(spi_t *obj, int bits, int mode, int order, int slave);

4- And now modifying stm_api_spi.c in the TARGET_STM directory by adding the code :

adding what is missing...

	handle->Init.FirstBit = (order) ? SPI_FIRSTBIT_LSB : SPI_FIRSTBIT_MSB;

Like this :

modifications of stm_api_spi.c in TARGET_STM directory

void spi_format(spi_t *obj, int bits, int mode, int order, int slave)
{
    struct spi_s *spiobj = SPI_S(obj);
    SPI_HandleTypeDef *handle = &(spiobj->handle);

    DEBUG_PRINTF("spi_format, bits:%d, mode:%d, order:%d, slave?:%d\r\n", bits, mode, order, slave);

    // Save new values
    handle->Init.DataSize          = (bits == 16) ? SPI_DATASIZE_16BIT : SPI_DATASIZE_8BIT;

    switch (mode) {
        case 0:
            handle->Init.CLKPolarity = SPI_POLARITY_LOW;
            handle->Init.CLKPhase = SPI_PHASE_1EDGE;
            break;
        case 1:
            handle->Init.CLKPolarity = SPI_POLARITY_LOW;
            handle->Init.CLKPhase = SPI_PHASE_2EDGE;
            break;
        case 2:
            handle->Init.CLKPolarity = SPI_POLARITY_HIGH;
            handle->Init.CLKPhase = SPI_PHASE_1EDGE;
            break;
        default:
            handle->Init.CLKPolarity = SPI_POLARITY_HIGH;
            handle->Init.CLKPhase = SPI_PHASE_2EDGE;
            break;
    }

    if (handle->Init.NSS != SPI_NSS_SOFT) {
        handle->Init.NSS = (slave) ? SPI_NSS_HARD_INPUT : SPI_NSS_HARD_OUTPUT;
    }

    handle->Init.Mode = (slave) ? SPI_MODE_SLAVE : SPI_MODE_MASTER;

	handle->Init.FirstBit = (order) ? SPI_FIRSTBIT_LSB : SPI_FIRSTBIT_MSB;

    init_spi(obj);
}

Job done !

1 Answer

7 years, 6 months ago.

Hi,

Thanks for this improvement. It looks ok to me.

You should propose your work by sending a Pull-Request on GitHub: https://github.com/ARMmbed/mbed-os

Regards

Is this feature supported on all STM SPI engines? What about other mbed platforms. Changing the spi.format() parameters to allow bit order selection is a good feature but should preferably work on all platforms or you may run into portability issues for component or code libraries. Ideally the mbed lib should implement bit order swapping in software whenever this feature is not supported by a specific platform.

posted by Wim Huiskamp 13 Jun 2017

At least by adding it in the back of the argument list it won't break anything else.

But how often is this feature required? Since every target has features in their peripherals that are not exposed by mbed. For this specific one a single instruction can do the same in software (RBIT reverses bits in ARMs, I didn't actually test it, but it compiles on also M0s).

posted by Erik - 14 Jun 2017

What a weird question ? How often ? Well almost everytime you will use SPI that I beleive you don't do often...

And this feature (bit order) is not a specific fuction of ST, but obviously is included by all manufacturer ! Freescale and ST does it, I'm sure that NXP does it too (check page 418 LSBF bit of LPC1768 User Manual) and so does every others because it's a feature of SPI !

But I understand your reactions, therefore I won't add it to Mbed lib and add modifications to ST lib instead by adding a function !

By the way I've found a issue in Mbed SPI lib (in another post) but no answers ! Do you guys that are the holly keepers of the sacred library, have a look to this issue ?

posted by H A 16 Jun 2017