arduino-audio-tools
I2SSTM32.h
1 #pragma once
2 
3 #ifdef STM32
4 #include "AudioI2S/I2SConfig.h"
5 #include "stm32-i2s.h"
6 
7 namespace audio_tools {
8 
22  friend class I2SStream;
23 
24  public:
27  I2SConfigStd c(mode);
28  return c;
29  }
30 
32  bool setAudioInfo(AudioInfo) { return false;}
33 
35  bool begin(RxTxMode mode = TX_MODE) {
36  TRACED();
37  return begin(defaultConfig(mode));
38  }
39 
41  bool begin(I2SConfigStd cfg) {
42  // TRACED();
43  this->cfg = cfg;
44  bool result = false;
45  deleteBuffers();
46  LOGI("buffer_size: %d", cfg.buffer_size);
47  LOGI("buffer_count: %d", cfg.buffer_count);
48 
49  if (cfg.channels > 2 || cfg.channels <= 0) {
50  LOGE("Channels not supported: %d", cfg.channels);
51  return false;
52  }
53 
54  setupDefaultI2SParameters();
55  result = use_dma ? startI2SDMA() : startI2S();
56  this->active = result;
57  return result;
58  }
59 
61  void end() {
62  // TRACED();
63  stm32_i2s::I2S.end();
64  deleteBuffers();
65  active = false;
66  }
67 
69  int available() {
70  if (!active) return 0;
71  if (use_dma) return p_rx_buffer == nullptr ? 0 : cfg.buffer_size;
72  return cfg.buffer_size;
73  }
74 
77  if (!active) return 0;
78  if (use_dma) return p_tx_buffer == nullptr ? 0 : cfg.buffer_size;
79  return cfg.buffer_size;
80  }
81 
83  I2SConfigStd config() { return cfg; }
84 
86  size_t writeBytes(const void *src, size_t size_bytes) {
87  TRACED();
88  if (!use_dma) {
89  result = stm32_i2s::I2S.write((uint8_t *)src, size_bytes);
90  } else {
91  // if we have an input stream we do not need to fill the buffer
92  if (p_dma_in != nullptr) {
93  // by calling writeBytes we activate the automatic timeout handling
94  // and expect further writes to continue the output
95  last_write_ms = millis();
96  result = size_bytes;
97  } else {
98  // fill buffer
99  result = writeBytesDMA(src, size_bytes);
100  }
101  }
102  return result;
103  }
104 
105  size_t readBytes(void *dest, size_t size_bytes) {
106  TRACED();
107  if (!use_dma) {
108  return stm32_i2s::I2S.readBytes((uint8_t *)dest, size_bytes);
109  } else {
110  if (cfg.channels == 2) {
111  return p_rx_buffer->readArray((uint8_t *)dest, size_bytes);
112  } else {
113  return readBytesDMA(dest, size_bytes);
114  }
115  }
116  }
117 
120  static void writeFromReceive(uint8_t *buffer, uint16_t byteCount, void *ref) {
121  I2SDriverSTM32 *self = (I2SDriverSTM32 *)ref;
122  uint16_t written = 0;
123  if (self->p_dma_out != nullptr)
124  written = self->p_dma_out->write(buffer, byteCount);
125  else
126  written = self->p_rx_buffer->writeArray(buffer, byteCount);
127 
128  // check for overflow
129  if (written != byteCount) {
130  LOGW("Buffer overflow: written %d of %d", written, byteCount);
131  }
132  }
133 
136  static void readToTransmit(uint8_t *buffer, uint16_t byteCount, void *ref) {
137  I2SDriverSTM32 *self = (I2SDriverSTM32 *)ref;
138  static size_t count = 0;
139  size_t read = 0;
140  memset(buffer, 0, byteCount);
141  if (self->p_dma_in != nullptr) {
142  // stop reading if timout is relevant
143  if (self->isWriteTimedOut()) {
144  // we just provide silence
145  read = byteCount;
146  } else {
147  // get data from stream
148  read = self->p_dma_in->readBytes(buffer, byteCount);
149  }
150  } else {
151  // get data from buffer
152  if (self->stm32_write_active) {
153  // if we risk an onderflow we force the execution of the loop
154  if (self->p_tx_buffer->available() < byteCount) {
155  loop();
156  }
157  read = self->p_tx_buffer->readArray(buffer, byteCount);
158  } else {
159  // force execution of loop to fill buffer;
160  loop();
161  }
162  }
163 
164  // check for underflow
165  count++;
166  if (read != byteCount) {
167  LOGW("Buffer undeflow at %lu: %d for %d", count, read, byteCount);
168  }
169  }
170 
173  return last_write_ms != 0 && last_write_ms + 500 < millis();
174  }
175 
177  void setDMAActive(bool flag) { use_dma = flag; }
178 
181  use_dma = true;
182  p_dma_in = &in;
183  }
184 
186  void setDMAOutput(Print &out) {
187  use_dma = true;
188  p_dma_out = &out;
189  }
190 
191  protected:
192  I2SConfigStd cfg;
193  bool active = false;
194  stm32_i2s::I2SSettingsSTM32 i2s_stm32;
195  bool result = true;
196  BaseBuffer<uint8_t> *p_tx_buffer = nullptr;
197  BaseBuffer<uint8_t> *p_rx_buffer = nullptr;
198  bool stm32_write_active = false;
199  bool use_dma = true;
200  Print *p_dma_out = nullptr;
201  Stream *p_dma_in = nullptr;
202  uint32_t last_write_ms = 0;
203 
204  size_t writeBytesDMA(const void *src, size_t size_bytes) {
205  size_t result = 0;
206  // fill the tx buffer
207  int open = size_bytes;
208  while (open > 0) {
209  int actual_written = writeBytesExt(src, size_bytes);
210  result += actual_written;
211  open -= actual_written;
212  if (open > 0) {
213  delay(10);
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  return size_bytes;
223  }
224 
225  size_t readBytesDMA(void *dest, size_t size_bytes) {
226  // combine two channels to 1: so request double the amout
227  int req_bytes = size_bytes * 2;
228  uint8_t tmp[req_bytes];
229  int16_t *tmp_16 = (int16_t *)tmp;
230  int eff_bytes = p_rx_buffer->readArray((uint8_t *)tmp, req_bytes);
231  // add 2 channels together
232  int16_t *dest_16 = (int16_t *)dest;
233  int16_t eff_samples = eff_bytes / 2;
234  int16_t idx = 0;
235  for (int j = 0; j < eff_samples; j += 2) {
236  dest_16[idx++] = static_cast<float>(tmp_16[j]) + tmp_16[j + 1] / 2.0;
237  }
238  return eff_bytes / 2;
239  }
240 
241  bool startI2S() {
242  switch (cfg.rx_tx_mode) {
243  case RX_MODE:
244  result = stm32_i2s::I2S.begin(i2s_stm32, false, true);
245  break;
246  case TX_MODE:
247  result = stm32_i2s::I2S.begin(i2s_stm32, true, false);
248  break;
249  case RXTX_MODE:
250  default:
251  result = stm32_i2s::I2S.begin(i2s_stm32, true, true);
252  break;
253  }
254  return result;
255  }
256 
257  bool startI2SDMA() {
258  switch (cfg.rx_tx_mode) {
259  case RX_MODE:
260  if (use_dma && p_rx_buffer == nullptr)
261  p_rx_buffer = new NBuffer<uint8_t>(cfg.buffer_size, cfg.buffer_count);
262  result = stm32_i2s::I2S.beginReadDMA(i2s_stm32, writeFromReceive);
263  break;
264  case TX_MODE:
265  stm32_write_active = false;
266  if (use_dma && p_tx_buffer == nullptr)
267  p_tx_buffer = new NBuffer<uint8_t>(cfg.buffer_size, cfg.buffer_count);
268  result = stm32_i2s::I2S.beginWriteDMA(i2s_stm32, readToTransmit);
269  break;
270  case RXTX_MODE:
271  if (use_dma) {
272  stm32_write_active = false;
273  if (p_rx_buffer == nullptr)
274  p_rx_buffer =
275  new NBuffer<uint8_t>(cfg.buffer_size, cfg.buffer_count);
276  if (p_tx_buffer == nullptr)
277  p_tx_buffer =
278  new NBuffer<uint8_t>(cfg.buffer_size, cfg.buffer_count);
279  }
280  result = stm32_i2s::I2S.beginReadWriteDMA(
281  i2s_stm32, readToTransmit, writeFromReceive);
282  break;
283  default:
284  LOGE("Unsupported mode");
285  return false;
286  }
287  return result;
288  }
289 
290  uint32_t toDataFormat(int bits_per_sample) {
291  switch (bits_per_sample) {
292  case 16:
293  return I2S_DATAFORMAT_16B;
294  case 24:
295  return I2S_DATAFORMAT_24B;
296  case 32:
297  return I2S_DATAFORMAT_32B;
298  }
299  return I2S_DATAFORMAT_16B;
300  }
301 
302  void deleteBuffers() {
303  if (p_rx_buffer != nullptr) {
304  delete p_rx_buffer;
305  p_rx_buffer = nullptr;
306  }
307  if (p_tx_buffer != nullptr) {
308  delete p_tx_buffer;
309  p_tx_buffer = nullptr;
310  }
311  }
312 
313  void setupDefaultI2SParameters() {
314  i2s_stm32.sample_rate = getSampleRate(cfg);
315  i2s_stm32.data_format = toDataFormat(cfg.bits_per_sample);
316  i2s_stm32.mode = getMode(cfg);
317  i2s_stm32.standard = getStandard(cfg);
318  i2s_stm32.fullduplexmode = cfg.rx_tx_mode == RXTX_MODE
319  ? I2S_FULLDUPLEXMODE_ENABLE
320  : I2S_FULLDUPLEXMODE_DISABLE;
321  i2s_stm32.hardware_config.buffer_size = cfg.buffer_size;
322  // provide ourself as parameter to callback
323  i2s_stm32.ref = this;
324  }
325 
326  uint32_t getMode(I2SConfigStd &cfg) {
327  if (cfg.is_master) {
328  switch (cfg.rx_tx_mode) {
329  case RX_MODE:
330  return I2S_MODE_MASTER_RX;
331  case TX_MODE:
332  return I2S_MODE_MASTER_TX;
333  default:
334  LOGE("RXTX_MODE not supported");
335  return I2S_MODE_MASTER_TX;
336  }
337  } else {
338  switch (cfg.rx_tx_mode) {
339  case RX_MODE:
340  return I2S_MODE_SLAVE_RX;
341  case TX_MODE:
342  return I2S_MODE_SLAVE_TX;
343  default:
344  LOGE("RXTX_MODE not supported");
345  return I2S_MODE_SLAVE_TX;
346  }
347  }
348  }
349 
350  uint32_t getStandard(I2SConfigStd &cfg) {
351  uint32_t result;
352  switch (cfg.i2s_format) {
353  case I2S_PHILIPS_FORMAT:
354  return I2S_STANDARD_PHILIPS;
355  case I2S_STD_FORMAT:
356  case I2S_LSB_FORMAT:
357  case I2S_RIGHT_JUSTIFIED_FORMAT:
358  return I2S_STANDARD_MSB;
359  case I2S_MSB_FORMAT:
360  case I2S_LEFT_JUSTIFIED_FORMAT:
361  return I2S_STANDARD_LSB;
362  }
363  return I2S_STANDARD_PHILIPS;
364  }
365 
366  uint32_t getSampleRate(I2SConfigStd &cfg) {
367  switch (cfg.sample_rate) {
368  case I2S_AUDIOFREQ_192K:
369  case I2S_AUDIOFREQ_96K:
370  case I2S_AUDIOFREQ_48K:
371  case I2S_AUDIOFREQ_44K:
372  case I2S_AUDIOFREQ_32K:
373  case I2S_AUDIOFREQ_22K:
374  case I2S_AUDIOFREQ_16K:
375  case I2S_AUDIOFREQ_11K:
376  case I2S_AUDIOFREQ_8K:
377  return cfg.sample_rate;
378  default:
379  LOGE("Unsupported sample rate: %u", cfg.sample_rate);
380  return cfg.sample_rate;
381  }
382  }
383 
384  size_t writeBytesExt(const void *src, size_t size_bytes) {
385  size_t result = 0;
386  if (cfg.channels == 2) {
387  result = p_tx_buffer->writeArray((uint8_t *)src, size_bytes);
388  } else {
389  // write each sample 2 times
390  int samples = size_bytes / 2;
391  int16_t *src_16 = (int16_t *)src;
392  int16_t tmp[2];
393  int result = 0;
394  for (int j = 0; j < samples; j++) {
395  tmp[0] = src_16[j];
396  tmp[1] = src_16[j];
397  if (p_tx_buffer->availableForWrite() >= 4) {
398  p_tx_buffer->writeArray((uint8_t *)tmp, 4);
399  result = j * 2;
400  } else {
401  // abort if the buffer is full
402  break;
403  }
404  }
405  }
406  LOGD("writeBytesExt: %u", result)
407  return result;
408  }
409 };
410 
411 using I2SDriver = I2SDriverSTM32;
412 
413 } // namespace audio_tools
414 
415 #endif
virtual int readArray(T data[], int len)
reads multiple values
Definition: Buffers.h:41
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition: Buffers.h:65
virtual int availableForWrite()=0
provides the number of entries that are available to write
Configuration for i2s.
Definition: I2SConfigStd.h:17
RxTxMode rx_tx_mode
public settings
Definition: I2SConfigStd.h:50
Basic I2S API - for the STM32 Depends on https://github.com/pschatzmann/stm32f411-i2s We provide a di...
Definition: I2SSTM32.h:21
bool setAudioInfo(AudioInfo)
Potentially updates the sample rate (if supported)
Definition: I2SSTM32.h:32
I2SConfigStd config()
provides the actual configuration
Definition: I2SSTM32.h:83
void setDMAActive(bool flag)
activate DMA using a read and write buffer
Definition: I2SSTM32.h:177
int available()
we assume the data is already available in the buffer
Definition: I2SSTM32.h:69
static void readToTransmit(uint8_t *buffer, uint16_t byteCount, void *ref)
Callback function used by https://github.com/pschatzmann/stm32f411-i2s.
Definition: I2SSTM32.h:136
static void writeFromReceive(uint8_t *buffer, uint16_t byteCount, void *ref)
Callback function used by https://github.com/pschatzmann/stm32f411-i2s.
Definition: I2SSTM32.h:120
int availableForWrite()
We limit the write size to the buffer size.
Definition: I2SSTM32.h:76
I2SConfigStd defaultConfig(RxTxMode mode=TX_MODE)
Provides the default configuration.
Definition: I2SSTM32.h:26
bool isWriteTimedOut()
Checks if timout has been activated and if so, if it is timed out.
Definition: I2SSTM32.h:172
bool begin(I2SConfigStd cfg)
starts the DAC
Definition: I2SSTM32.h:41
void end()
stops the I2C and unistalls the driver
Definition: I2SSTM32.h:61
size_t writeBytes(const void *src, size_t size_bytes)
blocking writes for the data to the I2S interface
Definition: I2SSTM32.h:86
void setDMAOutput(Print &out)
activate DMA and defines the output
Definition: I2SSTM32.h:186
bool begin(RxTxMode mode=TX_MODE)
starts the DAC with the default config in TX Mode
Definition: I2SSTM32.h:35
void setDMAInputStream(Stream &in)
activate DMA and defines the input
Definition: I2SSTM32.h:180
We support the Stream interface for the I2S access. In addition we allow a separate mute pin which mi...
Definition: I2SStream.h:31
Definition: NoArduino.h:58
Definition: NoArduino.h:125
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition: AudioTypes.h:26
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10
void delay(uint32_t ms)
Waits for the indicated milliseconds.
Definition: Millis.h:11
uint32_t millis()
Returns the milliseconds since the start.
Definition: Millis.h:18
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:50
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