diff --git a/cores/esp8266/core_esp8266_i2s.cpp b/cores/esp8266/core_esp8266_i2s.cpp index c50d5e2d8..eb1d7d7e2 100644 --- a/cores/esp8266/core_esp8266_i2s.cpp +++ b/cores/esp8266/core_esp8266_i2s.cpp @@ -24,7 +24,7 @@ #include "osapi.h" #include "ets_sys.h" #include "i2s_reg.h" -#include "i2s.h" +#include "core_esp8266_i2s.h" extern "C" { @@ -194,11 +194,11 @@ static void ICACHE_RAM_ATTR i2s_slc_isr(void) { } void i2s_set_callback(void (*callback) (void)) { - tx->callback = callback; + if (tx) tx->callback = callback; } void i2s_rx_set_callback(void (*callback) (void)) { - rx->callback = callback; + if (rx) rx->callback = callback; } static bool _alloc_channel(i2s_state_t *ch) { @@ -343,7 +343,7 @@ bool i2s_write_lr(int16_t left, int16_t right){ // writes a buffer of frames into the DMA memory, returns the amount of frames written // A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel. -static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mono, bool nb) { +static uint16_t _i2s_write_buffer(const int16_t *frames, uint16_t frame_count, bool mono, bool nb) { uint16_t frames_written=0; while(frame_count>0) { @@ -401,13 +401,13 @@ static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mo return frames_written; } -uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); } +uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); } -uint16_t i2s_write_buffer_mono(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); } +uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); } -uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); } +uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); } -uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); } +uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); } bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) { if (!rx) { diff --git a/cores/esp8266/core_esp8266_i2s.h b/cores/esp8266/core_esp8266_i2s.h new file mode 100644 index 000000000..db572fa19 --- /dev/null +++ b/cores/esp8266/core_esp8266_i2s.h @@ -0,0 +1,81 @@ +/* + i2s.h - Software I2S library for esp8266 + + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef I2S_h +#define I2S_h + +#define I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS 1 + +/* +How does this work? Basically, to get sound, you need to: +- Connect an I2S codec to the I2S pins on the ESP. +- Start up a thread that's going to do the sound output +- Call i2s_set_bits() if you want to enable 24-bit mode +- Call i2s_begin() +- Call i2s_set_rate() with the sample rate you want. +- Generate sound and call i2s_write_sample() with 32-bit samples. +The 32bit samples basically are 2 16-bit signed values (the analog values for +the left and right channel) concatenated as (Rout<<16)+Lout + +i2s_write_sample will block when you're sending data too quickly, so you can just +generate and push data as fast as you can and i2s_write_sample will regulate the +speed. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +bool i2s_set_bits(int bits); // Set bits per sample, only 16 or 24 supported. Call before begin. +// Note that in 24 bit mode each sample must be left-aligned (i.e. 0x00000000 .. 0xffffff00) as the +// hardware shifts starting at bit 31, not bit 23. + +void i2s_begin(); // Enable TX only, for compatibility +bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error +bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks); +void i2s_end(); +void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000) +void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate +float i2s_get_real_rate();//The actual Sample Rate on output +bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full) +bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead +bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result +bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs. +bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow) +bool i2s_is_empty();//returns true if DMA is empty (underflow) +bool i2s_rx_is_full(); +bool i2s_rx_is_empty(); +uint16_t i2s_available();// returns the number of samples than can be written before blocking +uint16_t i2s_rx_available();// returns the number of samples than can be written before blocking +void i2s_set_callback(void (*callback) (void)); +void i2s_rx_set_callback(void (*callback) (void)); + +// writes a buffer of frames into the DMA memory, returns the amount of frames written +// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel. +uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count); +uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count); +uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count); +uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cores/esp8266/i2s.h b/cores/esp8266/i2s.h index 581a91657..e3d40706c 100644 --- a/cores/esp8266/i2s.h +++ b/cores/esp8266/i2s.h @@ -1,81 +1,12 @@ -/* - i2s.h - Software I2S library for esp8266 +// This include file is a hack to ensure backward compatibility with +// pre 3.0.0 versions of the core. There was a *lowercase* "i2s.h" +// header which was in this directory, now renamed to "core_esp82i66s.h" +// But, the I2S class has a header, "I2S.h" in uppercase. On Linux +// the two names are different, but on Windows it's case-insensitive +// so the names conflict. +// +// Avoid the issue by preserving the old i2s.h file and have it redirect +// to I2S.h which will give the ESP8266-specific functions as well as +// the generic I2S class. - Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef I2S_h -#define I2S_h - -#define I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS 1 - -/* -How does this work? Basically, to get sound, you need to: -- Connect an I2S codec to the I2S pins on the ESP. -- Start up a thread that's going to do the sound output -- Call i2s_set_bits() if you want to enable 24-bit mode -- Call i2s_begin() -- Call i2s_set_rate() with the sample rate you want. -- Generate sound and call i2s_write_sample() with 32-bit samples. -The 32bit samples basically are 2 16-bit signed values (the analog values for -the left and right channel) concatenated as (Rout<<16)+Lout - -i2s_write_sample will block when you're sending data too quickly, so you can just -generate and push data as fast as you can and i2s_write_sample will regulate the -speed. -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -bool i2s_set_bits(int bits); // Set bits per sample, only 16 or 24 supported. Call before begin. -// Note that in 24 bit mode each sample must be left-aligned (i.e. 0x00000000 .. 0xffffff00) as the -// hardware shifts starting at bit 31, not bit 23. - -void i2s_begin(); // Enable TX only, for compatibility -bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error -bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks); -void i2s_end(); -void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000) -void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate -float i2s_get_real_rate();//The actual Sample Rate on output -bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full) -bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead -bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result -bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs. -bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow) -bool i2s_is_empty();//returns true if DMA is empty (underflow) -bool i2s_rx_is_full(); -bool i2s_rx_is_empty(); -uint16_t i2s_available();// returns the number of samples than can be written before blocking -uint16_t i2s_rx_available();// returns the number of samples than can be written before blocking -void i2s_set_callback(void (*callback) (void)); -void i2s_rx_set_callback(void (*callback) (void)); - -// writes a buffer of frames into the DMA memory, returns the amount of frames written -// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel. -uint16_t i2s_write_buffer_mono(int16_t *frames, uint16_t frame_count); -uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count); -uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count); -uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count); - -#ifdef __cplusplus -} -#endif - -#endif +#include "../../libraries/I2S/src/I2S.h" diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino new file mode 100644 index 000000000..713440d73 --- /dev/null +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -0,0 +1,36 @@ +/* + This example reads audio data from an Invensense's ICS43432 I2S microphone + breakout board, and prints out the samples to the Serial console. The + Serial Plotter built into the Arduino IDE can be used to plot the audio + data (Tools -> Serial Plotter) + created 17 November 2016 + by Sandeep Mistry +*/ + +#include + +void setup() { + // Open serial communications and wait for port to open: + // A baud rate of 115200 is used instead of 9600 for a faster data rate + // on non-native USB ports + Serial.begin(115200); + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } + + // start I2S at 8 kHz with 24-bits per sample + if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 24)) { + Serial.println("Failed to initialize I2S!"); + while (1); // do nothing + } +} + +void loop() { + // read a sample + int sample = I2S.read(); + + if (sample) { + // if it's non-zero print value to serial + Serial.println(sample); + } +} diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino new file mode 100644 index 000000000..6959ae74b --- /dev/null +++ b/libraries/I2S/examples/SimpleTone/SimpleTone.ino @@ -0,0 +1,46 @@ +/* + This example generates a square wave based tone at a specified frequency + and sample rate. Then outputs the data using the I2S interface to a + MAX08357 I2S Amp Breakout board. + + created 17 November 2016 + by Sandeep Mistry + modified for ESP8266 by Earle F. Philhower, III +*/ + +#include + +const int frequency = 440; // frequency of square wave in Hz +const int amplitude = 500; // amplitude of square wave +const int sampleRate = 8000; // sample rate in Hz + +const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave + +short sample = amplitude; // current sample value +int count = 0; + +void setup() { + Serial.begin(115200); + Serial.println("I2S simple tone"); + + // start I2S at the sample rate with 16-bits per sample + if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, 16)) { + Serial.println("Failed to initialize I2S!"); + while (1); // do nothing + } +} + +void loop() { + if (count % halfWavelength == 0) { + // invert the sample every half wavelength count multiple to generate square wave + sample = -1 * sample; + } + + // write the same sample twice, once for left and once for the right channel + I2S.write(sample); + I2S.write(sample); + + // increment the counter for the next sample + count++; +} + diff --git a/libraries/I2S/keywords.txt b/libraries/I2S/keywords.txt new file mode 100644 index 000000000..ad80bcb4c --- /dev/null +++ b/libraries/I2S/keywords.txt @@ -0,0 +1,23 @@ +####################################### +# Syntax Coloring Map I2S +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +I2S KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +begin KEYWORD2 +end KEYWORD2 + +onReceive KEYWORD2 +onTransmit KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +I2S_PHILIPS_MODE LITERAL1 diff --git a/libraries/I2S/library.properties b/libraries/I2S/library.properties new file mode 100644 index 000000000..9f1d31f2f --- /dev/null +++ b/libraries/I2S/library.properties @@ -0,0 +1,9 @@ +name=I2S +version=1.0 +author=Earle F. Philhower, III +maintainer=Earle F. Philhower, III +sentence=Enables the communication with devices that use the Inter-IC Sound (I2S) Bus. Specific implementation for ESP8266, based off of SAMD. +paragraph= +category=Communication +url=http://www.arduino.cc/en/Reference/I2S +architectures=esp8266 diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp new file mode 100644 index 000000000..6220d97a5 --- /dev/null +++ b/libraries/I2S/src/I2S.cpp @@ -0,0 +1,211 @@ +/* + Based off of ArduinoCore-SAMD I2S interface. Modified for the + ESP8266 by Earle F. Philhower, III + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "I2S.h" + +I2SClass::I2SClass(bool enableTransmit, bool enableRecv, bool driveClocks) { + _enableTx = enableTransmit; + _enableRx = enableRecv; + _driveClk = driveClocks; + _running = false; + _onTransmit = nullptr; + _onReceive = nullptr; + _havePeeked = 0; + _peekedData = 0; + _bps = 0; + _writtenHalf = false; +} + +int I2SClass::begin(i2s_mode_t mode, long sampleRate, int bitsPerSample) { + if ( _running || (mode != I2S_PHILIPS_MODE) || ( (bitsPerSample != 16) && (bitsPerSample != 24) ) ) { + return 0; + } + if (!i2s_rxtxdrive_begin(_enableRx, _enableTx, _driveClk, _driveClk)) { + return 0; + } + i2s_set_rate(sampleRate); + i2s_set_callback(_onTransmit); + i2s_rx_set_callback(_onReceive); + _bps = bitsPerSample; + _running = true; + return 1; +} + +void I2SClass::end() { + if (_running) { + i2s_end(); + } + i2s_set_callback(nullptr); + i2s_rx_set_callback(nullptr); + _running = false; +} + +void I2SClass::onTransmit(void(*fcn)(void)) { + i2s_set_callback(fcn); + _onTransmit = fcn; +} + +void I2SClass::onReceive(void(*fcn)(void)) { + i2s_rx_set_callback(fcn); + _onReceive = fcn; +} + +int I2SClass::available() { + if (!_running) return 0; + return i2s_rx_available(); +} + +int I2SClass::availableForWrite() { + if (!_running) return 0; + return i2s_available(); +} + +void I2SClass::flush() { + /* No-op */ +} + +int I2SClass::read() { + if (!_running) return -1; + // Always just read from the peeked value to simplify operation + if (!_havePeeked) { + peek(); + } + if (_havePeeked) { + if (_bps == 16) { + _havePeeked--; + int ret = _peekedData; + _peekedData >>= 16; + return ret; + } else /* _bps == 24 */ { + _havePeeked = 0; + return _peekedData; + } + } + return 0; +} + +int I2SClass::peek() { + if (!_running) return -1; + if (_havePeeked) { + if (_bps == 16) { + int16_t sample = (int16_t)_peekedData; // Will extends sign on return + return sample; + } else { + return _peekedData; + } + } + int16_t l, r; + i2s_read_sample(&l, &r, true); + _peekedData = ((int)l << 16) | (0xffff & (int)r); + _havePeeked = 2; // We now have 2 16-bit quantities which can also be used as 1 32-bit(24-bit) + if (_bps == 16) { + return r; + } else { + return _peekedData; + } +} + +int I2SClass::read(void *buffer, size_t size) { + if (!_running) return -1; + int cnt = 0; + + if ( ((_bps == 24) && (size % 4)) || ((_bps == 16) && (size % 2)) || (size < 2) ) { + return 0; // Invalid, can only read in units of samples + } + // Make sure any peeked data is consumed first + if (_havePeeked) { + if (_bps == 16) { + while (_havePeeked && size) { + uint16_t *p = (uint16_t *)buffer; + *(p++) = _peekedData; + _peekedData >>= 16; + _havePeeked--; + buffer = (void *)p; + size -= 2; + cnt += 2; + } + } else { + uint32_t *p = (uint32_t *)buffer; + *(p++) = _peekedData; + buffer = (void *)p; + size -= 4; + cnt += 4; + } + } + // Now just non-blocking read up to the remaining size + int16_t l, r; + int16_t *p = (int16_t *)buffer; + while (size && i2s_read_sample(&l, &r, false)) { + *(p++) = l; + size--; + cnt++; + if (size) { + *(p++) = r; + size--; + cnt++; + } else { + // We read a simple we can't return, stuff it in the peeked data + _havePeeked = 1; + _peekedData = r; + } + } + return cnt; +} + +size_t I2SClass::write(uint8_t s) { + if (!_running) return 0; + return write((int32_t)s); +} + +size_t I2SClass::write(const uint8_t *buffer, size_t size) { + return write((const void *)buffer, size); +} + +size_t I2SClass::write(int32_t s) { + if (!_running) return 0; + // Because our HW really wants 32b writes, store any 16b writes until another + // 16b write comes in and then send the combined write down. + if (_bps == 16) { + if (_writtenHalf) { + _writtenData <<= 16; + _writtenData |= 0xffff & s; + _writtenHalf = false; + return i2s_write_sample(_writtenData) ? 1 : 0; + } else { + _writtenHalf = true; + _writtenData = s & 0xffff; + return 1; + } + } else { + return i2s_write_sample((uint32_t)s) ? 1 : 0; + } +} + +// SAMD core has this as non-blocking +size_t I2SClass::write(const void *buffer, size_t size) { + if (!_running) return 0; + return i2s_write_buffer_nb((int16_t *)buffer, size / 2); +} + + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_I2S) +I2SClass I2S; +#endif + diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h new file mode 100644 index 000000000..08400d299 --- /dev/null +++ b/libraries/I2S/src/I2S.h @@ -0,0 +1,95 @@ +/* + Based off of ArduinoCore-SAMD I2S interface. Modified for the + ESP8266 by Earle F. Philhower, III + + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _I2S_H_INCLUDED +#define _I2S_H_INCLUDED + +#include +#include + +typedef enum { + I2S_PHILIPS_MODE // Only mode allowed for now by the core +} i2s_mode_t; + +class I2SClass : public Stream +{ +public: + // By default only transmit and drive the clock pins + I2SClass(bool enableTransmit = true, bool enableRecv = false, + bool driveClocks = true); + + // Only 16 and 24 bitsPerSample are allowed by the hardware + // 24-bit is MSB-aligned, with 0x00 in the lowest byte of each element. + int begin(i2s_mode_t mode, long sampleRate, int bitsPerSample); + void end(); + + // from Stream + virtual int available(); + virtual int read(); // Blocking, will wait for incoming data + virtual int peek(); // Blocking, will wait for incoming data + virtual void flush(); + + // from Print (see notes on write() methods below) + virtual size_t write(uint8_t); + virtual size_t write(const uint8_t *buffer, size_t size); + virtual int availableForWrite(); + + // Read up to size samples from the I2S device. Non-blocking, will read + // from 0...size samples and return the count read. Be sure your app handles + // the partial read case (i.e. yield()ing and trying to read more). + int read(void* buffer, size_t size); + + // Write a single sample to the I2S device. Blocking until write succeeds + size_t write(int32_t); + // Write up to size samples to the I2S device. Non-blocking, will write + // from 0...size samples and return that count. Be sure your app handles + // partial writes (i.e. by yield()ing and then retrying to write the + // remaining data. + size_t write(const void *buffer, size_t size); + + // Note that these callback are called from **INTERRUPT CONTEXT** and hence + // must be both stored in IRAM and not perform anything that's not legal in + // an interrupt + void onTransmit(void(*)(void)); + void onReceive(void(*)(void)); + +private: + int _bps; + bool _running; + bool _enableTx; + bool _enableRx; + bool _driveClk; + void (*_onTransmit)(void); + void (*_onReceive)(void); + // Support for peek() on read path + uint32_t _peekedData; + int _havePeeked; + // Support for ::write(x) on 16b wuantities + uint32_t _writtenData; + bool _writtenHalf; +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_I2S) +extern I2SClass I2S; +#endif + +#endif +