arduino-audio-tools
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 
10 namespace audio_tools {
11 
24 class 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 
459 using 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: AudioConfig.h:872
uint32_t millis()
Returns the milliseconds since the start.
Definition: Time.h:12