/* 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