1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-07-29 05:21:37 +03:00

Add I2S class support (#7874)

Fixes #427

Adds a basic I2S class based off of the Arduino-SAMD core.  The raw
i2s_xxx functions are still a better  way to use I2S due to their
flexibility, but this will allow basic Arduino sketches to work.
This commit is contained in:
Earle F. Philhower, III
2021-03-07 08:14:07 -08:00
committed by GitHub
parent 9fc5afd5fd
commit e99df4fe1a
9 changed files with 520 additions and 88 deletions

View File

@ -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 <I2S.h>
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);
}
}

View File

@ -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 <earlephilhower@yahoo.com>
*/
#include <I2S.h>
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++;
}

View File

@ -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

View File

@ -0,0 +1,9 @@
name=I2S
version=1.0
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
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

211
libraries/I2S/src/I2S.cpp Normal file
View File

@ -0,0 +1,211 @@
/*
Based off of ArduinoCore-SAMD I2S interface. Modified for the
ESP8266 by Earle F. Philhower, III <earlephilhower@yahoo.com>
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 <Arduino.h>
#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

95
libraries/I2S/src/I2S.h Normal file
View File

@ -0,0 +1,95 @@
/*
Based off of ArduinoCore-SAMD I2S interface. Modified for the
ESP8266 by Earle F. Philhower, III <earlephilhower@yahoo.com>
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 <Arduino.h>
#include <core_esp8266_i2s.h>
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