arduino-audio-tools
Loading...
Searching...
No Matches
I2SDriverSTM32.h
Go to the documentation of this file.
1#pragma once
2
3#ifdef STM32
7#include "stm32-i2s.h"
8
9#ifdef STM_I2S_PINS
10#define IS_I2S_IMPLEMENTED
11
12namespace audio_tools {
13
26class I2SDriverSTM32 : public I2SDriverBase {
27 friend class I2SStream;
28
29 public:
31 I2SConfigStd defaultConfig(RxTxMode mode = TX_MODE) {
32 I2SConfigStd c(mode);
33 return c;
34 }
35
37 bool setAudioInfo(AudioInfo) { return false;}
38
40 bool begin(RxTxMode mode = TX_MODE) {
41 TRACED();
42 return begin(defaultConfig(mode));
43 }
44
46 bool begin(I2SConfigStd cfg) {
47 // TRACED();
48 this->cfg = cfg;
49 bool result = false;
50 deleteBuffers();
51 LOGI("buffer_size: %d", cfg.buffer_size);
52 LOGI("buffer_count: %d", cfg.buffer_count);
53
54 if (cfg.channels > 2 || cfg.channels <= 0) {
55 LOGE("Channels not supported: %d", cfg.channels);
56 return false;
57 }
58
59 setupDefaultI2SParameters();
60 setupPins();
61 result = use_dma ? startI2SDMA() : startI2S();
62 this->active = result;
63 return result;
64 }
65
67 void end() {
68 // TRACED();
69 i2s.end();
70 deleteBuffers();
71 active = false;
72 }
73
75 int available() {
76 if (!active) return 0;
77 if (use_dma && p_rx_buffer == nullptr) return 0;
78 return cfg.buffer_size;
79 }
80
82 int availableForWrite() {
83 if (!active) return 0;
84 if (use_dma && p_tx_buffer == nullptr) return 0;
85 return cfg.buffer_size;
86 }
87
89 I2SConfigStd config() { return cfg; }
90
92 size_t writeBytes(const void *src, size_t size_bytes) {
93 TRACED();
94 size_t result = 0;
95 if (!use_dma) {
96 result = i2s.write((uint8_t *)src, size_bytes);
97 } else {
98 // if we have an input stream we do not need to fill the buffer
99 if (p_dma_in != nullptr) {
100 // by calling writeBytes we activate the automatic timeout handling
101 // and expect further writes to continue the output
102 last_write_ms = millis();
103 result = size_bytes;
104 } else {
105 // fill buffer
106 result = writeBytesDMA(src, size_bytes);
107 }
108 }
109 return result;
110 }
111
112 size_t readBytes(void *dest, size_t size_bytes) {
113 TRACED();
114 if (!use_dma) {
115 return i2s.readBytes((uint8_t *)dest, size_bytes);
116 } else {
117 if (cfg.channels == 2) {
118 return p_rx_buffer->readArray((uint8_t *)dest, size_bytes);
119 } else {
120 return readBytesDMA(dest, size_bytes);
121 }
122 }
123 }
124
127 static void writeFromReceive(uint8_t *buffer, uint16_t byteCount, void *ref) {
128 I2SDriverSTM32 *self = (I2SDriverSTM32 *)ref;
129 // called from the DMA ISR: must not log/print here - Serial I/O relies on
130 // interrupts (SysTick, USB) that are prio-blocked while this ISR runs,
131 // so it would deadlock forever
132 uint16_t written = 0;
133 if (self->p_dma_out != nullptr)
134 written = self->p_dma_out->write(buffer, byteCount);
135 else
136 written = self->p_rx_buffer->writeArray(buffer, byteCount);
137
138 if (written != byteCount) self->rx_overflow_count++;
139 }
140
143 static void readToTransmit(uint8_t *buffer, uint16_t byteCount, void *ref) {
144 I2SDriverSTM32 *self = (I2SDriverSTM32 *)ref;
145 // called from the DMA ISR: must not log/print here - Serial I/O relies on
146 // interrupts (SysTick, USB) that are prio-blocked while this ISR runs,
147 // so it would deadlock forever
148 size_t read = 0;
149 if (self->p_dma_in != nullptr) {
150 // stop reading if timout is relevant
151 if (self->isWriteTimedOut()) {
152 // we just provide silence
153 read = byteCount;
154 } else {
155 // get data from stream
156 read = self->p_dma_in->readBytes(buffer, byteCount);
157 }
158 } else {
159 // get data from buffer
160 if (self->stm32_write_active) {
161 read = self->p_tx_buffer->readArray(buffer, byteCount);
162 }
163 }
164 memset(buffer+read, 0, byteCount-read);
165
166 if (read != byteCount) self->tx_underflow_count++;
167 }
168
170 bool isWriteTimedOut() {
171 return last_write_ms != 0 && last_write_ms + 500 < millis();
172 }
173
175 void setDMAActive(bool flag) { use_dma = flag; }
176
178 void setDMAInputStream(Stream &in) {
179 use_dma = true;
180 p_dma_in = &in;
181 }
182
184 void setDMAOutput(Print &out) {
185 use_dma = true;
186 p_dma_out = &out;
187 }
188
190 uint32_t getUnderflowCount() { return tx_underflow_count; }
191
193 uint32_t getOverflowCount() { return rx_overflow_count; }
194
196 void resetErrorCounters() { tx_underflow_count = 0; rx_overflow_count = 0; }
197
198 protected:
199 stm32_i2s::Stm32I2sClass i2s;
200 stm32_i2s::I2SSettingsSTM32 i2s_stm32;
201 I2SConfigStd cfg;
202 bool active = false;
203 bool result = true;
204 BaseBuffer<uint8_t> *p_tx_buffer = nullptr;
205 BaseBuffer<uint8_t> *p_rx_buffer = nullptr;
206 volatile bool stm32_write_active = false;
207 bool use_dma = true;
208 // incremented from the DMA ISR callbacks: must stay lock-free (no logging!)
209 volatile uint32_t tx_underflow_count = 0;
210 volatile uint32_t rx_overflow_count = 0;
211 Print *p_dma_out = nullptr;
212 Stream *p_dma_in = nullptr;
213 uint32_t last_write_ms = 0;
214
215 size_t writeBytesDMA(const void *src, size_t size_bytes) {
216 size_t result = 0;
217 // fill the tx buffer
218 const uint8_t *p_src = (const uint8_t *)src;
219 int open = size_bytes;
220 while (open > 0) {
221 int actual_written = writeBytesExt(p_src, open);
222 if (actual_written <= 0) {
223 // tx buffer is full: caller will retry the remainder later
224 stm32_write_active = true;
225 break;
226 }
227 p_src += actual_written;
228 result += actual_written;
229 open -= actual_written;
230 }
231
232 // start output of data only when buffer has been filled
233 // p_tx_buffer is a lock-free SPSCByteRingBuffer: safe to read concurrently
234 // with the DMA ISR without a critical section
235 if (!stm32_write_active && p_tx_buffer->availableForWrite() == 0) {
236 stm32_write_active = true;
237 LOGI("Buffer is full->starting i2s output");
238 }
239
240 return result;
241 }
242
243 size_t readBytesDMA(void *dest, size_t size_bytes) {
244 // combine two channels to 1: so request double the amout
245 int req_bytes = size_bytes * 2;
246 uint8_t tmp[req_bytes];
247 int16_t *tmp_16 = (int16_t *)tmp;
248 int eff_bytes = p_rx_buffer->readArray((uint8_t *)tmp, req_bytes);
249 // add 2 channels together
250 int16_t *dest_16 = (int16_t *)dest;
251 int16_t eff_samples = eff_bytes / 2;
252 int16_t idx = 0;
253 for (int j = 0; j < eff_samples; j += 2) {
254 dest_16[idx++] = static_cast<float>(tmp_16[j]) + tmp_16[j + 1] / 2.0;
255 }
256 return eff_bytes / 2;
257 }
258
259 bool startI2S() {
260 switch (cfg.rx_tx_mode) {
261 case RX_MODE:
262 result = i2s.begin(i2s_stm32, false, true);
263 break;
264 case TX_MODE:
265 result = i2s.begin(i2s_stm32, true, false);
266 break;
267 case RXTX_MODE:
268 default:
269 result = i2s.begin(i2s_stm32, true, true);
270 break;
271 }
272 return result;
273 }
274
275 bool startI2SDMA() {
276 switch (cfg.rx_tx_mode) {
277 case RX_MODE:
278 if (use_dma && p_rx_buffer == nullptr)
279 p_rx_buffer = allocateBuffer();
280 result = i2s.beginReadDMA(i2s_stm32, writeFromReceive);
281 break;
282 case TX_MODE:
283 stm32_write_active = false;
284 if (use_dma && p_tx_buffer == nullptr)
285 p_tx_buffer = allocateBuffer();
286 result = i2s.beginWriteDMA(i2s_stm32, readToTransmit);
287 break;
288
289 case RXTX_MODE:
290 if (use_dma) {
291 stm32_write_active = false;
292 if (p_rx_buffer == nullptr)
293 p_rx_buffer = allocateBuffer();
294 if (p_tx_buffer == nullptr)
295 p_tx_buffer = allocateBuffer();
296 }
297 result = i2s.beginReadWriteDMA(
298 i2s_stm32, readToTransmit, writeFromReceive);
299 break;
300
301 default:
302 LOGE("Unsupported mode");
303 return false;
304 }
305 return result;
306 }
307
308 uint32_t toDataFormat(int bits_per_sample) {
309 switch (bits_per_sample) {
310 case 16:
311 return I2S_DATAFORMAT_16B;
312 case 24:
313 return I2S_DATAFORMAT_24B;
314 case 32:
315 return I2S_DATAFORMAT_32B;
316 }
317 return I2S_DATAFORMAT_16B;
318 }
319
320 void deleteBuffers() {
321 if (p_rx_buffer != nullptr) {
322 delete p_rx_buffer;
323 p_rx_buffer = nullptr;
324 }
325 if (p_tx_buffer != nullptr) {
326 delete p_tx_buffer;
327 p_tx_buffer = nullptr;
328 }
329 }
330
331 void setupDefaultI2SParameters() {
332 i2s_stm32.sample_rate = getSampleRate(cfg);
333 i2s_stm32.data_format = toDataFormat(cfg.bits_per_sample);
334 i2s_stm32.mode = getMode(cfg);
335 i2s_stm32.standard = getStandard(cfg);
336 i2s_stm32.fullduplexmode = cfg.rx_tx_mode == RXTX_MODE
337 ? I2S_FULLDUPLEXMODE_ENABLE
338 : I2S_FULLDUPLEXMODE_DISABLE;
339 i2s_stm32.hardware_config.buffer_size = cfg.buffer_size;
340 // provide ourself as parameter to callback
341 i2s_stm32.ref = this;
342 }
343
344 void setupPins(){
345 if (cfg.pin_bck == -1 || cfg.pin_ws == -1 || cfg.pin_data == -1) {
346 LOGW("pins ignored: used from stm32-i2s");
347 } else {
348 LOGI("setting up pins for stm32-i2s");
349 // cfg.pin_alt_function defaults to -1 (not explicitly set by the
350 // sketch/board). -1 is not a valid AF index, so only override the
351 // board's compiled-in default (from STM_I2S_PINS, which differs per
352 // pin/board - e.g. BLACKPILL's data_out needs AF7, everything else AF6)
353 // when the caller actually asked for a specific one.
354 i2s_stm32.hardware_config.pins[0].function = stm32_i2s::mclk;
355 i2s_stm32.hardware_config.pins[0].pin = digitalPinToPinName(cfg.pin_mck);
356 if (cfg.pin_alt_function != -1)
357 i2s_stm32.hardware_config.pins[0].altFunction = cfg.pin_alt_function;
358
359 i2s_stm32.hardware_config.pins[1].function = stm32_i2s::bck;
360 i2s_stm32.hardware_config.pins[1].pin = digitalPinToPinName(cfg.pin_bck);
361 if (cfg.pin_alt_function != -1)
362 i2s_stm32.hardware_config.pins[1].altFunction = cfg.pin_alt_function;
363
364 i2s_stm32.hardware_config.pins[2].function = stm32_i2s::ws;
365 i2s_stm32.hardware_config.pins[2].pin = digitalPinToPinName(cfg.pin_ws);
366 if (cfg.pin_alt_function != -1)
367 i2s_stm32.hardware_config.pins[2].altFunction = cfg.pin_alt_function;
368
369 switch (cfg.rx_tx_mode) {
370 case TX_MODE:
371 i2s_stm32.hardware_config.pins[3].function = stm32_i2s::data_out;
372 i2s_stm32.hardware_config.pins[3].pin = digitalPinToPinName(cfg.pin_data);
373 if (cfg.pin_alt_function != -1)
374 i2s_stm32.hardware_config.pins[3].altFunction = cfg.pin_alt_function;
375 break;
376 case RX_MODE:
377 i2s_stm32.hardware_config.pins[4].function = stm32_i2s::data_in;
378 i2s_stm32.hardware_config.pins[4].pin = digitalPinToPinName(cfg.pin_data);
379 if (cfg.pin_alt_function != -1)
380 i2s_stm32.hardware_config.pins[4].altFunction = cfg.pin_alt_function;
381 break;
382 case RXTX_MODE:
383 i2s_stm32.hardware_config.pins[3].function = stm32_i2s::data_out;
384 i2s_stm32.hardware_config.pins[3].pin = digitalPinToPinName(cfg.pin_data);
385 if (cfg.pin_alt_function != -1)
386 i2s_stm32.hardware_config.pins[3].altFunction = cfg.pin_alt_function;
387
388 i2s_stm32.hardware_config.pins[4].function = stm32_i2s::data_in;
389 i2s_stm32.hardware_config.pins[4].pin = digitalPinToPinName(cfg.pin_data);
390 if (cfg.pin_alt_function != -1)
391 i2s_stm32.hardware_config.pins[4].altFunction = cfg.pin_alt_function;
392 break;
393 };
394
395 }
396 }
397
398 uint32_t getMode(I2SConfigStd &cfg) {
399 if (cfg.is_master) {
400 switch (cfg.rx_tx_mode) {
401 case RX_MODE:
402 return I2S_MODE_MASTER_RX;
403 case TX_MODE:
404 return I2S_MODE_MASTER_TX;
405 default:
406 LOGE("RXTX_MODE not supported");
407 return I2S_MODE_MASTER_TX;
408 }
409 } else {
410 switch (cfg.rx_tx_mode) {
411 case RX_MODE:
412 return I2S_MODE_SLAVE_RX;
413 case TX_MODE:
414 return I2S_MODE_SLAVE_TX;
415 default:
416 LOGE("RXTX_MODE not supported");
417 return I2S_MODE_SLAVE_TX;
418 }
419 }
420 }
421
422 uint32_t getStandard(I2SConfigStd &cfg) {
423 // I2S_STD_FORMAT is the default and means classic Philips I2S, same as
424 // I2S_PHILIPS_FORMAT - both must map to I2S_STANDARD_PHILIPS. The
425 // LSB/MSB cases were previously swapped (I2S_LSB_FORMAT ended up as
426 // I2S_STANDARD_MSB and vice versa), which shifts the data by one BCLK
427 // relative to what most codecs (e.g. CS43L22) expect and produces
428 // silence/garbled audio even though the peripheral looks "active".
429 switch (cfg.i2s_format) {
431 case I2S_STD_FORMAT:
432 return I2S_STANDARD_PHILIPS;
433 case I2S_LSB_FORMAT:
435 return I2S_STANDARD_LSB;
436 case I2S_MSB_FORMAT:
438 return I2S_STANDARD_MSB;
439 }
440 return I2S_STANDARD_PHILIPS;
441 }
442
443 uint32_t getSampleRate(I2SConfigStd &cfg) {
444 switch (cfg.sample_rate) {
445 case I2S_AUDIOFREQ_192K:
446 case I2S_AUDIOFREQ_96K:
447 case I2S_AUDIOFREQ_48K:
448 case I2S_AUDIOFREQ_44K:
449 case I2S_AUDIOFREQ_32K:
450 case I2S_AUDIOFREQ_22K:
451 case I2S_AUDIOFREQ_16K:
452 case I2S_AUDIOFREQ_11K:
453 case I2S_AUDIOFREQ_8K:
454 return cfg.sample_rate;
455 default:
456 LOGE("Unsupported sample rate: %u", cfg.sample_rate);
457 return cfg.sample_rate;
458 }
459 }
460
461 size_t writeBytesExt(const void *src, size_t size_bytes) {
462 // p_tx_buffer is a lock-free RingBufferSPSC: safe to write here on the
463 // main thread while the DMA ISR (readToTransmit) reads it concurrently,
464 // no critical section needed.
465 size_t result = 0;
466 if (cfg.channels == 2) {
467 result = p_tx_buffer->writeArray((uint8_t *)src, size_bytes);
468 } else {
469 // write each sample 2 times
470 int samples = size_bytes / 2;
471 int16_t *src_16 = (int16_t *)src;
472 int16_t tmp[2];
473 for (int j = 0; j < samples; j++) {
474 tmp[0] = src_16[j];
475 tmp[1] = src_16[j];
476 if (p_tx_buffer->availableForWrite() >= 4) {
477 p_tx_buffer->writeArray((uint8_t *)tmp, 4);
478 result = (j + 1) * 2;
479 } else {
480 // abort if the buffer is full
481 break;
482 }
483 }
484 }
485 LOGD("writeBytesExt: %u", result)
486 return result;
487 }
488
489 // NBuffer's QueueFromVector is not safe for concurrent main-thread/DMA-ISR
490 // access (see writeBytesExt/writeBytesDMA); RingBufferSPSC is a proper
491 // lock-free single-producer/single-consumer buffer, which matches exactly
492 // how p_tx_buffer/p_rx_buffer are actually used here.
493 BaseBuffer<uint8_t>* allocateBuffer() {
494 return new RingBufferSPSC<uint8_t>(cfg.buffer_size * cfg.buffer_count);
495 }
496};
497
498using I2SDriver = I2SDriverSTM32;
499
500} // namespace audio_tools
501
502#endif
503#endif
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define TRACED()
Definition AudioLoggerIDF.h:31
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGD(...)
Definition AudioLoggerIDF.h:27
#define LOGE(...)
Definition AudioLoggerIDF.h:30
Definition Arduino.h:56
Definition Arduino.h:136
@ RXTX_MODE
Definition AudioTypes.h:26
@ TX_MODE
Definition AudioTypes.h:26
@ RX_MODE
Definition AudioTypes.h:26
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
@ I2S_STD_FORMAT
Definition AudioTypes.h:417
@ I2S_PHILIPS_FORMAT
Definition AudioTypes.h:420
@ I2S_LSB_FORMAT
Definition AudioTypes.h:418
@ I2S_LEFT_JUSTIFIED_FORMAT
Definition AudioTypes.h:422
@ I2S_RIGHT_JUSTIFIED_FORMAT
Definition AudioTypes.h:421
@ I2S_MSB_FORMAT
Definition AudioTypes.h:419
I2SDriverESP32 I2SDriver
Definition I2SDriverESP32.h:403
uint32_t millis()
Returns the milliseconds since the start.
Definition Arduino.h:256
constexpr const _Ep * end(initializer_list< _Ep > __il) noexcept
Definition InitializerList.h:63
constexpr const _Ep * begin(initializer_list< _Ep > __il) noexcept
Definition InitializerList.h:55