This is the multi-page printable view of this section. Click here to print...

Return to the regular view of this page

As of 2025-07-24

SPI

Performs reading and writing on the SPI bus (controller side).
Performs reading and writing on the SPI bus (controller side).

Notes

Constants

ConstantMeaning
const uint8_t
SPI_CONF::MSBFIRST
Set MSB as the first bit
const uint8_t
SPI_CONF::LSBFIRST
Set LSB as the first bit
const uint8_t
SPI_CONF::SPI_MODE0
Set to SPI MODE 0
const uint8_t
SPI_CONF::SPI_MODE1
Set to SPI MODE 1
const uint8_t
SPI_CONF::SPI_MODE2
Set to SPI MODE 2
const uint8_t
SPI_CONF::SPI_MODE3
Set to SPI MODE 3

Initialization and Termination

The procedure to use the SPI bus is via the begin() method.

begin()

void begin(uint8_t slave_select, SPISettings settings)
SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode)

Initializes the hardware.

ParameterDescription
slave_selectSpecifies the select pin of the target peripheral.
0 : DIO19``1 : DIO0 (DIO19 is reserved)``2 : DIO1 (DIO0,19 are reserved)
settingsSpecifies the SPI bus settings.

clock [Hz] specifies the SPI bus frequency. A divisor close to the specified frequency is selected. The value will be 16MHz or an even division of 16MHz.
bitOrder is either SPI_CONF::MSBFIRST or SPI_CONF::LSBFIRST.
dataMode is one of SPI_CONF::SPIMODE0..3.

Example

void setup() {
  ...
  SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
  ...
}

void wakeip() {
  ...
  SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
  ...
}

end()

void end()

Terminates the use of SPI hardware.

Reading and Writing

There are two types of procedures for reading and writing. Use either of them as needed.

Example

The following sample code reads the temperature every second from the Analog Devices temperature sensor ADT7310 and outputs it to the serial port.

#include <TWELITE>
#include <SM_SIMPLE>

enum class STATE : uint8_t {
    INTERACTIVE = 255,
    INIT = 0,
    INIT_WAIT,
    SENSOR,
    LOOP_WAIT
};
SM_SIMPLE<STATE> step;

struct SensorData {
    uint8_t highByte;
    uint8_t lowByte;
    uint16_t rawValue;
    int32_t tempValue16th;
    div_result_i32 temperature;
} sensorData;

void setup() {
    step.setup(); // Initialize the state machine
}

void loop() {
    do {
        switch (step.state()) {
        case STATE::INIT: // Initial state
            SPI.begin(0 /* Use DIO19 as chip select */
                      , { 400000UL /* Clock frequency */
                    , SPI_CONF::MSBFIRST
                    , SPI_CONF::SPI_MODE3
            }
                      );

            // Software reset
            SPI.beginTransaction();
            for (int i = 0; i < 4; i++) {
                SPI.transfer(0xFF);
            }
            SPI.endTransaction();

            // Start Continuous Read mode
            SPI.beginTransaction();
            SPI.transfer(0x54);
            SPI.endTransaction();

            step.set_timeout(300); // Set wait time
            step.next(STATE::INIT_WAIT);
            break;

        case STATE::INIT_WAIT: // Wait
            if (step.is_timeout()) {
                step.next(STATE::SENSOR);
            }
            break;

        case STATE::SENSOR: // Read sensor data
            SPI.beginTransaction();
            sensorData.highByte = SPI.transfer(0x00);  // Send dummy data to generate clock signal
            sensorData.lowByte = SPI.transfer(0x00);   // Send dummy data to generate clock signal
            SPI.endTransaction();

            sensorData.rawValue = (((uint16_t)sensorData.highByte << 8) | sensorData.lowByte) >> 3;
            if (sensorData.rawValue & 0x1000) {
                sensorData.tempValue16th = int32_t(sensorData.rawValue) - 0x2000;
            } else {
                sensorData.tempValue16th = sensorData.rawValue;
            }

            // Calculate temperature using div100()
            sensorData.temperature = div100((sensorData.tempValue16th * 100) / 16);

            // Output result to serial
            Serial << crlf << sensorData.temperature.format() << "°C";

            step.set_timeout(1000); // Wait until next capture
            step.next(STATE::LOOP_WAIT);
            break;

        case STATE::LOOP_WAIT: // Wait
            if (step.is_timeout()) {
                step.next(STATE::SENSOR);
            }
            break;

        default:
            break;
        }
    } while (step.b_more_loop());
}

Here, the member function interface is used.

1 - SPI (Member Function Version)

SPI (Method-based usage)
After initializing the hardware with the begin() method, use beginTransaction() to enable bus communication. When beginTransaction() is executed, the SPI select pin is asserted. Use the transfer() function for reading and writing. SPI performs read and write operations simultaneously.

beginTransaction()

void beginTransaction()
void beginTransaction(SPISettings settings)

Starts using the SPI bus. Asserts the SPI select pin.

When called with the settings parameter, it also configures the bus settings.

endTransaction()

void endTransaction()

Ends the use of the SPI bus. Deasserts the SPI select pin.

transfer(), transfer16(), transfer32()

inline uint8_t transfer(uint8_t data)
inline uint16_t transfer16(uint16_t data)
inline uint32_t transfer32(uint32_t data)

Performs read/write operations on the SPI bus. transfer() transfers 8 bits, transfer16() transfers 16 bits, and transfer32() transfers 32 bits.

2 - SPI (Helper Class Version)

SPI (How to use with helper class)
The helper class version is a more abstract implementation. By creating a read/write object transceiver, you start using the bus, and by destroying the object, you end the use of the bus.

By creating the object inside the conditional expression of an if statement, the lifetime of the object is limited to the scope within the if block, and when exiting the if block, the object is destroyed and the bus release procedure is performed at that point.

uint8_t c;
if (auto&& trs = SPI.get_rwer()) { // Create object and check device communication
   // The scope (curly braces) here is the lifetime of trs.
   trs << 0x00; // Write 0x00 using mwx::stream interface
   trs >> c;    // Store the read data into c.
}
// When exiting the if block, trs is destroyed and the bus usage ends

Also, since the read/write object implements the mwx::stream interface, operators like << can be used.

  • Matching the bus usage start and end with the object’s lifetime improves code readability and prevents omission of release procedures.
  • Unifies read/write procedures using the mwx::stream interface.

Read/Write

A reading method using a helper class that performs read operations and their termination procedures within the scope if() { ... }.

inline uint8_t _spi_single_op(uint8_t cmd, uint8_t arg) {
    uint8_t d0, d1;
    if (auto&& x = SPI.get_rwer()) {
        d0 = x.transfer(cmd); (void)d0;
        d1 = x.transfer(arg);
        // (x << (cmd)) >> d0;
        // (x << (arg)) >> d1;
    }

    return d1;
}

Above, the x object created by the get_rwer() method is used to perform byte-wise read/write.

  1. The x object is created inside the if(...). At the same time, the SPI bus select pin is set. (The type is resolved by universal reference auto&& with type inference.)
  2. The created x object defines operator bool () which is used for evaluation in the conditional expression. For SPI bus, it always evaluates to true.
  3. The x object defines the method uint8_t transfer(uint8_t), which performs an 8-bit read/write transfer to SPI when called.
  4. At the end of the if() { ... } scope, the destructor of x is called, releasing the SPI bus select pin.

get_rwer()

periph_spi::transceiver get_rwer()

Obtains a worker object used for SPI bus read/write.

Worker Object

transfer(), transfer16(), transfer32()

uint8_t transfer(uint8_t val)
uint16_t transfer16(uint16_t val)
uint32_t transfer32(uint32_t val)

Perform transfers of 8-bit, 16-bit, and 32-bit respectively, and return the read result with the same data width as the written data.

<< operator

operator << (int c)
operator << (uint8_t c)
operator << (uint16_t c)
operator << (uint32_t c)

int and uint8_t types perform 8-bit transfers.

uint16_t and uint32_t types perform 16-bit and 32-bit transfers respectively.

Transfer results are stored in an internal FIFO queue of up to 16 bytes and read out by the >> operator. Since the buffer is not large, it is assumed to be read out each time after transfer.

>> operator

operator >> (uint8_t& c)
operator >> (uint16_t& c)
operator >> (uint32_t& c)

null_stream(size_t i = 1)
operator >> (null_stream&& p)

Specify a variable with the same data width as the previous transfer.

If the read result is not needed, use the null_stream() object. It skips the number of bytes specified by i.