idfxx 1.0.0
Modern C++23 components for ESP-IDF
Loading...
Searching...
No Matches
master.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Chris Leishman
3
4#pragma once
5
22#include <idfxx/cpu>
23#include <idfxx/error>
24#include <idfxx/flags>
25#include <idfxx/future>
26#include <idfxx/gpio>
27#include <idfxx/intr_alloc>
28
29#include <atomic>
30#include <chrono>
31#include <cstdint>
32#include <driver/spi_master.h>
33#include <esp_idf_version.h>
34#include <frequency/frequency>
35#include <memory>
36#include <optional>
37#include <span>
38#include <string>
39#include <vector>
40
45namespace idfxx::spi {
46
51enum class host_device : int {
52 spi1 = SPI1_HOST,
53 spi2 = SPI2_HOST,
54#if SOC_SPI_PERIPH_NUM > 2
55 spi3 = SPI3_HOST,
56#endif
57};
58
63enum class dma_chan : int {
65#if CONFIG_IDF_TARGET_ESP32
66 ch_1 = SPI_DMA_CH1,
67 ch_2 = SPI_DMA_CH2,
68#endif
70};
71
100
120
145
146} // namespace idfxx::spi
147
148template<>
150
151template<>
153
154template<>
156
157namespace idfxx::spi {
158
179 std::span<const uint8_t> tx_buffer = {};
180 std::span<uint8_t> rx_buffer = {};
181 size_t length = 0;
182 size_t rx_length = 0;
183 freq::hertz override_freq{0};
187};
188
189class master_device;
190
246
255public:
256#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
267 [[nodiscard]] explicit master_bus(enum host_device host, enum dma_chan dma_chan, struct bus_config config);
268#endif
269
280 make(enum host_device host, enum dma_chan dma_chan, struct bus_config config);
281
283
284 master_bus(const master_bus&) = delete;
285 master_bus& operator=(const master_bus&) = delete;
286
288 master_bus(master_bus&& other) noexcept;
289
291 master_bus& operator=(master_bus&& other) noexcept;
292
294 [[nodiscard]] enum host_device host() const { return _host; }
295
305
306private:
307 explicit master_bus(enum host_device host);
308
309 enum host_device _host;
310 bool _initialized = true;
311};
312
333public:
361
362#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
383 [[nodiscard]] explicit master_device(master_bus& bus, const struct config& config);
384#endif
385
406
408
409 master_device(const master_device&) = delete;
411 master_device(master_device&& other) noexcept;
413
414 // =========================================================================
415 // Accessors
416 // =========================================================================
417
422 [[nodiscard]] master_bus& bus() const {
423 assert(_bus != nullptr);
424 return *_bus;
425 }
426
428 [[nodiscard]] spi_device_handle_t idf_handle() const { return _handle; }
429
439 [[nodiscard]] freq::hertz frequency() const;
440
441 // =========================================================================
442 // Simple API (byte-oriented, blocking)
443 // =========================================================================
444
445#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
459 void transmit(std::span<const uint8_t> tx_data) { unwrap(try_transmit(tx_data)); }
460
469 void receive(std::span<uint8_t> rx_data) { unwrap(try_receive(rx_data)); }
470
485 [[nodiscard]] std::vector<uint8_t> receive(size_t size) { return unwrap(try_receive(size)); }
486
502 void transfer(std::span<const uint8_t> tx_data, std::span<uint8_t> rx_data) {
504 }
505#endif
506
514 [[nodiscard]] result<void> try_transmit(std::span<const uint8_t> tx_data);
515
523 [[nodiscard]] result<void> try_receive(std::span<uint8_t> rx_data);
524
533
542 [[nodiscard]] result<void> try_transfer(std::span<const uint8_t> tx_data, std::span<uint8_t> rx_data);
543
544 // =========================================================================
545 // Full transaction API (blocking)
546 // =========================================================================
547
548#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
558#endif
559
568
569 // =========================================================================
570 // Polling API (busy-wait; NOT thread-safe — lock() the device first)
571 // =========================================================================
572
573#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
583 void polling_transmit(std::span<const uint8_t> tx_data) { unwrap(try_polling_transmit(tx_data)); }
584
595
607 [[nodiscard]] std::vector<uint8_t> polling_receive(size_t size) { return unwrap(try_polling_receive(size)); }
608
619 void polling_transfer(std::span<const uint8_t> tx_data, std::span<uint8_t> rx_data) {
621 }
622
633#endif
634
644 [[nodiscard]] result<void> try_polling_transmit(std::span<const uint8_t> tx_data);
645
656
667
678 [[nodiscard]] result<void> try_polling_transfer(std::span<const uint8_t> tx_data, std::span<uint8_t> rx_data);
679
690
691 // =========================================================================
692 // Async queue API
693 // =========================================================================
694
695#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
719
733 template<typename Rep, typename Period>
735 queue_trans(const transaction& trans, const std::chrono::duration<Rep, Period>& timeout) {
737 }
738
739#endif
740
758
769 template<typename Rep, typename Period>
771 try_queue_trans(const transaction& trans, const std::chrono::duration<Rep, Period>& timeout) {
772 return _try_queue_trans(trans, std::chrono::ceil<std::chrono::milliseconds>(timeout));
773 }
774
775 // =========================================================================
776 // Bus exclusivity (Lockable interface)
777 // =========================================================================
778
785 void lock() const;
786
793
797 void unlock() const;
798
799private:
800 // Opaque async state held in the .cpp. master_device keeps a single
801 // unique_ptr to it, so the class stays cheap to move while the mutexes,
802 // condition variable, and slot pool keep a stable address across the
803 // device's lifetime.
805
806 explicit master_device(master_bus* bus, spi_device_handle_t handle, size_t queue_size);
807
808 void _delete() noexcept;
809
811
812 [[nodiscard]] result<void> _simple_trans(std::span<const uint8_t> tx, std::span<uint8_t> rx, _trans_fn fn);
813 [[nodiscard]] result<void> _full_trans(const transaction& trans, _trans_fn fn);
814
815 [[nodiscard]] result<idfxx::future<void>>
816 _try_queue_trans(const transaction& trans, std::optional<std::chrono::milliseconds> timeout);
817 [[nodiscard]] result<void> _try_wait_for(
818 const std::shared_ptr<std::atomic<bool>>& done_flag,
819 std::optional<std::chrono::milliseconds> timeout
820 );
821
822 [[nodiscard]] result<uint16_t> _acquire_slot(std::optional<std::chrono::milliseconds> timeout);
823 void _release_slot(uint16_t slot_idx) noexcept;
824
825 static void _prepare_idf_trans(spi_transaction_t& idf_trans, const transaction& trans);
826 static void _prepare_idf_trans_ext(spi_transaction_ext_t& idf_ext, const transaction& trans);
827
828 master_bus* _bus = nullptr;
829 spi_device_handle_t _handle = nullptr;
830 std::unique_ptr<async_state> _async;
831};
832
// end of idfxx_spi
834
835} // namespace idfxx::spi
836
838
846[[nodiscard]] inline std::string to_string(spi::host_device h) {
847 switch (h) {
849 return "SPI1";
851 return "SPI2";
852#if SOC_SPI_PERIPH_NUM > 2
854 return "SPI3";
855#endif
856 default:
857 return "unknown(" + std::to_string(static_cast<int>(h)) + ")";
858 }
859}
860
861} // namespace idfxx
862
863#include "sdkconfig.h"
864#ifdef CONFIG_IDFXX_STD_FORMAT
866#include <algorithm>
867#include <format>
868namespace std {
869template<>
870struct formatter<idfxx::spi::host_device> {
871 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
872
873 template<typename FormatContext>
874 auto format(idfxx::spi::host_device h, FormatContext& ctx) const {
875 auto s = idfxx::to_string(h);
876 return std::copy(s.begin(), s.end(), ctx.out());
877 }
878};
879} // namespace std
881#endif // CONFIG_IDFXX_STD_FORMAT
Type-safe set of flags from a scoped enum.
Definition flags.hpp:88
Async completion token.
Definition future.hpp:62
A GPIO pin.
Definition gpio.hpp:62
static constexpr gpio nc()
Returns a GPIO representing "not connected".
Definition gpio.hpp:255
level
GPIO output/input level.
Definition gpio.hpp:68
@ low
Logic low (0)
A SPI master bus.
Definition master.hpp:254
master_bus(master_bus &&other) noexcept
Move constructor.
master_bus(const master_bus &)=delete
static result< master_bus > make(enum host_device host, enum dma_chan dma_chan, struct bus_config config)
Creates a new SPI master bus.
master_bus(enum host_device host, enum dma_chan dma_chan, struct bus_config config)
Constructs a new SPI master bus.
enum host_device host() const
Returns the host device ID the bus is using.
Definition master.hpp:294
size_t max_transaction_length() const
Returns the maximum transaction length for this bus.
master_bus & operator=(master_bus &&other) noexcept
Move assignment.
master_bus & operator=(const master_bus &)=delete
A device on a SPI master bus.
Definition master.hpp:332
bool try_lock() const noexcept
Tries to acquire exclusive bus access without blocking.
master_bus & bus() const
Returns the parent bus.
Definition master.hpp:422
void transmit(const transaction &trans)
Executes a full-control transaction.
Definition master.hpp:557
std::vector< uint8_t > receive(size_t size)
Receives data from the device.
Definition master.hpp:485
void transfer(std::span< const uint8_t > tx_data, std::span< uint8_t > rx_data)
Performs a full-duplex transfer.
Definition master.hpp:502
void transmit(std::span< const uint8_t > tx_data)
Transmits data to the device.
Definition master.hpp:459
result< std::vector< uint8_t > > try_receive(size_t size)
Receives data from the device.
result< void > try_polling_receive(std::span< uint8_t > rx_data)
Receives data using polling (busy-wait).
std::vector< uint8_t > polling_receive(size_t size)
Receives data using polling (busy-wait).
Definition master.hpp:607
idfxx::future< void > queue_trans(const transaction &trans, const std::chrono::duration< Rep, Period > &timeout)
Queues a transaction for asynchronous execution with timeout.
Definition master.hpp:735
void polling_transfer(std::span< const uint8_t > tx_data, std::span< uint8_t > rx_data)
Performs a full-duplex transfer using polling (busy-wait).
Definition master.hpp:619
result< idfxx::future< void > > try_queue_trans(const transaction &trans, const std::chrono::duration< Rep, Period > &timeout)
Queues a transaction for asynchronous execution with timeout.
Definition master.hpp:771
result< void > try_polling_transmit(std::span< const uint8_t > tx_data)
Transmits data using polling (busy-wait).
result< void > try_transfer(std::span< const uint8_t > tx_data, std::span< uint8_t > rx_data)
Performs a full-duplex transfer.
master_device & operator=(master_device &&other) noexcept
master_device(master_bus &bus, const struct config &config)
Creates a new SPI device on the specified bus.
master_device(master_device &&other) noexcept
void unlock() const
Releases exclusive bus access.
master_device(const master_device &)=delete
result< idfxx::future< void > > try_queue_trans(const transaction &trans)
Queues a transaction for asynchronous execution.
spi_device_handle_t idf_handle() const
Returns the underlying ESP-IDF device handle.
Definition master.hpp:428
master_device & operator=(const master_device &)=delete
void lock() const
Acquires exclusive access to the SPI bus for this device.
static result< master_device > make(master_bus &bus, const struct config &config)
Creates a new SPI device on the specified bus.
void polling_transmit(std::span< const uint8_t > tx_data)
Transmits data using polling (busy-wait).
Definition master.hpp:583
void receive(std::span< uint8_t > rx_data)
Receives data from the device into the provided buffer.
Definition master.hpp:469
result< void > try_transmit(const transaction &trans)
Executes a full-control transaction.
result< void > try_transmit(std::span< const uint8_t > tx_data)
Transmits data to the device.
result< void > try_polling_transmit(const transaction &trans)
Executes a full-control transaction using polling (busy-wait).
result< std::vector< uint8_t > > try_polling_receive(size_t size)
Receives data using polling (busy-wait).
idfxx::future< void > queue_trans(const transaction &trans)
Queues a transaction for asynchronous execution.
Definition master.hpp:718
result< void > try_receive(std::span< uint8_t > rx_data)
Receives data from the device into the provided buffer.
void polling_transmit(const transaction &trans)
Executes a full-control transaction using polling (busy-wait).
Definition master.hpp:632
freq::hertz frequency() const
Returns the actual clock frequency in use.
void polling_receive(std::span< uint8_t > rx_data)
Receives data using polling (busy-wait).
Definition master.hpp:594
result< void > try_polling_transfer(std::span< const uint8_t > tx_data, std::span< uint8_t > rx_data)
Performs a full-duplex transfer using polling (busy-wait).
int esp_err_t
Definition error.hpp:35
std::string to_string(core_id c)
Returns a string representation of a CPU core identifier.
Definition cpu.hpp:52
trans_flags
SPI transaction flags.
Definition master.hpp:128
dma_chan
SPI DMA channel selection.
Definition master.hpp:63
bus_flags
SPI bus capability and configuration flags.
Definition master.hpp:81
device_flags
SPI device capability and configuration flags.
Definition master.hpp:108
host_device
General purpose SPI Host Controller ID.
Definition master.hpp:51
@ multiline_cmd
Use multi-line mode for command phase.
@ dma_use_psram
Use PSRAM for DMA buffer directly. Requires ESP-IDF 6.0+.
@ mode_qio
Transmit/receive data in 4-bit mode.
@ variable_cmd
Use per-transaction command_bits length.
@ use_rxdata
Receive into inline rx_data instead of rx_buffer.
@ use_txdata
Transmit inline tx_data instead of tx_buffer.
@ cs_keep_active
Keep CS active after data transfer.
@ mode_oct
Transmit/receive data in 8-bit mode.
@ variable_addr
Use per-transaction address_bits length.
@ variable_dummy
Use per-transaction dummy_bits length.
@ dma_buffer_align_manual
Disable automatic DMA buffer re-alignment.
@ multiline_addr
Use multi-line mode for address phase.
@ mode_dio
Transmit/receive data in 2-bit mode.
@ ch_auto
Auto select DMA channel.
@ slp_allow_pd
Allow to power down the peripheral during light sleep, and auto recover then.
@ slave
Bus supports slave mode.
@ iomux_pins
Bus uses IOMUX pins.
@ octal
Check existing of MOSI/MISO/WP/HD/SPIIO4/SPIIO5/SPIIO6/SPIIO7 pins as output.
@ dual
Check MOSI and MISO pins can output. Or indicates bus able to work under DIO mode.
@ quad
Check existing of MOSI/MISO/WP/HD pins as output.
@ io4_io7
Check existing of IO4~IO7 pins. Or indicates IO4~IO7 pins initialized.
@ wphd
Check existing of WP and HD pins. Or indicates WP & HD pins initialized.
@ native_pins
Bus uses native pins.
@ mosi
Check existing of MOSI pin. Or indicates MOSI line initialized.
@ miso
Check existing of MISO pin. Or indicates MISO line initialized.
@ master
Bus supports master mode.
@ sclk
Check existing of SCLK pin. Or indicates CLK line initialized.
@ no_dummy
Disable automatic dummy bit insertion.
@ halfduplex
Transmit data before receiving, not simultaneously.
@ three_wire
Use MOSI for both sending and receiving.
@ ddrclk
Use double data rate clocking.
@ clk_as_cs
Output clock on CS line while CS is active.
@ txbit_lsbfirst
Transmit command/address/data LSB first.
@ bit_lsbfirst
Transmit and receive LSB first.
@ rxbit_lsbfirst
Receive data LSB first.
@ no_return_result
Don't return descriptor on completion (use post_cb).
@ positive_cs
CS is active high during a transaction.
SPI driver classes.
Definition master.hpp:45
T unwrap(result< T > result)
Throws a std::system_error if the result is an error.
Definition error.hpp:307
@ timeout
Operation timed out.
std::expected< T, std::error_code > result
result type wrapping a value or error code.
Definition error.hpp:120
SPI bus configuration.
Definition master.hpp:207
gpio::level data_idle_level
Data pin output level while no transaction is active.
Definition master.hpp:230
idfxx::flags< idfxx::intr_flag > intr_flags
Behavioral interrupt flags.
Definition master.hpp:241
idfxx::gpio data7
GPIO pin for spi data7 signal in octal mode, or gpio::nc() if not used.
Definition master.hpp:228
idfxx::gpio mosi
GPIO pin for Master Out Slave In (=spi_d) signal.
Definition master.hpp:209
idfxx::gpio data1
GPIO pin for spi data1 signal in quad/octal mode.
Definition master.hpp:214
idfxx::gpio miso
GPIO pin for Master In Slave Out (=spi_q) signal.
Definition master.hpp:213
idfxx::gpio data2
GPIO pin for spi data2 signal in quad/octal mode, or gpio::nc() if not used.
Definition master.hpp:219
idfxx::gpio data4
GPIO pin for spi data4 signal in octal mode, or gpio::nc() if not used.
Definition master.hpp:225
idfxx::gpio quadwp
GPIO pin for WP (Write Protect) signal, or gpio::nc() if not used.
Definition master.hpp:218
size_t max_transfer_sz
Maximum transfer size, in bytes (DMA only).
Definition master.hpp:231
idfxx::gpio data0
GPIO pin for spi data0 signal in quad/octal mode.
Definition master.hpp:210
idfxx::gpio data5
GPIO pin for spi data5 signal in octal mode, or gpio::nc() if not used.
Definition master.hpp:226
idfxx::gpio sclk
GPIO pin for SPI Clock signal.
Definition master.hpp:216
idfxx::gpio data3
GPIO pin for spi data3 signal in quad/octal mode, or gpio::nc() if not used.
Definition master.hpp:223
idfxx::gpio quadhd
GPIO pin for HD (Hold) signal, or gpio::nc() if not used.
Definition master.hpp:222
idfxx::gpio data6
GPIO pin for spi data6 signal in octal mode, or gpio::nc() if not used.
Definition master.hpp:227
std::optional< idfxx::core_id > isr_cpu_id
Select cpu core to register SPI ISR.
Definition master.hpp:238
SPI device configuration.
Definition master.hpp:347
std::chrono::nanoseconds input_delay
Maximum data valid time of slave.
Definition master.hpp:356
uint8_t mode
SPI mode (0-3): (CPOL, CPHA) pair.
Definition master.hpp:351
uint16_t cs_ena_pretrans
SPI bit-cycles CS is active before transmission (half-duplex only).
Definition master.hpp:353
uint8_t cs_ena_posttrans
SPI bit-cycles CS stays active after transmission.
Definition master.hpp:354
freq::hertz clock_speed
SPI clock speed in Hz.
Definition master.hpp:355
uint8_t dummy_bits
Dummy bits between address and data phase.
Definition master.hpp:350
uint8_t address_bits
Default address phase length in bits (0-64).
Definition master.hpp:349
int queue_size
Transaction queue depth for async API.
Definition master.hpp:359
uint8_t command_bits
Default command phase length in bits (0-16).
Definition master.hpp:348
uint16_t duty_cycle_pos
Duty cycle of positive clock in 1/256 increments (128 = 50%, 0 = default).
Definition master.hpp:352
idfxx::gpio cs
GPIO pin for chip select, or gpio::nc() if not used.
Definition master.hpp:357
SPI transaction descriptor for full-control transactions.
Definition master.hpp:175
size_t length
Total data length in bits.
Definition master.hpp:181
uint8_t dummy_bits
Dummy length for variable_dummy flag.
Definition master.hpp:186
uint64_t addr
Address data (length set by device config or variable_addr).
Definition master.hpp:178
std::span< uint8_t > rx_buffer
Receive buffer, or empty for no MISO phase.
Definition master.hpp:180
freq::hertz override_freq
Override clock speed for this transaction (0 = no override).
Definition master.hpp:183
uint16_t cmd
Command data (length set by device config or variable_cmd).
Definition master.hpp:177
std::span< const uint8_t > tx_buffer
Transmit buffer, or empty for no MOSI phase.
Definition master.hpp:179
size_t rx_length
Receive length in bits (0 = same as length).
Definition master.hpp:182
uint8_t command_bits
Command length for variable_cmd flag.
Definition master.hpp:184
uint8_t address_bits
Address length for variable_addr flag.
Definition master.hpp:185