1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-19 23:22:16 +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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 520 additions and 88 deletions

View File

@ -24,7 +24,7 @@
#include "osapi.h" #include "osapi.h"
#include "ets_sys.h" #include "ets_sys.h"
#include "i2s_reg.h" #include "i2s_reg.h"
#include "i2s.h" #include "core_esp8266_i2s.h"
extern "C" { extern "C" {
@ -194,11 +194,11 @@ static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
} }
void i2s_set_callback(void (*callback) (void)) { void i2s_set_callback(void (*callback) (void)) {
tx->callback = callback; if (tx) tx->callback = callback;
} }
void i2s_rx_set_callback(void (*callback) (void)) { void i2s_rx_set_callback(void (*callback) (void)) {
rx->callback = callback; if (rx) rx->callback = callback;
} }
static bool _alloc_channel(i2s_state_t *ch) { 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 // 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. // 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; uint16_t frames_written=0;
while(frame_count>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; 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) { bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
if (!rx) { if (!rx) {

View File

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

View File

@ -1,81 +1,12 @@
/* // This include file is a hack to ensure backward compatibility with
i2s.h - Software I2S library for esp8266 // 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. #include "../../libraries/I2S/src/I2S.h"
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

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