arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
I2SSTM32.h
1#pragma once
2
3#ifdef STM32
4#include "AudioTools/CoreAudio/AudioI2S/I2SConfig.h"
5#include "stm32-i2s.h"
6
7#ifdef STM_I2S_PINS
8#define IS_I2S_IMPLEMENTED
9
10namespace audio_tools {
11
24class I2SDriverSTM32 {
25 friend class I2SStream;
26
27 public:
29 I2SConfigStd defaultConfig(RxTxMode mode = TX_MODE) {
30 I2SConfigStd c(mode);
31 return c;
32 }
33
35 bool setAudioInfo(AudioInfo) { return false;}
36
38 bool begin(RxTxMode mode = TX_MODE) {
39 TRACED();
40 return begin(defaultConfig(mode));
41 }
42
44 bool begin(I2SConfigStd cfg) {
45 // TRACED();
46 this->cfg = cfg;
47 bool result = false;
48 deleteBuffers();
49 LOGI("buffer_size: %d", cfg.buffer_size);
50 LOGI("buffer_count: %d", cfg.buffer_count);
51
52 if (cfg.channels > 2 || cfg.channels <= 0) {
53 LOGE("Channels not supported: %d", cfg.channels);
54 return false;
55 }
56
57 setupDefaultI2SParameters();
58 setupPins();
59 result = use_dma ? startI2SDMA() : startI2S();
60 this->active = result;
61 return result;
62 }
63
65 void end() {
66 // TRACED();
67 i2s.end();
68 deleteBuffers();
69 active = false;
70 }
71
73 int available() {
74 if (!active) return 0;
75 if (use_dma && p_rx_buffer == nullptr) return 0;
76 return cfg.buffer_size;
77 }
78
80 int availableForWrite() {
81 if (!active) return 0;
82 if (use_dma && p_tx_buffer == nullptr) return 0;
83 return cfg.buffer_size;
84 }
85
87 I2SConfigStd config() { return cfg; }
88
90 size_t writeBytes(const void *src, size_t size_bytes) {
91 TRACED();
92 size_t result = 0;
93 if (!use_dma) {
94 result = i2s.write((uint8_t *)src, size_bytes);
95 } else {
96 // if we have an input stream we do not need to fill the buffer
97 if (p_dma_in != nullptr) {
98 // by calling writeBytes we activate the automatic timeout handling
99 // and expect further writes to continue the output
100 last_write_ms = millis();
101 result = size_bytes;
102 } else {
103 // fill buffer
104 result = writeBytesDMA(src, size_bytes);
105 }
106 }
107 return result;
108 }
109
110 size_t readBytes(void *dest, size_t size_bytes) {
111 TRACED();
112 if (!use_dma) {
113 return i2s.readBytes((uint8_t *)dest, size_bytes);
114 } else {
115 if (cfg.channels == 2) {
116 return p_rx_buffer->readArray((uint8_t *)dest, size_bytes);
117 } else {
118 return readBytesDMA(dest, size_bytes);
119 }
120 }
121 }
122
125 static void writeFromReceive(uint8_t *buffer, uint16_t byteCount, void *ref) {
126 I2SDriverSTM32 *self = (I2SDriverSTM32 *)ref;
127 uint16_t written = 0;
128 if (self->p_dma_out != nullptr)
129 written = self->p_dma_out->write(buffer, byteCount);
130 else
131 written = self->p_rx_buffer->writeArray(buffer, byteCount);
132
133 // check for overflow
134 if (written != byteCount) {
135 LOGW("Buffer overflow: written %d of %d", written, byteCount);
136 }
137 }
138
141 static void readToTransmit(uint8_t *buffer, uint16_t byteCount, void *ref) {
142 I2SDriverSTM32 *self = (I2SDriverSTM32 *)ref;
143 static size_t count = 0;
144 size_t read = 0;
145 if (self->p_dma_in != nullptr) {
146 // stop reading if timout is relevant
147 if (self->isWriteTimedOut()) {
148 // we just provide silence
149 read = byteCount;
150 } else {
151 // get data from stream
152 read = self->p_dma_in->readBytes(buffer, byteCount);
153 }
154 } else {
155 // get data from buffer
156 if (self->stm32_write_active) {
157 read = self->p_tx_buffer->readArray(buffer, byteCount);
158 }
159 }
160 memset(buffer+read, 0, byteCount-read);
161
162 // check for underflow
163 count++;
164 if (read != byteCount) {
165 LOGW("Buffer underflow at %lu: %d for %d", count, read, byteCount);
166 }
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
189 protected:
190 stm32_i2s::Stm32I2sClass i2s;
191 stm32_i2s::I2SSettingsSTM32 i2s_stm32;
192 I2SConfigStd cfg;
193 bool active = false;
194 bool result = true;
195 BaseBuffer<uint8_t> *p_tx_buffer = nullptr;
196 BaseBuffer<uint8_t> *p_rx_buffer = nullptr;
197 volatile bool stm32_write_active = false;
198 bool use_dma = true;
199 Print *p_dma_out = nullptr;
200 Stream *p_dma_in = nullptr;
201 uint32_t last_write_ms = 0;
202
203 size_t writeBytesDMA(const void *src, size_t size_bytes) {
204 size_t result = 0;
205 // fill the tx buffer
206 int open = size_bytes;
207 while (open > 0) {
208 int actual_written = writeBytesExt(src, size_bytes);
209 result += actual_written;
210 open -= actual_written;
211 if (open > 0) {
212 stm32_write_active = true;
213 //delay(1);
214 }
215 }
216
217 // start output of data only when buffer has been filled
218 if (!stm32_write_active && p_tx_buffer->availableForWrite() == 0) {
219 stm32_write_active = true;
220 LOGI("Buffer is full->starting i2s output");
221 }
222
223 return size_bytes;
224 }
225
226 size_t readBytesDMA(void *dest, size_t size_bytes) {
227 // combine two channels to 1: so request double the amout
228 int req_bytes = size_bytes * 2;
229 uint8_t tmp[req_bytes];
230 int16_t *tmp_16 = (int16_t *)tmp;
231 int eff_bytes = p_rx_buffer->readArray((uint8_t *)tmp, req_bytes);
232 // add 2 channels together
233 int16_t *dest_16 = (int16_t *)dest;
234 int16_t eff_samples = eff_bytes / 2;
235 int16_t idx = 0;
236 for (int j = 0; j < eff_samples; j += 2) {
237 dest_16[idx++] = static_cast<float>(tmp_16[j]) + tmp_16[j + 1] / 2.0;
238 }
239 return eff_bytes / 2;
240 }
241
242 bool startI2S() {
243 switch (cfg.rx_tx_mode) {
244 case RX_MODE:
245 result = i2s.begin(i2s_stm32, false, true);
246 break;
247 case TX_MODE:
248 result = i2s.begin(i2s_stm32, true, false);
249 break;
250 case RXTX_MODE:
251 default:
252 result = i2s.begin(i2s_stm32, true, true);
253 break;
254 }
255 return result;
256 }
257
258 bool startI2SDMA() {
259 switch (cfg.rx_tx_mode) {
260 case RX_MODE:
261 if (use_dma && p_rx_buffer == nullptr)
262 p_rx_buffer = allocateBuffer();
263 result = i2s.beginReadDMA(i2s_stm32, writeFromReceive);
264 break;
265 case TX_MODE:
266 stm32_write_active = false;
267 if (use_dma && p_tx_buffer == nullptr)
268 p_tx_buffer = allocateBuffer();
269 result = i2s.beginWriteDMA(i2s_stm32, readToTransmit);
270 break;
271
272 case RXTX_MODE:
273 if (use_dma) {
274 stm32_write_active = false;
275 if (p_rx_buffer == nullptr)
276 p_rx_buffer = allocateBuffer();
277 if (p_tx_buffer == nullptr)
278 p_tx_buffer = allocateBuffer();
279 }
280 result = i2s.beginReadWriteDMA(
281 i2s_stm32, readToTransmit, writeFromReceive);
282 break;
283
284 default:
285 LOGE("Unsupported mode");
286 return false;
287 }
288 return result;
289 }
290
291 uint32_t toDataFormat(int bits_per_sample) {
292 switch (bits_per_sample) {
293 case 16:
294 return I2S_DATAFORMAT_16B;
295 case 24:
296 return I2S_DATAFORMAT_24B;
297 case 32:
298 return I2S_DATAFORMAT_32B;
299 }
300 return I2S_DATAFORMAT_16B;
301 }
302
303 void deleteBuffers() {
304 if (p_rx_buffer != nullptr) {
305 delete p_rx_buffer;
306 p_rx_buffer = nullptr;
307 }
308 if (p_tx_buffer != nullptr) {
309 delete p_tx_buffer;
310 p_tx_buffer = nullptr;
311 }
312 }
313
314 void setupDefaultI2SParameters() {
315 i2s_stm32.sample_rate = getSampleRate(cfg);
316 i2s_stm32.data_format = toDataFormat(cfg.bits_per_sample);
317 i2s_stm32.mode = getMode(cfg);
318 i2s_stm32.standard = getStandard(cfg);
319 i2s_stm32.fullduplexmode = cfg.rx_tx_mode == RXTX_MODE
320 ? I2S_FULLDUPLEXMODE_ENABLE
321 : I2S_FULLDUPLEXMODE_DISABLE;
322 i2s_stm32.hardware_config.buffer_size = cfg.buffer_size;
323 // provide ourself as parameter to callback
324 i2s_stm32.ref = this;
325 }
326
327 void setupPins(){
328 if (cfg.pin_bck == -1 || cfg.pin_ws == -1 || cfg.pin_data == -1) {
329 LOGW("pins ignored: used from stm32-i2s");
330 } else {
331 LOGI("setting up pins for stm32-i2s");
332 i2s_stm32.hardware_config.pins[0].function = stm32_i2s::mclk;
333 i2s_stm32.hardware_config.pins[0].pin = digitalPinToPinName(cfg.pin_mck);
334 i2s_stm32.hardware_config.pins[0].altFunction = cfg.pin_alt_function;
335
336 i2s_stm32.hardware_config.pins[1].function = stm32_i2s::bck;
337 i2s_stm32.hardware_config.pins[1].pin = digitalPinToPinName(cfg.pin_bck);
338 i2s_stm32.hardware_config.pins[1].altFunction = cfg.pin_alt_function;
339
340 i2s_stm32.hardware_config.pins[2].function = stm32_i2s::ws;
341 i2s_stm32.hardware_config.pins[2].pin = digitalPinToPinName(cfg.pin_ws);
342 i2s_stm32.hardware_config.pins[2].altFunction = cfg.pin_alt_function;
343
344 switch (cfg.rx_tx_mode) {
345 case TX_MODE:
346 i2s_stm32.hardware_config.pins[3].function = stm32_i2s::data_out;
347 i2s_stm32.hardware_config.pins[3].pin = digitalPinToPinName(cfg.pin_data);
348 i2s_stm32.hardware_config.pins[3].altFunction = cfg.pin_alt_function;
349 break;
350 case RX_MODE:
351 i2s_stm32.hardware_config.pins[4].function = stm32_i2s::data_in;
352 i2s_stm32.hardware_config.pins[4].pin = digitalPinToPinName(cfg.pin_data);
353 i2s_stm32.hardware_config.pins[4].altFunction = cfg.pin_alt_function;
354 break;
355 case RXTX_MODE:
356 i2s_stm32.hardware_config.pins[3].function = stm32_i2s::data_out;
357 i2s_stm32.hardware_config.pins[3].pin = digitalPinToPinName(cfg.pin_data);
358 i2s_stm32.hardware_config.pins[3].altFunction = cfg.pin_alt_function;
359
360 i2s_stm32.hardware_config.pins[4].function = stm32_i2s::data_in;
361 i2s_stm32.hardware_config.pins[4].pin = digitalPinToPinName(cfg.pin_data);
362 i2s_stm32.hardware_config.pins[4].altFunction = cfg.pin_alt_function;
363 break;
364 };
365
366 }
367 }
368
369 uint32_t getMode(I2SConfigStd &cfg) {
370 if (cfg.is_master) {
371 switch (cfg.rx_tx_mode) {
372 case RX_MODE:
373 return I2S_MODE_MASTER_RX;
374 case TX_MODE:
375 return I2S_MODE_MASTER_TX;
376 default:
377 LOGE("RXTX_MODE not supported");
378 return I2S_MODE_MASTER_TX;
379 }
380 } else {
381 switch (cfg.rx_tx_mode) {
382 case RX_MODE:
383 return I2S_MODE_SLAVE_RX;
384 case TX_MODE:
385 return I2S_MODE_SLAVE_TX;
386 default:
387 LOGE("RXTX_MODE not supported");
388 return I2S_MODE_SLAVE_TX;
389 }
390 }
391 }
392
393 uint32_t getStandard(I2SConfigStd &cfg) {
394 uint32_t result;
395 switch (cfg.i2s_format) {
396 case I2S_PHILIPS_FORMAT:
397 return I2S_STANDARD_PHILIPS;
398 case I2S_STD_FORMAT:
399 case I2S_LSB_FORMAT:
400 case I2S_RIGHT_JUSTIFIED_FORMAT:
401 return I2S_STANDARD_MSB;
402 case I2S_MSB_FORMAT:
403 case I2S_LEFT_JUSTIFIED_FORMAT:
404 return I2S_STANDARD_LSB;
405 }
406 return I2S_STANDARD_PHILIPS;
407 }
408
409 uint32_t getSampleRate(I2SConfigStd &cfg) {
410 switch (cfg.sample_rate) {
411 case I2S_AUDIOFREQ_192K:
412 case I2S_AUDIOFREQ_96K:
413 case I2S_AUDIOFREQ_48K:
414 case I2S_AUDIOFREQ_44K:
415 case I2S_AUDIOFREQ_32K:
416 case I2S_AUDIOFREQ_22K:
417 case I2S_AUDIOFREQ_16K:
418 case I2S_AUDIOFREQ_11K:
419 case I2S_AUDIOFREQ_8K:
420 return cfg.sample_rate;
421 default:
422 LOGE("Unsupported sample rate: %u", cfg.sample_rate);
423 return cfg.sample_rate;
424 }
425 }
426
427 size_t writeBytesExt(const void *src, size_t size_bytes) {
428 size_t result = 0;
429 if (cfg.channels == 2) {
430 result = p_tx_buffer->writeArray((uint8_t *)src, size_bytes);
431 } else {
432 // write each sample 2 times
433 int samples = size_bytes / 2;
434 int16_t *src_16 = (int16_t *)src;
435 int16_t tmp[2];
436 int result = 0;
437 for (int j = 0; j < samples; j++) {
438 tmp[0] = src_16[j];
439 tmp[1] = src_16[j];
440 if (p_tx_buffer->availableForWrite() >= 4) {
441 p_tx_buffer->writeArray((uint8_t *)tmp, 4);
442 result = j * 2;
443 } else {
444 // abort if the buffer is full
445 break;
446 }
447 }
448 }
449 LOGD("writeBytesExt: %u", result)
450 return result;
451 }
452
453 BaseBuffer<uint8_t>* allocateBuffer() {
454 //return new RingBuffer<uint8_t>(cfg.buffer_size * cfg.buffer_count);
455 return new NBuffer<uint8_t>(cfg.buffer_size, cfg.buffer_count);
456 }
457};
458
459using I2SDriver = I2SDriverSTM32;
460
461} // namespace audio_tools
462
463#endif
464#endif
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition AudioTypes.h:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
uint32_t millis()
Returns the milliseconds since the start.
Definition Time.h:12