arduino-audio-tools
Loading...
Searching...
No Matches
SPIAudioSlave.h
Go to the documentation of this file.
1#pragma once
2
3#include <SPI.h>
4
10#include "SPIAudioCommand.h"
11
12#if defined(ESP32)
13#include "driver/spi_slave.h"
14#endif
15
16namespace audio_tools {
17
32 size_t max_command_payload = 1024;
34 size_t max_mime_len = 20;
35#if defined(ESP32)
36 bool use_hw_slave = true;
37#if defined(SPI2_HOST)
38 spi_host_device_t host = SPI2_HOST;
39#elif defined(HSPI_HOST)
40 spi_host_device_t host = HSPI_HOST;
41#elif defined(SPI3_HOST)
42 spi_host_device_t host = SPI3_HOST;
43#elif defined(VSPI_HOST)
44 spi_host_device_t host = VSPI_HOST;
45#else
46 spi_host_device_t host = SPI2_HOST;
47#endif
48 int dma_chan = 1;
49 int pin_mosi = 23;
50 int pin_miso = 19;
51 int pin_sclk = 18;
52 int pin_cs = 5;
55 int queue_size = 4;
56#endif
57 void log() const {
58 LOGI(
59 "SPIAudioSlaveConfig: sample_rate=%d, channels=%d, bits_per_sample=%d, "
60 "max_command_payload=%d, max_response_payload=%d, max_mime_len=%d ",
61 (int)sample_rate, (int)channels, (int)bits_per_sample,
63#if defined(ESP32)
64 LOGI(
65 "ESP32 SPI Slave Config: use_hw_slave=%s, host=%d, dma_chan=%d, "
66 "pin_mosi=%d, pin_miso=%d, pin_sclk=%d, pin_cs=%d, queue_size=%d",
67 use_hw_slave ? "true" : "false", host, dma_chan, pin_mosi, pin_miso,
69#endif
70 }
71};
72
238 public:
246
249
252 config = cfg;
253 config.log();
254 setAudioInfo(cfg);
258
259 if (p_buffer == nullptr) return false;
260 p_buffer->reset();
264 mime = "";
265
267
268#if defined(ESP32)
269 if (config.use_hw_slave) {
270 spi_bus_config_t bus_cfg;
271 memset(&bus_cfg, 0, sizeof(bus_cfg));
272 bus_cfg.mosi_io_num = config.pin_mosi;
273 bus_cfg.miso_io_num = config.pin_miso;
274 bus_cfg.sclk_io_num = config.pin_sclk;
275 bus_cfg.quadwp_io_num = -1;
276 bus_cfg.quadhd_io_num = -1;
277
278 spi_slave_interface_config_t slave_cfg;
279 memset(&slave_cfg, 0, sizeof(slave_cfg));
280 slave_cfg.mode = 0;
281 slave_cfg.spics_io_num = config.pin_cs;
282 slave_cfg.queue_size = config.queue_size;
283 slave_cfg.flags = 0;
284
285 esp_err_t rc = spi_slave_initialize(config.host, &bus_cfg, &slave_cfg,
287 if (rc != ESP_OK) {
288 active = false;
289 return false;
290 }
291 esp32_spi_active = true;
292 spi_tx_byte = 0;
293 }
294#endif
295
296 active = true;
297 return true;
298 }
299
301 void end() override {
302#if defined(ESP32)
303 if (esp32_spi_active) {
304 spi_slave_free(config.host);
305 esp32_spi_active = false;
306 }
307#endif
308 active = false;
309 if (p_buffer != nullptr) p_buffer->reset();
311 }
312
314 bool isActive() const { return active; }
315
321
325 bool process() {
326#if defined(ESP32)
327 if (!esp32_spi_active) return false;
328
329 spi_slave_transaction_t t;
330 memset(&t, 0, sizeof(t));
331 t.length = 8;
332 t.tx_buffer = &spi_tx_byte;
333 t.rx_buffer = &spi_rx_byte;
334
335 esp_err_t rc = spi_slave_transmit(config.host, &t, 0);
336 if (rc != ESP_OK) return false;
337
339 // If the last Rx byte triggered a Rx→TxStatus transition, the returned
340 // value was 0 (Rx state return). Pre-advance through TxStatus now so that
341 // spi_tx_byte holds tx_status before the next spi_slave_transmit call,
342 // eliminating the 1-byte pipeline shift in the response stream.
343 if (state == TxStatus) {
345 }
346 return true;
347#else
348 return false;
349#endif
350 }
351
353 uint8_t onTransfer(uint8_t in) {
354 switch (state) {
355 case RxCmd:
356 current_cmd = in;
358 rx_pos = 0;
359 tx_pos = 0;
360 tx_len = 0;
361 tx_status = Ok;
362 state = RxLenLo;
363 return 0;
364
365 case RxLenLo:
367 state = RxLenHi;
368 return 0;
369
370 case RxLenHi:
371 expected_payload_len |= static_cast<uint16_t>(in) << 8;
372 if (expected_payload_len == 0) {
374 state = TxStatus;
375 } else {
376 rx_pos = 0;
378 }
379 return 0;
380
381 case RxPayload:
382 if (rx_pos < request_payload.size()) {
384 }
385 rx_pos++;
388 state = TxStatus;
389 }
390 return 0;
391
392 case TxStatus:
393 state = TxLenLo;
394 return tx_status;
395
396 case TxLenLo:
397 state = TxLenHi;
398 return static_cast<uint8_t>(tx_len & 0xFF);
399
400 case TxLenHi: {
401 uint8_t out = static_cast<uint8_t>((tx_len >> 8) & 0xFF);
402 if (tx_len == 0) {
403 state = RxCmd;
404 } else {
406 }
407 return out;
408 }
409
410 case TxPayload: {
411 uint8_t out = tx_pos < tx_len ? response_payload[tx_pos] : 0;
412 tx_pos++;
413 if (tx_pos >= tx_len) {
414 state = RxCmd;
415 }
416 return out;
417 }
418 }
420 return 0;
421 }
422
424 bool processFrame(const uint8_t* request_frame, uint16_t request_frame_len,
425 uint8_t* response_frame, uint16_t response_frame_max,
426 uint16_t* response_frame_len = nullptr) {
427 if (request_frame == nullptr || request_frame_len < 3 ||
428 response_frame == nullptr || response_frame_max < 3) {
429 return false;
430 }
431
432 uint8_t cmd = request_frame[0];
433 uint16_t payload_len = static_cast<uint16_t>(request_frame[1]) |
434 (static_cast<uint16_t>(request_frame[2]) << 8);
435 if (static_cast<uint32_t>(payload_len) + 3 > request_frame_len) {
436 return false;
437 }
438
439 uint16_t out_len = 0;
440 uint8_t status = processCommand(
441 static_cast<SPIAudioCommand>(cmd), request_frame + 3, payload_len,
442 response_frame + 3, response_frame_max - 3, out_len);
443
444 response_frame[0] = status;
445 response_frame[1] = static_cast<uint8_t>(out_len & 0xFF);
446 response_frame[2] = static_cast<uint8_t>((out_len >> 8) & 0xFF);
447
448 if (response_frame_len != nullptr) {
449 *response_frame_len = out_len + 3;
450 }
451 return true;
452 }
453
454 // Audio data access
456 int available() override {
457 return p_buffer == nullptr ? 0 : p_buffer->available();
458 }
459
461 int availableForWrite() override {
462 return p_buffer == nullptr ? 0 : p_buffer->availableForWrite();
463 }
464
466 size_t readBytes(uint8_t* data, size_t len) override {
467 if (!active || data == nullptr || len == 0) return 0;
468 if (p_buffer == nullptr) return 0;
469 int to_read = min(static_cast<int>(len), p_buffer->available());
470 if (to_read <= 0) return 0;
471 return p_buffer->readArray(data, to_read);
472 }
473
475 size_t write(const uint8_t* data, size_t len) override {
476 if (!active || data == nullptr || len == 0) return 0;
477 if (p_buffer == nullptr) return 0;
478 int to_write = min(static_cast<int>(len), p_buffer->availableForWrite());
479 if (to_write <= 0) return 0;
480 return p_buffer->writeArray(data, to_write);
481 }
482
484 void clearBuffer() {
485 if (p_buffer != nullptr) p_buffer->reset();
486 }
487
489 const char* mimeType() { return mime.c_str(); }
490
491 protected:
502
504 bool active = false;
509
510#if defined(ESP32)
511 bool esp32_spi_active = false;
512 uint8_t spi_tx_byte = 0;
513 uint8_t spi_rx_byte = 0;
514#endif
515
516 // protocol state
518 uint8_t current_cmd = 0;
520 uint16_t rx_pos = 0;
521 uint16_t tx_pos = 0;
522 uint16_t tx_len = 0;
523 uint8_t tx_status = Ok;
524
527 state = RxCmd;
528 current_cmd = 0;
530 rx_pos = 0;
531 tx_pos = 0;
532 tx_len = 0;
533 tx_status = Ok;
534 }
535
540 tx_len = 0;
541 tx_pos = 0;
542 return;
543 }
544
545 uint16_t out_len = 0;
549 static_cast<uint16_t>(response_payload.size()), out_len);
550 tx_len = out_len;
551 tx_pos = 0;
552 }
553
555 uint8_t processCommand(SPIAudioCommand cmd, const uint8_t* payload,
556 uint16_t payload_len, uint8_t* response,
557 uint16_t response_max, uint16_t& response_len) {
558 response_len = 0;
559
560 switch (cmd) {
562 if (payload_len == 0 || payload == nullptr) return InvalidPayload;
563 size_t max_len = config.max_mime_len > 0 ? config.max_mime_len - 1 : 0;
564 size_t n = min(static_cast<size_t>(payload_len), max_len);
565 if (n > 0 && payload[n - 1] == '\0') {
566 --n;
567 }
568 mime.copyFrom(reinterpret_cast<const char*>(payload), n, max_len);
569 return Ok;
570 }
571
573 if (payload_len != 6 || payload == nullptr) return InvalidPayload;
575 info.sample_rate = readU32LE(payload);
576 info.channels = payload[4];
577 info.bits_per_sample = payload[5];
579 return Ok;
580 }
581
583 if (payload_len == 0 || payload == nullptr) return Ok;
584 if (p_buffer == nullptr) return InternalError;
585 int written = p_buffer->writeArray(payload, payload_len);
586 return written == static_cast<int>(payload_len) ? Ok : BufferOverflow;
587 }
588
590 if (response == nullptr || response_max < 4) return InternalError;
591 if (p_buffer == nullptr) return InternalError;
592 int avail = p_buffer->availableForWrite();
593 writeU32LE(response, static_cast<uint32_t>(avail > 0 ? avail : 0));
594 response_len = 4;
595 return Ok;
596 }
597
599 if (p_buffer != nullptr) p_buffer->reset();
600 return Ok;
601 }
602
604 if (response == nullptr || response_max < 4) return InternalError;
605 if (p_buffer == nullptr) return InternalError;
606 int filled = p_buffer->available();
607 writeU32LE(response, static_cast<uint32_t>(filled > 0 ? filled : 0));
608 response_len = 4;
609 return Ok;
610 }
611
612 default:
613 return InvalidCommand;
614 }
615 }
616
618 static void writeU32LE(uint8_t* out, uint32_t value) {
619 out[0] = static_cast<uint8_t>(value & 0xFF);
620 out[1] = static_cast<uint8_t>((value >> 8) & 0xFF);
621 out[2] = static_cast<uint8_t>((value >> 16) & 0xFF);
622 out[3] = static_cast<uint8_t>((value >> 24) & 0xFF);
623 }
624
626 static uint32_t readU32LE(const uint8_t* in) {
627 return static_cast<uint32_t>(in[0]) | (static_cast<uint32_t>(in[1]) << 8) |
628 (static_cast<uint32_t>(in[2]) << 16) |
629 (static_cast<uint32_t>(in[3]) << 24);
630 }
631};
632
633} // namespace audio_tools
#define LOGI(...)
Definition AudioLoggerIDF.h:28
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:120
AudioInfo info
Definition BaseStream.h:171
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition BaseStream.h:128
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition BaseStream.h:151
Shared functionality of all buffers.
Definition Buffers.h:23
virtual int readArray(T data[], int len)
reads multiple values
Definition Buffers.h:34
virtual void reset()=0
clears the buffer
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition Buffers.h:56
virtual int availableForWrite()=0
provides the number of entries that are available to write
virtual int available()=0
provides the number of entries that are available to read
SPI Audio Slave endpoint matching SPIAudioMaster protocol.
Definition SPIAudioSlave.h:237
bool active
Definition SPIAudioSlave.h:504
uint8_t tx_status
Definition SPIAudioSlave.h:523
uint16_t expected_payload_len
Definition SPIAudioSlave.h:519
void clearBuffer()
Clears local buffered audio data.
Definition SPIAudioSlave.h:484
BaseBuffer< uint8_t > * p_buffer
Definition SPIAudioSlave.h:505
uint8_t spi_tx_byte
Definition SPIAudioSlave.h:512
bool processFrame(const uint8_t *request_frame, uint16_t request_frame_len, uint8_t *response_frame, uint16_t response_frame_max, uint16_t *response_frame_len=nullptr)
Frame-based helper (same wire format as master, useful for tests)
Definition SPIAudioSlave.h:424
bool isActive() const
Returns true when the slave is active.
Definition SPIAudioSlave.h:314
static void writeU32LE(uint8_t *out, uint32_t value)
Writes a 32-bit little-endian integer to the target buffer.
Definition SPIAudioSlave.h:618
const char * mimeType()
Returns the currently configured mime type.
Definition SPIAudioSlave.h:489
SPIAudioSlaveConfig config
Definition SPIAudioSlave.h:503
uint8_t current_cmd
Definition SPIAudioSlave.h:518
size_t readBytes(uint8_t *data, size_t len) override
Reads buffered audio bytes.
Definition SPIAudioSlave.h:466
bool esp32_spi_active
Definition SPIAudioSlave.h:511
uint16_t rx_pos
Definition SPIAudioSlave.h:520
static uint32_t readU32LE(const uint8_t *in)
Reads a 32-bit little-endian integer from the source buffer.
Definition SPIAudioSlave.h:626
uint8_t spi_rx_byte
Definition SPIAudioSlave.h:513
void end() override
Stops the slave and releases all resources.
Definition SPIAudioSlave.h:301
int available() override
Returns readable bytes currently buffered.
Definition SPIAudioSlave.h:456
void resetProtocolState()
Resets internal request/response parser state.
Definition SPIAudioSlave.h:526
size_t write(const uint8_t *data, size_t len) override
Writes bytes directly into the local slave buffer.
Definition SPIAudioSlave.h:475
StatusCode
Definition SPIAudioSlave.h:239
@ Ok
Definition SPIAudioSlave.h:240
@ BufferOverflow
Definition SPIAudioSlave.h:243
@ InvalidPayload
Definition SPIAudioSlave.h:242
@ InvalidCommand
Definition SPIAudioSlave.h:241
@ InternalError
Definition SPIAudioSlave.h:244
int availableForWrite() override
Returns remaining writable bytes in the input buffer.
Definition SPIAudioSlave.h:461
uint8_t processCommand(SPIAudioCommand cmd, const uint8_t *payload, uint16_t payload_len, uint8_t *response, uint16_t response_max, uint16_t &response_len)
Executes one protocol command and writes response payload.
Definition SPIAudioSlave.h:555
Vector< uint8_t > request_payload
Definition SPIAudioSlave.h:506
Vector< uint8_t > response_payload
Definition SPIAudioSlave.h:507
uint16_t tx_len
Definition SPIAudioSlave.h:522
void prepareResponse()
Evaluates current request payload and prepares response payload.
Definition SPIAudioSlave.h:537
ProtocolState
Definition SPIAudioSlave.h:492
@ RxCmd
Definition SPIAudioSlave.h:493
@ RxLenHi
Definition SPIAudioSlave.h:495
@ TxPayload
Definition SPIAudioSlave.h:500
@ RxPayload
Definition SPIAudioSlave.h:496
@ TxLenHi
Definition SPIAudioSlave.h:499
@ RxLenLo
Definition SPIAudioSlave.h:494
@ TxStatus
Definition SPIAudioSlave.h:497
@ TxLenLo
Definition SPIAudioSlave.h:498
ProtocolState state
Definition SPIAudioSlave.h:517
bool begin(SPIAudioSlaveConfig cfg=SPIAudioSlaveConfig())
Initializes protocol state, buffers and optional HW slave support.
Definition SPIAudioSlave.h:251
void setAudioInfo(AudioInfo info) override
Updates current audio format and mirrors it into config.
Definition SPIAudioSlave.h:317
uint16_t tx_pos
Definition SPIAudioSlave.h:521
SPIAudioSlave(BaseBuffer< uint8_t > &buffer)
Constructor using an externally provided buffer implementation.
Definition SPIAudioSlave.h:248
uint8_t onTransfer(uint8_t in)
Byte-wise SPI handler. Call this for each received SPI byte.
Definition SPIAudioSlave.h:353
bool process()
Definition SPIAudioSlave.h:325
Str mime
Definition SPIAudioSlave.h:508
Str which keeps the data on the heap. We grow the allocated memory only if the copy source is not fit...
Definition Str.h:24
void copyFrom(const char *source, int len, int maxlen=0)
assigns a memory buffer
Definition Str.h:96
void setCapacity(size_t newLen)
Definition Str.h:86
virtual const char * c_str()
provides the string value as const char*
Definition StrView.h:380
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
bool resize(size_t newSize, T value)
Definition Vector.h:266
T * data()
Definition Vector.h:316
int size()
Definition Vector.h:178
SPIAudioCommand
SPI command ids used by SPIAudioMaster.
Definition SPIAudioCommand.h:19
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:51
void copyFrom(AudioInfo info)
Same as set.
Definition AudioTypes.h:101
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:57
Configuration for SPIAudioSlave.
Definition SPIAudioSlave.h:31
int pin_mosi
Definition SPIAudioSlave.h:49
int pin_sclk
Definition SPIAudioSlave.h:51
int pin_miso
Definition SPIAudioSlave.h:50
int dma_chan
Definition SPIAudioSlave.h:48
int pin_cs
Definition SPIAudioSlave.h:52
void log() const
Definition SPIAudioSlave.h:57
bool use_hw_slave
Definition SPIAudioSlave.h:36
size_t max_command_payload
Definition SPIAudioSlave.h:32
int queue_size
Definition SPIAudioSlave.h:55
size_t max_response_payload
Definition SPIAudioSlave.h:33
spi_host_device_t host
Definition SPIAudioSlave.h:46
size_t max_mime_len
Definition SPIAudioSlave.h:34