Skip to content

Commit

Permalink
Add I2S class support (#7874)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
earlephilhower committed Mar 7, 2021
1 parent 9fc5afd commit e99df4f
Show file tree
Hide file tree
Showing 9 changed files with 521 additions and 89 deletions.
16 changes: 8 additions & 8 deletions cores/esp8266/core_esp8266_i2s.cpp
Expand Up @@ -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" {

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
81 changes: 81 additions & 0 deletions 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
93 changes: 12 additions & 81 deletions cores/esp8266/i2s.h
@@ -1,81 +1,12 @@
/*
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(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
// 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.

#include "../../libraries/I2S/src/I2S.h"
36 changes: 36 additions & 0 deletions 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 <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);
}
}
46 changes: 46 additions & 0 deletions 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 <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++;
}

23 changes: 23 additions & 0 deletions 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
9 changes: 9 additions & 0 deletions libraries/I2S/library.properties
@@ -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

0 comments on commit e99df4f

Please sign in to comment.