libxr  1.0
Want to be the best embedded framework
Loading...
Searching...
No Matches
LibXR::ESP32SPI Class Reference
Inheritance diagram for LibXR::ESP32SPI:
[legend]
Collaboration diagram for LibXR::ESP32SPI:
[legend]

Public Member Functions

 ESP32SPI (spi_host_device_t host, int sclk_pin, int miso_pin, int mosi_pin, RawData dma_rx, RawData dma_tx, SPI::Configuration config={ SPI::ClockPolarity::LOW, SPI::ClockPhase::EDGE_1, SPI::Prescaler::DIV_8, false, }, uint32_t dma_enable_min_size=3U, bool enable_dma=true)
 
ErrorCode ReadAndWrite (RawData read_data, ConstRawData write_data, OperationRW &op, bool in_isr=false) override
 进行 SPI 读写操作。Performs SPI read and write operations.
 
ErrorCode SetConfig (SPI::Configuration config) override
 设置 SPI 配置参数。Sets SPI configuration parameters.
 
ErrorCode MemRead (uint16_t reg, RawData read_data, OperationRW &op, bool in_isr=false) override
 SPI 设备的寄存器读取数据。 Reads data from a specific register of the SPI device.
 
ErrorCode MemWrite (uint16_t reg, ConstRawData write_data, OperationRW &op, bool in_isr=false) override
 SPI 设备的寄存器写入数据。 Writes data to a specific register of the SPI device.
 
ErrorCode Transfer (size_t size, OperationRW &op, bool in_isr=false) override
 进行一次SPI传输(使用当前缓冲区数据,零拷贝,支持双缓冲)。 Performs a SPI transfer (zero-copy, supports double buffering).
 
uint32_t GetMaxBusSpeed () const override
 获取 SPI 设备的最大时钟速度。Gets the maximum clock speed of the SPI device.
 
Prescaler GetMaxPrescaler () const override
 获取 SPI 设备的最大分频系数。Gets the maximum prescaler of the SPI device.
 
RawData GetRxBuffer ()
 
RawData GetTxBuffer ()
 
- Public Member Functions inherited from LibXR::SPI
 SPI (RawData rx_buffer, RawData tx_buffer)
 构造函数。Constructor.
 
virtual ErrorCode Read (RawData read_data, OperationRW &op, bool in_isr=false)
 进行 SPI 读取操作。Performs SPI read operation.
 
virtual ErrorCode Write (ConstRawData write_data, OperationRW &op, bool in_isr=false)
 进行 SPI 写入操作。Performs SPI write operation.
 
uint32_t GetBusSpeed () const
 获取 SPI 设备的当前总线速度。Gets the current bus speed of the SPI device.
 
Prescaler CalcPrescaler (uint32_t target_max_bus_speed, uint32_t target_min_bus_speed, bool increase)
 计算 SPI 分频系数。Calculates the SPI prescaler.
 
RawData GetRxBuffer ()
 获取接收数据的缓冲区。Gets the buffer for storing received data.
 
RawData GetTxBuffer ()
 获取发送数据的缓冲区。Gets the buffer for storing data to be sent.
 
void SwitchBuffer ()
 切换缓冲区。Switches the buffer.
 
void SetActiveLength (size_t len)
 设置缓冲区的有效数据长度。Sets the length of valid data in the buffer.
 
size_t GetActiveLength () const
 获取缓冲区的有效数据长度。Gets the length of valid data in the buffer.
 
ConfigurationGetConfig ()
 获取 SPI 配置参数。Gets the SPI configuration parameters.
 
bool IsDoubleBuffer () const
 检查是否使用双缓冲区。Checks if double buffering is enabled.
 

Private Member Functions

bool Acquire ()
 
void Release ()
 
ErrorCode InitializeHardware ()
 
ErrorCode ConfigurePins ()
 
ErrorCode ResolveClockSource (uint32_t &source_hz)
 
ErrorCode InstallInterrupt ()
 
ErrorCode InitDmaBackend ()
 
void HandleInterrupt ()
 
void FinishAsync (bool in_isr, ErrorCode ec)
 
bool CanUseDma (size_t size) const
 
ErrorCode EnsureReadyAndAcquire ()
 
ErrorCode ReturnAsyncStartResult (ErrorCode ec, bool started)
 
ErrorCode StartAsyncTransfer (const uint8_t *tx, uint8_t *rx, size_t size, bool enable_rx, RawData read_back, bool mem_read, OperationRW &op, bool &started)
 
void ConfigureTransferRegisters (size_t size)
 
ErrorCode ExecuteChunk (const uint8_t *tx, uint8_t *rx, size_t size, bool enable_rx)
 
ErrorCode ExecuteTransfer (const uint8_t *tx, uint8_t *rx, size_t size, bool enable_rx)
 
bool UseLocalDoubleBuffer () const
 
void SwitchBufferLocal ()
 

Static Private Member Functions

static void SpiIsrEntry (void *arg)
 
static ErrorCode FinalizeSyncResult (OperationRW &op, bool in_isr, ErrorCode ec)
 
static ErrorCode CompleteZeroSize (OperationRW &op, bool in_isr)
 

Private Attributes

spi_host_device_t host_
 
spi_dev_t * hw_ = nullptr
 
int sclk_pin_
 
int miso_pin_
 
int mosi_pin_
 
uint32_t source_clock_hz_ = 0
 
Flag::Atomic busy_ {}
 
bool initialized_ = false
 
uint32_t dma_enable_min_size_ = 3U
 
bool dma_requested_ = true
 
RawData dma_rx_raw_ = {nullptr, 0}
 
RawData dma_tx_raw_ = {nullptr, 0}
 
size_t dbuf_rx_block_size_ = 0
 
size_t dbuf_tx_block_size_ = 0
 
uint8_t dbuf_active_block_ = 0
 
AsyncBlockWait block_wait_ {}
 
bool dbuf_enabled_ = false
 
intr_handle_t intr_handle_ = nullptr
 
bool intr_installed_ = false
 
bool dma_enabled_ = false
 
void * dma_ctx_ = nullptr
 
size_t dma_max_transfer_bytes_ = 0
 
bool async_running_ = false
 
size_t async_dma_size_ = 0
 
bool async_dma_rx_enabled_ = false
 
bool mem_read_ = false
 
RawData read_back_ = {nullptr, 0}
 
OperationRW rw_op_
 

Static Private Attributes

static constexpr size_t MAX_POLLING_TRANSFER_BYTES = SOC_SPI_MAXIMUM_BUFFER_SIZE
 
static constexpr size_t MAX_DMA_TRANSFER_BYTES = SPI_LL_DMA_MAX_BIT_LEN / 8U
 

Additional Inherited Members

- Public Types inherited from LibXR::SPI
enum class  ClockPolarity : uint8_t { LOW = 0 , HIGH = 1 }
 定义 SPI 时钟极性。Defines the SPI clock polarity. More...
 
enum class  ClockPhase : uint8_t { EDGE_1 = 0 , EDGE_2 = 1 }
 定义 SPI 时钟相位。Defines the SPI clock phase. More...
 
enum class  Prescaler : uint8_t {
  DIV_1 = 0 , DIV_2 = 1 , DIV_4 = 2 , DIV_8 = 3 ,
  DIV_16 = 4 , DIV_32 = 5 , DIV_64 = 6 , DIV_128 = 7 ,
  DIV_256 = 8 , DIV_512 = 9 , DIV_1024 = 10 , DIV_2048 = 11 ,
  DIV_4096 = 12 , DIV_8192 = 13 , DIV_16384 = 14 , DIV_32768 = 15 ,
  DIV_65536 = 16 , UNKNOWN = 0xFF
}
 
using OperationRW = WriteOperation
 定义读写操作类型的别名。Defines an alias for the read/write operation type.
 
- Static Public Member Functions inherited from LibXR::SPI
static constexpr uint32_t PrescalerToDiv (Prescaler prescaler)
 将分频系数转换为除数。Converts a prescaler to a divisor.
 

Detailed Description

Definition at line 19 of file esp_spi.hpp.

Constructor & Destructor Documentation

◆ ESP32SPI()

LibXR::ESP32SPI::ESP32SPI ( spi_host_device_t host,
int sclk_pin,
int miso_pin,
int mosi_pin,
RawData dma_rx,
RawData dma_tx,
SPI::Configuration config = SPI::ClockPolarity::LOWSPI::ClockPhase::EDGE_1SPI::Prescaler::DIV_8, false, },
uint32_t dma_enable_min_size = 3U,
bool enable_dma = true )

Definition at line 113 of file esp_spi.cpp.

116 : SPI(dma_rx, dma_tx),
117 host_(host),
118 sclk_pin_(sclk_pin),
119 miso_pin_(miso_pin),
120 mosi_pin_(mosi_pin),
121 dma_enable_min_size_(dma_enable_min_size),
122 dma_requested_(enable_dma),
123 dma_rx_raw_(dma_rx),
124 dma_tx_raw_(dma_tx),
125 dbuf_rx_block_size_(dma_rx.size_ / 2U),
126 dbuf_tx_block_size_(dma_tx.size_ / 2U)
127{
128 ASSERT(host_ != SPI1_HOST);
129 ASSERT(host_ < SPI_HOST_MAX);
130 ASSERT(static_cast<int>(host_) < SOC_SPI_PERIPH_NUM);
131 ASSERT(dma_rx_raw_.addr_ != nullptr);
132 ASSERT(dma_tx_raw_.addr_ != nullptr);
133 ASSERT(dma_rx_raw_.size_ > 0U);
134 ASSERT(dma_tx_raw_.size_ > 0U);
135
136 if (InitializeHardware() != ErrorCode::OK)
137 {
138 ASSERT(false);
139 return;
140 }
141
142 if (ConfigurePins() != ErrorCode::OK)
143 {
144 ASSERT(false);
145 return;
146 }
147
148 if (InstallInterrupt() != ErrorCode::OK)
149 {
150 ASSERT(false);
151 return;
152 }
153
154 if (dma_requested_)
155 {
156 const ErrorCode dma_ans = InitDmaBackend();
157 ASSERT(dma_ans == ErrorCode::OK);
158 if (dma_ans != ErrorCode::OK)
159 {
160 return;
161 }
162 }
163
164 if (SetConfig(config) != ErrorCode::OK)
165 {
166 ASSERT(false);
167 return;
168 }
169}
ErrorCode SetConfig(SPI::Configuration config) override
设置 SPI 配置参数。Sets SPI configuration parameters.
Definition esp_spi.cpp:456
size_t size_
数据字节数 / Data size in bytes
void * addr_
数据起始地址 / Data start address
SPI(RawData rx_buffer, RawData tx_buffer)
构造函数。Constructor.
Definition spi.hpp:110
ErrorCode
定义错误码枚举
@ OK
操作成功 | Operation successful

Member Function Documentation

◆ Acquire()

bool LibXR::ESP32SPI::Acquire ( )
private

Definition at line 171 of file esp_spi.cpp.

171{ return !busy_.TestAndSet(); }
bool TestAndSet() noexcept
测试并置位:置位并返回旧状态 / Test-and-set: set and return previous state
Definition flag.hpp:66

◆ CanUseDma()

bool LibXR::ESP32SPI::CanUseDma ( size_t size) const
private

Definition at line 542 of file esp_spi.cpp.

543{
544 return dma_requested_ && dma_enabled_ && (dma_ctx_ != nullptr) &&
545 (size > dma_enable_min_size_) && (size <= dma_max_transfer_bytes_);
546}

◆ CompleteZeroSize()

ErrorCode LibXR::ESP32SPI::CompleteZeroSize ( OperationRW & op,
bool in_isr )
staticprivate

Definition at line 570 of file esp_spi.cpp.

571{
572 return FinalizeSyncResult(op, in_isr, ErrorCode::OK);
573}

◆ ConfigurePins()

ErrorCode LibXR::ESP32SPI::ConfigurePins ( )
private

Definition at line 236 of file esp_spi.cpp.

237{
238 if (!initialized_ || (hw_ == nullptr))
239 {
241 }
242
243 const auto& signal = spi_periph_signal[host_];
244
245 if (sclk_pin_ >= 0)
246 {
247 if (!GPIO_IS_VALID_OUTPUT_GPIO(sclk_pin_))
248 {
249 return ErrorCode::ARG_ERR;
250 }
251 esp_rom_gpio_pad_select_gpio(static_cast<uint32_t>(sclk_pin_));
252 esp_rom_gpio_connect_out_signal(sclk_pin_, signal.spiclk_out, false, false);
253 gpio_input_enable(static_cast<gpio_num_t>(sclk_pin_));
254 esp_rom_gpio_connect_in_signal(sclk_pin_, signal.spiclk_in, false);
255 }
256
257 if (mosi_pin_ >= 0)
258 {
259 if (!GPIO_IS_VALID_OUTPUT_GPIO(mosi_pin_))
260 {
261 return ErrorCode::ARG_ERR;
262 }
263 esp_rom_gpio_pad_select_gpio(static_cast<uint32_t>(mosi_pin_));
264 esp_rom_gpio_connect_out_signal(mosi_pin_, signal.spid_out, false, false);
265 gpio_input_enable(static_cast<gpio_num_t>(mosi_pin_));
266 esp_rom_gpio_connect_in_signal(mosi_pin_, signal.spid_in, false);
267 }
268
269 if (miso_pin_ >= 0)
270 {
271 if (!GPIO_IS_VALID_GPIO(miso_pin_))
272 {
273 return ErrorCode::ARG_ERR;
274 }
275 gpio_input_enable(static_cast<gpio_num_t>(miso_pin_));
276 esp_rom_gpio_connect_in_signal(miso_pin_, signal.spiq_in, false);
277 }
278
279 return ErrorCode::OK;
280}
@ STATE_ERR
状态错误 | State error
@ ARG_ERR
参数错误 | Argument error

◆ ConfigureTransferRegisters()

void LibXR::ESP32SPI::ConfigureTransferRegisters ( size_t size)
private

Definition at line 584 of file esp_spi.cpp.

585{
586 static constexpr spi_line_mode_t LINE_MODE = {
587 .cmd_lines = 1,
588 .addr_lines = 1,
589 .data_lines = 1,
590 };
591 const size_t bitlen = size * 8U;
592
593 spi_ll_master_set_line_mode(hw_, LINE_MODE);
594 spi_ll_set_mosi_bitlen(hw_, bitlen);
595 spi_ll_set_miso_bitlen(hw_, bitlen);
596 spi_ll_set_command_bitlen(hw_, 0);
597 spi_ll_set_addr_bitlen(hw_, 0);
598 spi_ll_set_command(hw_, 0, 0, false);
599 spi_ll_set_address(hw_, 0, 0, false);
600 hw_->user.usr_command = 0;
601 hw_->user.usr_addr = 0;
602}

◆ EnsureReadyAndAcquire()

ErrorCode LibXR::ESP32SPI::EnsureReadyAndAcquire ( )
private

Definition at line 548 of file esp_spi.cpp.

549{
550 if (!initialized_)
551 {
552 return ErrorCode::INIT_ERR;
553 }
554 if (!Acquire())
555 {
556 return ErrorCode::BUSY;
557 }
558 return ErrorCode::OK;
559}
@ INIT_ERR
初始化错误 | Initialization error
@ BUSY
忙碌 | Busy

◆ ExecuteChunk()

ErrorCode LibXR::ESP32SPI::ExecuteChunk ( const uint8_t * tx,
uint8_t * rx,
size_t size,
bool enable_rx )
private

Definition at line 700 of file esp_spi.cpp.

702{
703 if ((size == 0U) || (size > MAX_POLLING_TRANSFER_BYTES))
704 {
705 return ErrorCode::SIZE_ERR;
706 }
707
708 static constexpr std::array<uint8_t, MAX_POLLING_TRANSFER_BYTES> ZERO = {};
709 const uint8_t* tx_data = (tx != nullptr) ? tx : ZERO.data();
710
711 ConfigureTransferRegisters(size);
712 spi_ll_enable_mosi(hw_, 1);
713 spi_ll_enable_miso(hw_, enable_rx ? 1 : 0);
714 spi_ll_write_buffer(hw_, tx_data, size * 8U);
715 spi_ll_clear_int_stat(hw_);
716 spi_ll_apply_config(hw_);
717 spi_ll_user_start(hw_);
718
719 const uint64_t timeout_us = CalcPollingTimeoutUs(size, GetBusSpeed());
720 const uint64_t start_us = GetNowUs();
721 while (!spi_ll_usr_is_done(hw_))
722 {
723 if ((GetNowUs() - start_us) > timeout_us)
724 {
725 return ErrorCode::TIMEOUT;
726 }
727 }
728
729 if (enable_rx && (rx != nullptr))
730 {
731 spi_ll_read_buffer(hw_, rx, size * 8U);
732 }
733 spi_ll_clear_int_stat(hw_);
734 return ErrorCode::OK;
735}
uint32_t GetBusSpeed() const
获取 SPI 设备的当前总线速度。Gets the current bus speed of the SPI device.
Definition spi.hpp:178
@ TIMEOUT
超时 | Timeout
@ SIZE_ERR
尺寸错误 | Size error

◆ ExecuteTransfer()

ErrorCode LibXR::ESP32SPI::ExecuteTransfer ( const uint8_t * tx,
uint8_t * rx,
size_t size,
bool enable_rx )
private

Definition at line 737 of file esp_spi.cpp.

739{
740 size_t offset = 0U;
741 while (offset < size)
742 {
743 const size_t remain = size - offset;
744 const size_t chunk = std::min(remain, MAX_POLLING_TRANSFER_BYTES);
745 const uint8_t* tx_chunk = (tx != nullptr) ? (tx + offset) : nullptr;
746 uint8_t* rx_chunk = (enable_rx && (rx != nullptr)) ? (rx + offset) : nullptr;
747
748 const ErrorCode ec = ExecuteChunk(tx_chunk, rx_chunk, chunk, enable_rx);
749 if (ec != ErrorCode::OK)
750 {
751 return ec;
752 }
753 offset += chunk;
754 }
755
756 return ErrorCode::OK;
757}

◆ FinalizeSyncResult()

ErrorCode LibXR::ESP32SPI::FinalizeSyncResult ( OperationRW & op,
bool in_isr,
ErrorCode ec )
staticprivate

Definition at line 561 of file esp_spi.cpp.

562{
563 if (op.type != OperationRW::OperationType::BLOCK)
564 {
565 op.UpdateStatus(in_isr, ec);
566 }
567 return ec;
568}

◆ FinishAsync()

void IRAM_ATTR LibXR::ESP32SPI::FinishAsync ( bool in_isr,
ErrorCode ec )
private

Definition at line 380 of file esp_spi.cpp.

381{
382 if (!async_running_)
383 {
384 return;
385 }
386
387#if CONFIG_IDF_TARGET_ESP32
388 // Keep ESP32 SPI DMA workaround state in sync with transfer lifecycle.
389 if (dma_enabled_)
390 {
391 spi_dma_ctx_t* ctx = ToDmaCtx(dma_ctx_);
392 if (ctx != nullptr)
393 {
394 spicommon_dmaworkaround_idle(ctx->tx_dma_chan.chan_id);
395 }
396 }
397#endif
398
399 spi_ll_disable_int(hw_);
400 spi_ll_clear_int_stat(hw_);
401
402 RawData rx = {nullptr, 0U};
403 const RawData read_back = read_back_;
404 const bool mem_read = mem_read_;
405 if ((ec == ErrorCode::OK) && async_dma_rx_enabled_)
406 {
407 rx = GetRxBuffer();
408#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
409 if (!CacheSyncDmaBuffer(rx.addr_, async_dma_size_, false))
410 {
412 }
413#endif
414 }
415
416 if ((ec == ErrorCode::OK) && (read_back.size_ > 0U))
417 {
418 if (rx.addr_ == nullptr)
419 {
420 rx = GetRxBuffer();
421 }
422 uint8_t* src = static_cast<uint8_t*>(rx.addr_);
423 if (mem_read)
424 {
425 ASSERT(rx.size_ >= (read_back.size_ + 1U));
426 src += 1;
427 }
428 else
429 {
430 ASSERT(rx.size_ >= read_back.size_);
431 }
432 Memory::FastCopy(read_back.addr_, src, read_back.size_);
433 }
434
435 if (ec == ErrorCode::OK)
436 {
437 SwitchBufferLocal();
438 }
439
440 async_running_ = false;
441 async_dma_size_ = 0U;
442 async_dma_rx_enabled_ = false;
443 mem_read_ = false;
444 read_back_ = {nullptr, 0};
445 Release();
446 if (rw_op_.type == OperationRW::OperationType::BLOCK)
447 {
448 (void)block_wait_.TryPost(in_isr, ec);
449 }
450 else
451 {
452 rw_op_.UpdateStatus(in_isr, ec);
453 }
454}
static void FastCopy(void *dst, const void *src, size_t size)
快速内存拷贝 / Fast memory copy
Definition libxr_mem.cpp:5
void UpdateStatus(bool in_isr, Status &&status)
Updates operation status based on type.
Definition libxr_rw.hpp:173
OperationType type
Definition libxr_rw.hpp:231
@ FAILED
操作失败 | Operation failed

◆ GetMaxBusSpeed()

uint32_t LibXR::ESP32SPI::GetMaxBusSpeed ( ) const
overridevirtual

获取 SPI 设备的最大时钟速度。Gets the maximum clock speed of the SPI device.

Returns
SPI 设备的最大时钟速度(单位:Hz)。The maximum clock speed of the SPI device (in Hz).

Implements LibXR::SPI.

Definition at line 935 of file esp_spi.cpp.

935{ return source_clock_hz_; }

◆ GetMaxPrescaler()

SPI::Prescaler LibXR::ESP32SPI::GetMaxPrescaler ( ) const
overridevirtual

获取 SPI 设备的最大分频系数。Gets the maximum prescaler of the SPI device.

Returns
SPI 设备的最大分频系数。The maximum prescaler of the SPI device.

Implements LibXR::SPI.

Definition at line 937 of file esp_spi.cpp.

937{ return Prescaler::DIV_65536; }
@ DIV_65536
分频系数为 65536。Division factor is 65536.

◆ GetRxBuffer()

RawData LibXR::ESP32SPI::GetRxBuffer ( )

Definition at line 512 of file esp_spi.cpp.

513{
514 if (UseLocalDoubleBuffer())
515 {
516 auto* base = static_cast<uint8_t*>(dma_rx_raw_.addr_);
517 return {base + static_cast<size_t>(dbuf_active_block_) * dbuf_rx_block_size_,
518 dbuf_rx_block_size_};
519 }
520 return dma_rx_raw_;
521}

◆ GetTxBuffer()

RawData LibXR::ESP32SPI::GetTxBuffer ( )

Definition at line 523 of file esp_spi.cpp.

524{
525 if (UseLocalDoubleBuffer())
526 {
527 auto* base = static_cast<uint8_t*>(dma_tx_raw_.addr_);
528 return {base + static_cast<size_t>(dbuf_active_block_) * dbuf_tx_block_size_,
529 dbuf_tx_block_size_};
530 }
531 return dma_tx_raw_;
532}

◆ HandleInterrupt()

void IRAM_ATTR LibXR::ESP32SPI::HandleInterrupt ( )
private

Definition at line 359 of file esp_spi.cpp.

360{
361 if ((hw_ == nullptr) || !initialized_)
362 {
363 return;
364 }
365
366 if (!async_running_)
367 {
368 spi_ll_clear_int_stat(hw_);
369 return;
370 }
371
372 if (!spi_ll_usr_is_done(hw_))
373 {
374 return;
375 }
376
377 FinishAsync(true, ErrorCode::OK);
378}

◆ InitDmaBackend()

ErrorCode LibXR::ESP32SPI::InitDmaBackend ( )
private

Definition at line 314 of file esp_spi.cpp.

315{
316 if (dma_enabled_)
317 {
318 return ErrorCode::OK;
319 }
320
321 spi_dma_ctx_t* ctx = nullptr;
322 if (spicommon_dma_chan_alloc(host_, SPI_DMA_CH_AUTO, &ctx) != ESP_OK)
323 {
324 return ErrorCode::INIT_ERR;
325 }
326
327 const size_t cfg_max_size = std::max(dma_rx_raw_.size_, dma_tx_raw_.size_);
328 int actual_max_size = 0;
329 if (spicommon_dma_desc_alloc(ctx, static_cast<int>(cfg_max_size), &actual_max_size) !=
330 ESP_OK)
331 {
332 (void)spicommon_dma_chan_free(ctx);
333 return ErrorCode::INIT_ERR;
334 }
335
336 dma_ctx_ = ctx;
337 dma_enabled_ = true;
338 dma_max_transfer_bytes_ =
339 std::min<size_t>({static_cast<size_t>(actual_max_size), dma_rx_raw_.size_,
340 dma_tx_raw_.size_, MAX_DMA_TRANSFER_BYTES});
341
342 if (dma_max_transfer_bytes_ == 0U)
343 {
344 return ErrorCode::INIT_ERR;
345 }
346
347 return ErrorCode::OK;
348}

◆ InitializeHardware()

ErrorCode LibXR::ESP32SPI::InitializeHardware ( )
private

Definition at line 175 of file esp_spi.cpp.

176{
177 if (initialized_)
178 {
179 return ErrorCode::OK;
180 }
181
182 if ((host_ <= SPI1_HOST) || (host_ >= SPI_HOST_MAX) ||
183 (static_cast<int>(host_) >= SOC_SPI_PERIPH_NUM))
184 {
185 return ErrorCode::ARG_ERR;
186 }
187
188 const auto& signal = spi_periph_signal[host_];
189 hw_ = signal.hw;
190 if (hw_ == nullptr)
191 {
193 }
194
195 PERIPH_RCC_ATOMIC()
196 {
197 (void)__DECLARE_RCC_ATOMIC_ENV;
198 spi_ll_enable_bus_clock(host_, true);
199 spi_ll_reset_register(host_);
200 }
201 spi_ll_enable_clock(host_, true);
202 spi_ll_master_init(hw_);
203
204 const spi_line_mode_t line_mode = {
205 .cmd_lines = 1,
206 .addr_lines = 1,
207 .data_lines = 1,
208 };
209 spi_ll_master_set_line_mode(hw_, line_mode);
210 spi_ll_set_half_duplex(hw_, false);
211 spi_ll_set_tx_lsbfirst(hw_, false);
212 spi_ll_set_rx_lsbfirst(hw_, false);
213 spi_ll_set_mosi_delay(hw_, 0, 0);
214 spi_ll_enable_mosi(hw_, 1);
215 spi_ll_enable_miso(hw_, 1);
216 spi_ll_set_dummy(hw_, 0);
217 spi_ll_set_command_bitlen(hw_, 0);
218 spi_ll_set_addr_bitlen(hw_, 0);
219 spi_ll_set_command(hw_, 0, 0, false);
220 spi_ll_set_address(hw_, 0, 0, false);
221 hw_->user.usr_command = 0;
222 hw_->user.usr_addr = 0;
223
224 spi_ll_set_clk_source(hw_, SPI_CLK_SRC_DEFAULT);
225 if (ResolveClockSource(source_clock_hz_) != ErrorCode::OK)
226 {
227 return ErrorCode::INIT_ERR;
228 }
229
230 spi_ll_disable_int(hw_);
231 spi_ll_clear_int_stat(hw_);
232 initialized_ = true;
233 return ErrorCode::OK;
234}
@ NOT_SUPPORT
不支持 | Not supported

◆ InstallInterrupt()

ErrorCode LibXR::ESP32SPI::InstallInterrupt ( )
private

Definition at line 295 of file esp_spi.cpp.

296{
297 if (intr_installed_)
298 {
299 return ErrorCode::OK;
300 }
301
302 const int irq_source = static_cast<int>(spi_periph_signal[host_].irq);
303 if (esp_intr_alloc(irq_source, ESP_INTR_FLAG_IRAM, &ESP32SPI::SpiIsrEntry, this,
304 &intr_handle_) != ESP_OK)
305 {
306 intr_handle_ = nullptr;
307 return ErrorCode::INIT_ERR;
308 }
309
310 intr_installed_ = true;
311 return ErrorCode::OK;
312}

◆ MemRead()

ErrorCode LibXR::ESP32SPI::MemRead ( uint16_t reg,
RawData read_data,
OperationRW & op,
bool in_isr = false )
overridevirtual

SPI 设备的寄存器读取数据。 Reads data from a specific register of the SPI device.

Parameters
reg寄存器地址。Register address.
read_data读取的数据缓冲区。Buffer to store read data.
op操作类型(同步/异步)。Operation mode (sync/async).
in_isr是否在中断中进行操作。Whether the operation is performed in an ISR.
Returns
操作结果的错误码。Error code indicating success or failure.

Implements LibXR::SPI.

Definition at line 812 of file esp_spi.cpp.

813{
814 if (read_data.size_ == 0U)
815 {
816 return CompleteZeroSize(op, in_isr);
817 }
818
819 const ErrorCode lock_ec = EnsureReadyAndAcquire();
820 if (lock_ec != ErrorCode::OK)
821 {
822 return lock_ec;
823 }
824
825 RawData rx = GetRxBuffer();
826 RawData tx = GetTxBuffer();
827 const size_t total = read_data.size_ + 1U;
828
829 ASSERT(rx.size_ >= total);
830 ASSERT(tx.size_ >= total);
831
832 auto* tx_ptr = static_cast<uint8_t*>(tx.addr_);
833 tx_ptr[0] = static_cast<uint8_t>(reg | 0x80U);
834 Memory::FastSet(tx_ptr + 1, 0x00, read_data.size_);
835
836 if (CanUseDma(total))
837 {
838 bool started = false;
839 const ErrorCode ec = StartAsyncTransfer(tx_ptr, static_cast<uint8_t*>(rx.addr_),
840 total, true, read_data, true, op, started);
841 return ReturnAsyncStartResult(ec, started);
842 }
843
844 const ErrorCode ec =
845 ExecuteTransfer(tx_ptr, static_cast<uint8_t*>(rx.addr_), total, true);
846 if (ec == ErrorCode::OK)
847 {
848 auto* rx_ptr = static_cast<uint8_t*>(rx.addr_);
849 Memory::FastCopy(read_data.addr_, rx_ptr + 1, read_data.size_);
850 SwitchBufferLocal();
851 }
852
853 Release();
854 return FinalizeSyncResult(op, in_isr, ec);
855}
static void FastSet(void *dst, uint8_t value, size_t size)
快速内存填充 / Fast memory fill

◆ MemWrite()

ErrorCode LibXR::ESP32SPI::MemWrite ( uint16_t reg,
ConstRawData write_data,
OperationRW & op,
bool in_isr = false )
overridevirtual

SPI 设备的寄存器写入数据。 Writes data to a specific register of the SPI device.

Parameters
reg寄存器地址。Register address.
write_data写入的数据缓冲区。Buffer containing data to write.
op操作类型(同步/异步)。Operation mode (sync/async).
in_isr是否在中断中进行操作。Whether the operation is performed in an ISR.
Returns
操作结果的错误码。Error code indicating success or failure.

Implements LibXR::SPI.

Definition at line 857 of file esp_spi.cpp.

859{
860 if (write_data.size_ == 0U)
861 {
862 return CompleteZeroSize(op, in_isr);
863 }
864
865 const ErrorCode lock_ec = EnsureReadyAndAcquire();
866 if (lock_ec != ErrorCode::OK)
867 {
868 return lock_ec;
869 }
870
871 RawData tx = GetTxBuffer();
872 const size_t total = write_data.size_ + 1U;
873 ASSERT(tx.size_ >= total);
874
875 auto* tx_ptr = static_cast<uint8_t*>(tx.addr_);
876 tx_ptr[0] = static_cast<uint8_t>(reg & 0x7FU);
877 Memory::FastCopy(tx_ptr + 1, write_data.addr_, write_data.size_);
878
879 if (CanUseDma(total))
880 {
881 bool started = false;
882 const ErrorCode ec = StartAsyncTransfer(tx_ptr, nullptr, total, false, {nullptr, 0},
883 false, op, started);
884 return ReturnAsyncStartResult(ec, started);
885 }
886
887 const ErrorCode ec = ExecuteTransfer(tx_ptr, nullptr, total, false);
888 if (ec == ErrorCode::OK)
889 {
890 SwitchBufferLocal();
891 }
892
893 Release();
894 return FinalizeSyncResult(op, in_isr, ec);
895}

◆ ReadAndWrite()

ErrorCode LibXR::ESP32SPI::ReadAndWrite ( RawData read_data,
ConstRawData write_data,
OperationRW & op,
bool in_isr = false )
overridevirtual

进行 SPI 读写操作。Performs SPI read and write operations.

Parameters
read_data存储读取数据的缓冲区。Buffer to store the read data.
write_data需要写入的数据缓冲区。Buffer containing the data to be written.
op读写操作类型。Type of read/write operation.
in_isr是否在中断中进行操作。Whether the operation is performed in an ISR.
Returns
操作结果的错误码。Error code indicating the result of the operation.

Implements LibXR::SPI.

Definition at line 759 of file esp_spi.cpp.

761{
762 const size_t need = std::max(read_data.size_, write_data.size_);
763 if (need == 0U)
764 {
765 return CompleteZeroSize(op, in_isr);
766 }
767
768 const ErrorCode lock_ec = EnsureReadyAndAcquire();
769 if (lock_ec != ErrorCode::OK)
770 {
771 return lock_ec;
772 }
773
774 RawData rx = GetRxBuffer();
775 RawData tx = GetTxBuffer();
776 ASSERT(rx.size_ >= need);
777 ASSERT(tx.size_ >= need);
778
779 auto* tx_ptr = static_cast<uint8_t*>(tx.addr_);
780 if (write_data.size_ > 0U)
781 {
782 Memory::FastCopy(tx_ptr, write_data.addr_, write_data.size_);
783 }
784 if (need > write_data.size_)
785 {
786 Memory::FastSet(tx_ptr + write_data.size_, 0x00, need - write_data.size_);
787 }
788
789 if (CanUseDma(need))
790 {
791 bool started = false;
792 const ErrorCode ec = StartAsyncTransfer(tx_ptr, static_cast<uint8_t*>(rx.addr_), need,
793 true, read_data, false, op, started);
794 return ReturnAsyncStartResult(ec, started);
795 }
796
797 const ErrorCode ec =
798 ExecuteTransfer(tx_ptr, static_cast<uint8_t*>(rx.addr_), need, true);
799 if (ec == ErrorCode::OK)
800 {
801 if (read_data.size_ > 0U)
802 {
803 Memory::FastCopy(read_data.addr_, rx.addr_, read_data.size_);
804 }
805 SwitchBufferLocal();
806 }
807
808 Release();
809 return FinalizeSyncResult(op, in_isr, ec);
810}

◆ Release()

void LibXR::ESP32SPI::Release ( )
private

Definition at line 173 of file esp_spi.cpp.

173{ busy_.Clear(); }
void Clear() noexcept
清除标志 / Clear the flag
Definition flag.hpp:47

◆ ResolveClockSource()

ErrorCode LibXR::ESP32SPI::ResolveClockSource ( uint32_t & source_hz)
private

Definition at line 282 of file esp_spi.cpp.

283{
284 source_hz = 0;
285 const esp_err_t err =
286 esp_clk_tree_src_get_freq_hz(static_cast<soc_module_clk_t>(SPI_CLK_SRC_DEFAULT),
287 ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &source_hz);
288 if ((err != ESP_OK) || (source_hz == 0))
289 {
290 return ErrorCode::INIT_ERR;
291 }
292 return ErrorCode::OK;
293}

◆ ReturnAsyncStartResult()

ErrorCode LibXR::ESP32SPI::ReturnAsyncStartResult ( ErrorCode ec,
bool started )
private

Definition at line 575 of file esp_spi.cpp.

576{
577 if (!started)
578 {
579 Release();
580 }
581 return ec;
582}

◆ SetConfig()

ErrorCode LibXR::ESP32SPI::SetConfig ( SPI::Configuration config)
overridevirtual

设置 SPI 配置参数。Sets SPI configuration parameters.

Parameters
config需要应用的 SPI 配置。The SPI configuration to apply.
Returns
操作结果的错误码。Error code indicating the result of the operation.

Implements LibXR::SPI.

Definition at line 456 of file esp_spi.cpp.

457{
458 if (!initialized_ || (hw_ == nullptr))
459 {
461 }
462 if (busy_.IsSet())
463 {
464 return ErrorCode::BUSY;
465 }
466
467 if (config.prescaler == Prescaler::UNKNOWN)
468 {
469 return ErrorCode::ARG_ERR;
470 }
471 if (config.double_buffer &&
472 ((dbuf_rx_block_size_ == 0U) || (dbuf_tx_block_size_ == 0U)))
473 {
474 return ErrorCode::ARG_ERR;
475 }
476
477 const uint32_t div = PrescalerToDiv(config.prescaler);
478 if ((div == 0) || (source_clock_hz_ == 0))
479 {
480 return ErrorCode::ARG_ERR;
481 }
482
483 const uint32_t target_hz = source_clock_hz_ / div;
484 if (target_hz == 0)
485 {
486 return ErrorCode::ARG_ERR;
487 }
488
489 const uint8_t mode = ResolveSpiMode(config.clock_polarity, config.clock_phase);
490 spi_ll_master_set_mode(hw_, mode);
491
492 const int applied_hz = spi_ll_master_set_clock(hw_, static_cast<int>(source_clock_hz_),
493 static_cast<int>(target_hz), 128);
494 if (applied_hz <= 0)
495 {
496 return ErrorCode::INIT_ERR;
497 }
498
499 spi_ll_apply_config(hw_);
500 GetConfig() = config;
501 dbuf_enabled_ = config.double_buffer;
502 dbuf_active_block_ = 0U;
503
504 return ErrorCode::OK;
505}
bool IsSet() const noexcept
判断是否已置位 / Check whether the flag is set
Definition flag.hpp:55
@ UNKNOWN
未知分频系数。Unknown prescaler.
static constexpr uint32_t PrescalerToDiv(Prescaler prescaler)
将分频系数转换为除数。Converts a prescaler to a divisor.
Definition spi.hpp:70
Configuration & GetConfig()
获取 SPI 配置参数。Gets the SPI configuration parameters.
Definition spi.hpp:396

◆ SpiIsrEntry()

void IRAM_ATTR LibXR::ESP32SPI::SpiIsrEntry ( void * arg)
staticprivate

Definition at line 350 of file esp_spi.cpp.

351{
352 auto* spi = static_cast<ESP32SPI*>(arg);
353 if (spi != nullptr)
354 {
355 spi->HandleInterrupt();
356 }
357}

◆ StartAsyncTransfer()

ErrorCode LibXR::ESP32SPI::StartAsyncTransfer ( const uint8_t * tx,
uint8_t * rx,
size_t size,
bool enable_rx,
RawData read_back,
bool mem_read,
OperationRW & op,
bool & started )
private

Definition at line 604 of file esp_spi.cpp.

607{
608 started = false;
609
610 if (!CanUseDma(size))
611 {
613 }
614 if ((tx == nullptr) || (size == 0U))
615 {
616 return ErrorCode::ARG_ERR;
617 }
618
619 spi_dma_ctx_t* ctx = ToDmaCtx(dma_ctx_);
620 if (ctx == nullptr)
621 {
622 return ErrorCode::INIT_ERR;
623 }
624
625 const bool rx_enabled = enable_rx && (rx != nullptr);
626 ConfigureTransferRegisters(size);
627
628#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
629 if (!CacheSyncDmaBuffer(tx, size, true))
630 {
631 return ErrorCode::FAILED;
632 }
633#endif
634
635 if (enable_rx && (rx != nullptr))
636 {
637 spicommon_dma_desc_setup_link(ctx->dmadesc_rx, rx, static_cast<int>(size), true);
638 if (DmaReset(ctx->rx_dma_chan) != ESP_OK)
639 {
640 return ErrorCode::INIT_ERR;
641 }
642 spi_hal_hw_prepare_rx(hw_);
643 if (DmaStart(ctx->rx_dma_chan, ctx->dmadesc_rx) != ESP_OK)
644 {
645 return ErrorCode::INIT_ERR;
646 }
647 }
648#if CONFIG_IDF_TARGET_ESP32
649 else
650 {
651 // Keep ESP32 full-duplex TX-only DMA behavior aligned with IDF workaround.
652 spi_ll_dma_rx_enable(hw_, true);
653 (void)DmaStart(ctx->rx_dma_chan, nullptr);
654 }
655#endif
656
657 spicommon_dma_desc_setup_link(ctx->dmadesc_tx, tx, static_cast<int>(size), false);
658 if (DmaReset(ctx->tx_dma_chan) != ESP_OK)
659 {
660 return ErrorCode::INIT_ERR;
661 }
662 spi_hal_hw_prepare_tx(hw_);
663 if (DmaStart(ctx->tx_dma_chan, ctx->dmadesc_tx) != ESP_OK)
664 {
665 return ErrorCode::INIT_ERR;
666 }
667
668#if CONFIG_IDF_TARGET_ESP32
669 spicommon_dmaworkaround_transfer_active(ctx->tx_dma_chan.chan_id);
670#endif
671
672 rw_op_ = op;
673 read_back_ = read_back;
674 mem_read_ = mem_read;
675 async_dma_size_ = size;
676 async_dma_rx_enabled_ = rx_enabled;
677 if (op.type == OperationRW::OperationType::BLOCK)
678 {
679 block_wait_.Start(*op.data.sem_info.sem);
680 }
681 started = true;
682
683 // On ESP32 classic, enable data lines only after DMA descriptors/channels are ready.
684 spi_ll_enable_mosi(hw_, 1);
685 spi_ll_enable_miso(hw_, rx_enabled ? 1 : 0);
686 spi_ll_clear_int_stat(hw_);
687 spi_ll_enable_int(hw_);
688 async_running_ = true;
689 spi_ll_apply_config(hw_);
690 spi_ll_user_start(hw_);
691
692 op.MarkAsRunning();
693 if (op.type == OperationRW::OperationType::BLOCK)
694 {
695 return block_wait_.Wait(op.data.sem_info.timeout);
696 }
697 return ErrorCode::OK;
698}

◆ SwitchBufferLocal()

void LibXR::ESP32SPI::SwitchBufferLocal ( )
private

Definition at line 534 of file esp_spi.cpp.

535{
536 if (UseLocalDoubleBuffer())
537 {
538 dbuf_active_block_ ^= 1U;
539 }
540}

◆ Transfer()

ErrorCode LibXR::ESP32SPI::Transfer ( size_t size,
OperationRW & op,
bool in_isr = false )
overridevirtual

进行一次SPI传输(使用当前缓冲区数据,零拷贝,支持双缓冲)。 Performs a SPI transfer (zero-copy, supports double buffering).

Parameters
size需要传输的数据大小。The size of the data to be transferred.
op读写操作类型。Type of read/write operation.
in_isr是否在中断中进行操作。Whether the operation is performed in an ISR.
Returns
操作结果的错误码。Error code indicating the result of the operation.

Implements LibXR::SPI.

Definition at line 897 of file esp_spi.cpp.

898{
899 if (size == 0U)
900 {
901 return CompleteZeroSize(op, in_isr);
902 }
903
904 const ErrorCode lock_ec = EnsureReadyAndAcquire();
905 if (lock_ec != ErrorCode::OK)
906 {
907 return lock_ec;
908 }
909
910 RawData rx = GetRxBuffer();
911 RawData tx = GetTxBuffer();
912 ASSERT(rx.size_ >= size);
913 ASSERT(tx.size_ >= size);
914
915 if (CanUseDma(size))
916 {
917 bool started = false;
918 const ErrorCode ec = StartAsyncTransfer(static_cast<const uint8_t*>(tx.addr_),
919 static_cast<uint8_t*>(rx.addr_), size, true,
920 {nullptr, 0}, false, op, started);
921 return ReturnAsyncStartResult(ec, started);
922 }
923
924 const ErrorCode ec = ExecuteTransfer(static_cast<const uint8_t*>(tx.addr_),
925 static_cast<uint8_t*>(rx.addr_), size, true);
926 if (ec == ErrorCode::OK)
927 {
928 SwitchBufferLocal();
929 }
930
931 Release();
932 return FinalizeSyncResult(op, in_isr, ec);
933}

◆ UseLocalDoubleBuffer()

bool LibXR::ESP32SPI::UseLocalDoubleBuffer ( ) const
private

Definition at line 507 of file esp_spi.cpp.

508{
509 return dbuf_enabled_ && (dbuf_rx_block_size_ > 0U) && (dbuf_tx_block_size_ > 0U);
510}

Field Documentation

◆ async_dma_rx_enabled_

bool LibXR::ESP32SPI::async_dma_rx_enabled_ = false
private

Definition at line 130 of file esp_spi.hpp.

◆ async_dma_size_

size_t LibXR::ESP32SPI::async_dma_size_ = 0
private

Definition at line 129 of file esp_spi.hpp.

◆ async_running_

bool LibXR::ESP32SPI::async_running_ = false
private

Definition at line 128 of file esp_spi.hpp.

◆ block_wait_

AsyncBlockWait LibXR::ESP32SPI::block_wait_ {}
private

Definition at line 121 of file esp_spi.hpp.

121{};

◆ busy_

Flag::Atomic LibXR::ESP32SPI::busy_ {}
private

Definition at line 112 of file esp_spi.hpp.

112{};

◆ dbuf_active_block_

uint8_t LibXR::ESP32SPI::dbuf_active_block_ = 0
private

Definition at line 120 of file esp_spi.hpp.

◆ dbuf_enabled_

bool LibXR::ESP32SPI::dbuf_enabled_ = false
private

Definition at line 122 of file esp_spi.hpp.

◆ dbuf_rx_block_size_

size_t LibXR::ESP32SPI::dbuf_rx_block_size_ = 0
private

Definition at line 118 of file esp_spi.hpp.

◆ dbuf_tx_block_size_

size_t LibXR::ESP32SPI::dbuf_tx_block_size_ = 0
private

Definition at line 119 of file esp_spi.hpp.

◆ dma_ctx_

void* LibXR::ESP32SPI::dma_ctx_ = nullptr
private

Definition at line 126 of file esp_spi.hpp.

◆ dma_enable_min_size_

uint32_t LibXR::ESP32SPI::dma_enable_min_size_ = 3U
private

Definition at line 114 of file esp_spi.hpp.

◆ dma_enabled_

bool LibXR::ESP32SPI::dma_enabled_ = false
private

Definition at line 125 of file esp_spi.hpp.

◆ dma_max_transfer_bytes_

size_t LibXR::ESP32SPI::dma_max_transfer_bytes_ = 0
private

Definition at line 127 of file esp_spi.hpp.

◆ dma_requested_

bool LibXR::ESP32SPI::dma_requested_ = true
private

Definition at line 115 of file esp_spi.hpp.

◆ dma_rx_raw_

RawData LibXR::ESP32SPI::dma_rx_raw_ = {nullptr, 0}
private

Definition at line 116 of file esp_spi.hpp.

116{nullptr, 0};

◆ dma_tx_raw_

RawData LibXR::ESP32SPI::dma_tx_raw_ = {nullptr, 0}
private

Definition at line 117 of file esp_spi.hpp.

117{nullptr, 0};

◆ host_

spi_host_device_t LibXR::ESP32SPI::host_
private

Definition at line 106 of file esp_spi.hpp.

◆ hw_

spi_dev_t* LibXR::ESP32SPI::hw_ = nullptr
private

Definition at line 107 of file esp_spi.hpp.

◆ initialized_

bool LibXR::ESP32SPI::initialized_ = false
private

Definition at line 113 of file esp_spi.hpp.

◆ intr_handle_

intr_handle_t LibXR::ESP32SPI::intr_handle_ = nullptr
private

Definition at line 123 of file esp_spi.hpp.

◆ intr_installed_

bool LibXR::ESP32SPI::intr_installed_ = false
private

Definition at line 124 of file esp_spi.hpp.

◆ MAX_DMA_TRANSFER_BYTES

size_t LibXR::ESP32SPI::MAX_DMA_TRANSFER_BYTES = SPI_LL_DMA_MAX_BIT_LEN / 8U
staticconstexprprivate

Definition at line 57 of file esp_spi.hpp.

◆ MAX_POLLING_TRANSFER_BYTES

size_t LibXR::ESP32SPI::MAX_POLLING_TRANSFER_BYTES = SOC_SPI_MAXIMUM_BUFFER_SIZE
staticconstexprprivate

Definition at line 56 of file esp_spi.hpp.

◆ mem_read_

bool LibXR::ESP32SPI::mem_read_ = false
private

Definition at line 131 of file esp_spi.hpp.

◆ miso_pin_

int LibXR::ESP32SPI::miso_pin_
private

Definition at line 109 of file esp_spi.hpp.

◆ mosi_pin_

int LibXR::ESP32SPI::mosi_pin_
private

Definition at line 110 of file esp_spi.hpp.

◆ read_back_

RawData LibXR::ESP32SPI::read_back_ = {nullptr, 0}
private

Definition at line 132 of file esp_spi.hpp.

132{nullptr, 0};

◆ rw_op_

OperationRW LibXR::ESP32SPI::rw_op_
private

Definition at line 133 of file esp_spi.hpp.

◆ sclk_pin_

int LibXR::ESP32SPI::sclk_pin_
private

Definition at line 108 of file esp_spi.hpp.

◆ source_clock_hz_

uint32_t LibXR::ESP32SPI::source_clock_hz_ = 0
private

Definition at line 111 of file esp_spi.hpp.


The documentation for this class was generated from the following files: