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