arduino-audio-tools
I2SNanoSenseBLE.h
1 #pragma once
2 
3 #include "AudioConfig.h"
4 
5 #if defined(USE_NANO33BLE)
6 
7 #include "AudioTools/CoreAudio/AudioI2S/I2SConfig.h"
8 #include "AudioTools/CoreAudio/AudioLogger.h"
9 #include "AudioTools/CoreAudio/AudioTypes.h"
10 #include "AudioTools/CoreAudio/Buffers.h"
11 
12 #define IS_I2S_IMPLEMENTED
13 
14 namespace audio_tools {
15 
16 static int i2s_buffer_size = 0;
17 static BaseBuffer<uint8_t> *p_i2s_buffer = nullptr;
18 static uint8_t *p_i2s_array = nullptr; // current array
19 static uint8_t *p_i2s_array_1 = nullptr; // array 1
20 static uint8_t *p_i2s_array_2 = nullptr; // array 2
21 static uint32_t i2s_underflow_count = 0;
22 // alternative API
23 static Stream *p_nano_ble_stream = nullptr;
24 
29  int id;
30  float freq; // in mhz
31 };
32 
33 static const Nano_BLE_freq_info freq_table[] = {
34  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV8, 32.0 / 8},
35  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV10, 32 / 10},
36  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV11, 32.0 / 11},
37  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV15, 32.0 / 15},
38  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV16, 32.0 / 16},
39  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV21, 32.0 / 21},
40  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV23, 32.0 / 23},
41  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV30, 32.0 / 30},
42  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV31, 32.0 / 31},
43  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV32, 32.0 / 32},
44  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV42, 32.0 / 42},
45  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV63, 32.0 / 63},
46  {I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV125, 32.0 / 125}};
47 
52  int id;
53  float ratio;
54 };
55 
56 static const Nano_BLE_ratio_info ratio_table[] = {
57  {I2S_CONFIG_RATIO_RATIO_32X, 32.0}, {I2S_CONFIG_RATIO_RATIO_48X, 48.0},
58  {I2S_CONFIG_RATIO_RATIO_64X, 64.0}, {I2S_CONFIG_RATIO_RATIO_96X, 96.0},
59  {I2S_CONFIG_RATIO_RATIO_128X, 128.0}, {I2S_CONFIG_RATIO_RATIO_192X, 192.0},
60  {I2S_CONFIG_RATIO_RATIO_256X, 256.0}, {I2S_CONFIG_RATIO_RATIO_384X, 384.0},
61  {I2S_CONFIG_RATIO_RATIO_512X, 512.0}};
62 
63 void I2S_IRQWrite(void) {
64  // Handle Wrtie
65  if (NRF_I2S->EVENTS_TXPTRUPD == 1) {
66  size_t eff_read = 0;
67 
68  // toggle arrays to avoid noise
69  p_i2s_array = p_i2s_array == p_i2s_array_1 ? p_i2s_array_2 : p_i2s_array_1;
70 
71  if (p_nano_ble_stream != nullptr) {
72  // Alternative API via Stream
73  eff_read = p_nano_ble_stream->readBytes(p_i2s_array, i2s_buffer_size);
74  } else {
75  // Using readArray
76  eff_read = p_i2s_buffer->readArray(p_i2s_array, i2s_buffer_size);
77  }
78  // if we did not get any valid data we provide silence
79  if (eff_read < i2s_buffer_size) {
80  memset(p_i2s_array, 0, i2s_buffer_size);
81  // allow checking for underflows
82  i2s_underflow_count++;
83  }
84  NRF_I2S->TXD.PTR = (uint32_t)p_i2s_array;
85  NRF_I2S->EVENTS_TXPTRUPD = 0;
86  }
87 }
88 
89 void I2S_IRQRead(void) {
90  // Handle Read
91  if (NRF_I2S->EVENTS_RXPTRUPD == 1) {
92  // reading from pins writing to buffer - overwrite oldest data on overflow
93  p_i2s_buffer->writeArrayOverwrite(p_i2s_array, i2s_buffer_size);
94  // switch buffer assuming that this is necessary like in the write case
95  p_i2s_array = p_i2s_array == p_i2s_array_1 ? p_i2s_array_2 : p_i2s_array_1;
96  NRF_I2S->RXD.PTR = (uint32_t)p_i2s_array;
97  NRF_I2S->EVENTS_RXPTRUPD = 0;
98  }
99 }
100 
104 void I2S_IRQHandler(void) {
105  // prevent NPE
106  if (p_i2s_buffer == nullptr || p_i2s_array == 0) {
107  NRF_I2S->EVENTS_TXPTRUPD = 0;
108  NRF_I2S->EVENTS_RXPTRUPD = 0;
109  return;
110  }
111 
112  I2S_IRQWrite();
113  I2S_IRQRead();
114 }
115 
126  friend class I2SStream;
127 
128  public:
129  I2SDriverNanoBLE() = default;
130 
133  I2SConfigStd c(mode);
134  return c;
135  }
137  bool setAudioInfo(AudioInfo) { return false; }
138 
140  bool begin(RxTxMode mode = TX_MODE) { return begin(defaultConfig(mode)); }
141 
143  bool begin(I2SConfigStd cfg) {
144  TRACEI();
145  cfg.logInfo();
146  this->cfg = cfg;
147 
148  if (cfg.bits_per_sample == 32) {
149  LOGE("32 bits not supported");
150  return false;
151  }
152 
153  if (!setupBuffers()) {
154  LOGE("out of memory");
155  return false;
156  }
157 
158  // setup IRQ
159  NVIC_SetVector(I2S_IRQn, (uint32_t)I2S_IRQHandler);
160  NVIC_EnableIRQ(I2S_IRQn);
161 
162  if (!setupRxTx(cfg)) {
163  return false;
164  }
165  setupClock(cfg);
166  setupBitWidth(cfg);
167  setupMode(cfg);
168  setupPins(cfg);
169 
170  // TX_MODE is started with first write
171  if (cfg.rx_tx_mode == RX_MODE || p_nano_ble_stream != nullptr) {
172  startI2SActive();
173  }
174 
175  return true;
176  }
177 
178  int available() {
179  if (cfg.rx_tx_mode == TX_MODE) return 0;
180  return p_i2s_buffer->available();
181  }
182 
183  int availableForWrite() {
184  if (cfg.rx_tx_mode == RX_MODE) return 0;
185  return max(i2s_buffer_size, p_i2s_buffer->availableForWrite());
186  }
187 
189  void end() {
190  LOGD(__func__);
191  // stop task
192  NRF_I2S->TASKS_START = 0;
193  // disble I2S
194  NRF_I2S->ENABLE = 0;
195 
196  releaseBuffers();
197 
198  is_active = false;
199  }
200 
202  I2SConfigStd config() { return cfg; }
203 
205  size_t writeBytes(const void *src, size_t size_bytes) {
206  size_t result = p_i2s_buffer->writeArray((uint8_t *)src, size_bytes);
207 
208  // activate I2S when the buffer is full
209  if (!is_active && result < size_bytes) {
210  startI2SActive();
211  }
212  return result;
213  }
214 
216  size_t readBytes(void *dest, size_t size_bytes) {
217  size_t result = p_i2s_buffer->readArray((uint8_t *)dest, size_bytes);
218  return result;
219  }
220 
222  void setStream(Stream &stream) { p_nano_ble_stream = &stream; }
223 
225  void clearStream() { p_nano_ble_stream = nullptr; }
226 
227  void setBufferSize(int size) { i2s_buffer_size = size; }
228 
229  protected:
230  I2SConfigStd cfg;
231  bool is_active = false;
232 
235  TRACED();
236  switch (cfg.rx_tx_mode) {
237  case TX_MODE:
238  // Enable transmission
239  NRF_I2S->CONFIG.TXEN =
240  (I2S_CONFIG_TXEN_TXEN_Enabled << I2S_CONFIG_TXEN_TXEN_Pos);
241  return true;
242  case RX_MODE:
243  // Enable reception
244  NRF_I2S->CONFIG.RXEN =
245  (I2S_CONFIG_RXEN_RXEN_Enabled << I2S_CONFIG_RXEN_RXEN_Pos);
246  return true;
247  default:
248  LOGE("rx_tx_mode not supported");
249  return false;
250  }
251  }
252 
255  TRACED();
256 
257  // Enable MCK generator if in master mode
258  if (cfg.is_master) {
259  NRF_I2S->CONFIG.MCKEN =
260  (I2S_CONFIG_MCKEN_MCKEN_Enabled << I2S_CONFIG_MCKEN_MCKEN_Pos);
261  }
262 
263  // find closest frequency for requested sample_rate
264  float freq_requested = cfg.sample_rate; // * cfg.bits_per_sample ;
265  float selected_freq = 0;
266  for (auto freq : freq_table) {
267  for (auto div : ratio_table) {
268  float freq_value = freq.freq * 1000000 / div.ratio;
269  if (abs(freq_value - freq_requested) <
270  abs(selected_freq - freq_requested)) {
271  // MCKFREQ
272  NRF_I2S->CONFIG.MCKFREQ = freq.id << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos;
273  // Ratio
274  NRF_I2S->CONFIG.RATIO = div.id << I2S_CONFIG_RATIO_RATIO_Pos;
275  selected_freq = freq_value;
276  LOGD("frequency requested %f vs %f", freq_requested, selected_freq);
277  }
278  }
279  }
280  LOGI("Frequency req. %f vs eff. %f", freq_requested, selected_freq);
281  }
282 
285  TRACED();
286  uint16_t swidth = I2S_CONFIG_SWIDTH_SWIDTH_16Bit;
287  switch (cfg.bits_per_sample) {
288  case 8:
289  NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_8Bit
290  << I2S_CONFIG_SWIDTH_SWIDTH_Pos;
291  break;
292  case 16:
293  NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_16Bit
294  << I2S_CONFIG_SWIDTH_SWIDTH_Pos;
295  break;
296  case 24:
297  NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_24Bit
298  << I2S_CONFIG_SWIDTH_SWIDTH_Pos;
299  break;
300  default:
301  LOGE("Unsupported bit width: %d", cfg.bits_per_sample);
302  }
303  }
304 
307  TRACED();
308  // setup mode
309  switch (cfg.i2s_format) {
310  case I2S_STD_FORMAT:
311  case I2S_PHILIPS_FORMAT:
312  case I2S_MSB_FORMAT:
313  case I2S_LEFT_JUSTIFIED_FORMAT:
314  NRF_I2S->CONFIG.FORMAT = I2S_CONFIG_FORMAT_FORMAT_I2S
315  << I2S_CONFIG_FORMAT_FORMAT_Pos;
316  NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_Left
317  << I2S_CONFIG_ALIGN_ALIGN_Pos;
318  ;
319  break;
320  case I2S_LSB_FORMAT:
321  case I2S_RIGHT_JUSTIFIED_FORMAT:
322  NRF_I2S->CONFIG.FORMAT = I2S_CONFIG_FORMAT_FORMAT_I2S
323  << I2S_CONFIG_FORMAT_FORMAT_Pos;
324  NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_Right
325  << I2S_CONFIG_ALIGN_ALIGN_Pos;
326  ;
327  break;
328  default:
329  LOGW("i2s_format not supported");
330  }
331  }
332 #ifdef IS_ZEPHYR
333  int digitalPinToPinName(int pin) {return pin;}
334 #endif
336  int getPinName(int pin) {
337 #if defined(USE_ALT_PIN_SUPPORT)
338  return cfg.is_arduino_pin_numbers ? digitalPinToPinName(pin) : pin;
339 #else
340  return digitalPinToPinName(pin);
341 #endif
342  }
343 
346  TRACED();
347 
348  // MCK
349  if (cfg.is_master && cfg.pin_mck >= 0) {
350  NRF_I2S->PSEL.MCK = getPinName(cfg.pin_mck) << I2S_PSEL_MCK_PIN_Pos;
351  }
352  // SCK - bit clock
353  NRF_I2S->PSEL.SCK = getPinName(cfg.pin_bck) << I2S_PSEL_SCK_PIN_Pos;
354  // LRCK
355  NRF_I2S->PSEL.LRCK = getPinName(cfg.pin_ws) << I2S_PSEL_LRCK_PIN_Pos;
356  // i2s Data Pins
357  switch (cfg.rx_tx_mode) {
358  case TX_MODE:
359  NRF_I2S->PSEL.SDOUT = getPinName(cfg.pin_data)
360  << I2S_PSEL_SDOUT_PIN_Pos;
361  break;
362  case RX_MODE:
363  NRF_I2S->PSEL.SDIN = getPinName(cfg.pin_data) << I2S_PSEL_SDIN_PIN_Pos;
364  break;
365  default:
366  TRACEW();
367  }
368  }
369 
371  unsigned long getINTENSET() {
372  unsigned long result = 0;
373  switch (cfg.rx_tx_mode) {
374  case TX_MODE:
375  result = I2S_INTENSET_TXPTRUPD_Enabled << I2S_INTENSET_TXPTRUPD_Pos;
376  break;
377  case RX_MODE:
378  result = I2S_INTENSET_RXPTRUPD_Enabled << I2S_INTENSET_RXPTRUPD_Pos;
379  break;
380  default:
381  TRACEE();
382  }
383  return result;
384  }
385 
387  void startI2SActive() {
388  TRACED();
389  // Use stereo
390  NRF_I2S->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_Stereo
391  << I2S_CONFIG_CHANNELS_CHANNELS_Pos;
392  // Setup master or slave mode
393  NRF_I2S->CONFIG.MODE =
394  cfg.is_master ? I2S_CONFIG_MODE_MODE_MASTER << I2S_CONFIG_MODE_MODE_Pos
395  : I2S_CONFIG_MODE_MODE_Slave << I2S_CONFIG_MODE_MODE_Pos;
396 
397  // initial empty buffer
398  NRF_I2S->TXD.PTR = (uint32_t)p_i2s_array;
399  NRF_I2S->RXD.PTR = (uint32_t)p_i2s_array;
400  // define copy size (always defined as number of 32 bits)
401  NRF_I2S->RXTXD.MAXCNT = i2s_buffer_size / 4;
402 
403  NRF_I2S->INTENSET = getINTENSET();
404 
405  // ensble I2S
406  NRF_I2S->ENABLE = 1;
407  // start task
408  NRF_I2S->TASKS_START = 1;
409 
410  is_active = true;
411  }
412 
414  bool setupBuffers() {
415  TRACED();
416  i2s_buffer_size = cfg.buffer_size;
417 
418  if (p_i2s_array == nullptr) {
419  p_i2s_array_1 = new uint8_t[i2s_buffer_size]{0};
420  p_i2s_array_2 = new uint8_t[i2s_buffer_size]{0};
421  p_i2s_array = p_i2s_array_1;
422  } else {
423  memset(p_i2s_array_1, 0, i2s_buffer_size);
424  memset(p_i2s_array_2, 0, i2s_buffer_size);
425  }
426 
427  // allocate buffer only when needed
428  if (p_i2s_buffer == nullptr && p_nano_ble_stream == nullptr) {
429  p_i2s_buffer = new NBuffer<uint8_t>(cfg.buffer_size, i2s_buffer_size);
430  }
431 
432  // on stream option we only need to have the arrays allocated
433  if (p_nano_ble_stream != nullptr) {
434  return p_i2s_array_1 != nullptr && p_i2s_array_2 != nullptr;
435  }
436  return p_i2s_array_1 != nullptr && p_i2s_array_2 != nullptr &&
437  p_i2s_buffer != nullptr;
438  }
439 
441  void releaseBuffers() {
442  TRACED();
443  i2s_buffer_size = 0;
444 
445  p_i2s_array = nullptr;
446  delete p_i2s_array_1;
447  p_i2s_array_1 = nullptr;
448  delete p_i2s_array_2;
449  p_i2s_array_2 = nullptr;
450 
451  delete p_i2s_buffer;
452  p_i2s_buffer = nullptr;
453  }
454 };
455 
456 using I2SDriver = I2SDriverNanoBLE;
457 
458 } // namespace audio_tools
459 
460 #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 writeArrayOverwrite(const T data[], int len)
Fills the buffer data and overwrites the oldest data if the buffer is full.
Definition: Buffers.h:83
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
Configuration for i2s.
Definition: I2SConfigStd.h:17
RxTxMode rx_tx_mode
public settings
Definition: I2SConfigStd.h:48
Basic I2S API - for the Arduino Nano BLE Sense See https://content.arduino.cc/assets/Nano_BLE_MCU-nRF...
Definition: I2SNanoSenseBLE.h:125
bool setupBuffers()
dynamic buffer management
Definition: I2SNanoSenseBLE.h:414
bool setAudioInfo(AudioInfo)
Potentially updates the sample rate (if supported)
Definition: I2SNanoSenseBLE.h:137
void setupPins(I2SConfigStd cfg)
setup pins
Definition: I2SNanoSenseBLE.h:345
I2SConfigStd config()
provides the actual configuration
Definition: I2SNanoSenseBLE.h:202
void clearStream()
Deactivate alternative API: don't forget to call begin()
Definition: I2SNanoSenseBLE.h:225
void setStream(Stream &stream)
alternative API which provides the data directly via a Stream
Definition: I2SNanoSenseBLE.h:222
I2SConfigStd defaultConfig(RxTxMode mode)
Provides the default configuration.
Definition: I2SNanoSenseBLE.h:132
unsigned long getINTENSET()
Determine the INTENSET value.
Definition: I2SNanoSenseBLE.h:371
void setupBitWidth(I2SConfigStd cfg)
setup SWIDTH
Definition: I2SNanoSenseBLE.h:284
int getPinName(int pin)
Provides the arduino or unconverted pin name.
Definition: I2SNanoSenseBLE.h:336
bool begin(I2SConfigStd cfg)
starts the I2S
Definition: I2SNanoSenseBLE.h:143
void end()
stops the I2S
Definition: I2SNanoSenseBLE.h:189
void setupMode(I2SConfigStd cfg)
setup format and align
Definition: I2SNanoSenseBLE.h:306
size_t writeBytes(const void *src, size_t size_bytes)
writes the data to the I2S buffer
Definition: I2SNanoSenseBLE.h:205
bool setupRxTx(I2SConfigStd cfg)
setup TXEN or RXEN
Definition: I2SNanoSenseBLE.h:234
size_t readBytes(void *dest, size_t size_bytes)
reads the data from the I2S buffer
Definition: I2SNanoSenseBLE.h:216
bool begin(RxTxMode mode=TX_MODE)
starts the I2S with the default config in TX Mode
Definition: I2SNanoSenseBLE.h:140
void releaseBuffers()
Release buffers.
Definition: I2SNanoSenseBLE.h:441
void setupClock(I2SConfigStd cfg)
setup MCKFREQ and RATIO
Definition: I2SNanoSenseBLE.h:254
void startI2SActive()
Start IRQ and I2S.
Definition: I2SNanoSenseBLE.h:387
We support the Stream interface for the I2S access. In addition we allow a separate mute pin which mi...
Definition: I2SStream.h:33
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:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:868
void I2S_IRQHandler(void)
Definition: I2SNanoSenseBLE.h:104
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:52
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:59
Mapping Frequency constants to available frequencies.
Definition: I2SNanoSenseBLE.h:28
Mapping from Ratio Constants to frequency ratios.
Definition: I2SNanoSenseBLE.h:51