[I2S][SR] Add new I2S library and Sound Recognition support (#8714)

* [I2S][SR] Add new I2S library and Sound Recognition support

* periman + TDM and PDM support

* separated init function + default values for pins

* fix SR example

* fix init functions

* remove old I2S from CmakeList

* Add ESP_I2S and ESP_SR to Cmakelist includedirs

* TDM slot_mask fix

* Peripheral manager pin check fix

* Compile ESP_SR Arduino code only if ESP-SR is available as component

* Guard I2S modes, depending on what is supported by the chip

* add check if i2s is supported

* Remove old I2S Example

---------

Co-authored-by: Jan Procházka <90197375+P-R-O-C-H-Y@users.noreply.github.com>
This commit is contained in:
Me No Dev 2023-10-18 12:05:26 +03:00 committed by GitHub
parent 4114c663b5
commit 601efed98f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1924 additions and 1893 deletions

View File

@ -86,6 +86,7 @@ jobs:
name: Build with ESP-IDF ${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
# The version names here correspond to the versions of espressif/idf Docker image.
# See https://hub.docker.com/r/espressif/idf/tags and

View File

@ -82,6 +82,9 @@ set(LIBRARY_SRCS
libraries/BluetoothSerial/src/BTScanResultsSet.cpp
libraries/DNSServer/src/DNSServer.cpp
libraries/EEPROM/src/EEPROM.cpp
libraries/ESP_I2S/src/ESP_I2S.cpp
libraries/ESP_SR/src/ESP_SR.cpp
libraries/ESP_SR/src/esp32-hal-sr.c
libraries/ESPmDNS/src/ESPmDNS.cpp
libraries/Ethernet/src/ETH.cpp
libraries/FFat/src/FFat.cpp
@ -91,7 +94,6 @@ set(LIBRARY_SRCS
libraries/HTTPUpdate/src/HTTPUpdate.cpp
libraries/LittleFS/src/LittleFS.cpp
libraries/Insights/src/Insights.cpp
libraries/I2S/src/I2S.cpp
libraries/NetBIOS/src/NetBIOS.cpp
libraries/Preferences/src/Preferences.cpp
libraries/RainMaker/src/RMaker.cpp
@ -179,6 +181,8 @@ set(includedirs
libraries/BluetoothSerial/src
libraries/DNSServer/src
libraries/EEPROM/src
libraries/ESP_I2S/src
libraries/ESP_SR/src
libraries/ESP32/src
libraries/ESPmDNS/src
libraries/Ethernet/src
@ -188,7 +192,6 @@ set(includedirs
libraries/HTTPUpdate/src
libraries/LittleFS/src
libraries/Insights/src
libraries/I2S/src
libraries/NetBIOS/src
libraries/Preferences/src
libraries/RainMaker/src

View File

@ -1,136 +0,0 @@
/*
This example demonstrates I2S ADC capability to sample high frequency analog signals.
The PWM signal generated with ledc is only for ease of use when first trying out.
To sample the generated signal connect default pins 27(PWM) and 32(Sampling) together.
If you do not wish to generate PWM simply comment out the definition of constant GENERATE_PWM
Try to change the PWM_DUTY_PERCENT and see how to averaged value changes.
The maximum for I2S ADC sampling frequency is 5MHz (value 5000000), however there will be many values repeated because the real
sampling frequency is much lower -
By default this example will print values compatible with Arduino plotter
1. signal - all values
2. signal - averaged value
You can change the number of sample over which is the signal averaged by changing value of AVERAGE_EVERY_N_SAMPLES
If you comment the definition altogether the averaging will not be performed nor printed.
If you do not wish to print every value, simply comment definition of constant PRINT_ALL_VALUES
Note: ESP prints messages at startup which will pollute Arduino IDE Serial plotter legend.
To avoid this pollution, start the plotter after startup (op restart)
*/
#include <driver/i2s.h>
// I2S
#define I2S_SAMPLE_RATE (277777) // Max sampling frequency = 277.777 kHz
#define ADC_INPUT (ADC1_CHANNEL_4) //pin 32
#define I2S_DMA_BUF_LEN (1024)
// PWM
#define GENERATE_PWM
#define OUTPUT_PIN (27)
#define PWM_FREQUENCY ((I2S_SAMPLE_RATE)/4)
#define PWM_DUTY_PERCENT (50)
#define PWM_RESOLUTION_BITS (2) // Lower bit resolution enables higher frequency
#define PWM_DUTY_VALUE ((((1<<(PWM_RESOLUTION_BITS)))*(PWM_DUTY_PERCENT))/100) // Duty value used for setup function based on resolution
// Sample post processing
#define PRINT_ALL_VALUES
#define AVERAGE_EVERY_N_SAMPLES (100)
void i2sInit(){
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
.sample_rate = I2S_SAMPLE_RATE, // The format of the signal using ADC_BUILT_IN
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = I2S_DMA_BUF_LEN,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_128,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT
};
Serial.printf("Attempting to setup I2S ADC with sampling frequency %d Hz\n", I2S_SAMPLE_RATE);
if(ESP_OK != i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL)){
Serial.printf("Error installing I2S. Halt!");
while(1);
}
if(ESP_OK != i2s_set_adc_mode(ADC_UNIT_1, ADC_INPUT)){
Serial.printf("Error setting up ADC. Halt!");
while(1);
}
if(ESP_OK != adc1_config_channel_atten(ADC_INPUT, ADC_ATTEN_DB_11)){
Serial.printf("Error setting up ADC attenuation. Halt!");
while(1);
}
if(ESP_OK != i2s_adc_enable(I2S_NUM_0)){
Serial.printf("Error enabling ADC. Halt!");
while(1);
}
Serial.printf("I2S ADC setup ok\n");
}
void setup() {
Serial.begin(115200);
#ifdef GENERATE_PWM
// PWM setup
Serial.printf("Setting up PWM: frequency = %d; resolution bits %d; Duty cycle = %d; duty value = %d, Output pin = %d\n", PWM_FREQUENCY, PWM_RESOLUTION_BITS, PWM_DUTY_PERCENT, PWM_DUTY_VALUE, OUTPUT_PIN);
ledcAttach(OUTPUT_PIN, PWM_FREQUENCY, PWM_RESOLUTION_BITS);
uint32_t freq = ledcReadFreq(OUTPUT_PIN);
if(freq != PWM_FREQUENCY){
Serial.printf("Error setting up PWM. Halt!");
while(1);
}
ledcWrite(OUTPUT_PIN, PWM_DUTY_VALUE);
Serial.printf("PWM setup ok\n");
#endif
// Initialize the I2S peripheral
i2sInit();
}
void loop(){
// The 4 high bits are the channel, and the data is inverted
size_t bytes_read;
uint16_t buffer[I2S_DMA_BUF_LEN] = {0};
#ifdef AVERAGE_EVERY_N_SAMPLES
uint32_t read_counter = 0;
uint32_t averaged_reading = 0;
uint64_t read_sum = 0;
#endif
while(1){
i2s_read(I2S_NUM_0, &buffer, sizeof(buffer), &bytes_read, 15);
//Serial.printf("read %d Bytes\n", bytes_read);
for(int i = 0; i < bytes_read/2; ++i){
#ifdef PRINT_ALL_VALUES
//Serial.printf("[%d] = %d\n", i, buffer[i] & 0x0FFF); // Print with indexes
Serial.printf("Signal:%d ", buffer[i] & 0x0FFF); // Print compatible with Arduino Plotter
#endif
#ifdef AVERAGE_EVERY_N_SAMPLES
read_sum += buffer[i] & 0x0FFF;
++read_counter;
if(read_counter == AVERAGE_EVERY_N_SAMPLES){
averaged_reading = read_sum / AVERAGE_EVERY_N_SAMPLES;
//Serial.printf("averaged_reading = %d over %d samples\n", averaged_reading, read_counter); // Print with additional info
Serial.printf("Averaged_signal:%ld", averaged_reading); // Print compatible with Arduino Plotter
read_counter = 0;
read_sum = 0;
}
#endif
#if defined(PRINT_ALL_VALUES) || defined (AVERAGE_EVERY_N_SAMPLES)
Serial.printf("\n");
#endif
} // for
} // while
}

View File

@ -0,0 +1,21 @@
#######################################
# Syntax Coloring Map For ESP_I2S
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
ESP_I2S KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
onEvent KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
SR_EVENT_WAKEWORD LITERAL1

View File

@ -0,0 +1,9 @@
name=ESP_I2S
version=1.0.0
author=me-no-dev
maintainer=me-no-dev
sentence=Library for ESP I2S communication
paragraph=Supports ESP32 Arduino platforms.
category=Sound
url=https://github.com/espressif/arduino-esp32/
architectures=esp32

View File

@ -0,0 +1,807 @@
#include "ESP_I2S.h"
#if SOC_I2S_SUPPORTED
#include "esp32-hal-periman.h"
#include "wav_header.h"
#include "mp3dec.h"
#define I2S_READ_CHUNK_SIZE 1920
#define I2S_DEFAULT_CFG() { \
.id = I2S_NUM_AUTO, \
.role = I2S_ROLE_MASTER, \
.dma_desc_num = 6, \
.dma_frame_num = 240, \
.auto_clear = true, \
}
#define I2S_STD_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \
{ \
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
.gpio_cfg = { \
.mclk = (gpio_num_t)_mclk, \
.bclk = (gpio_num_t)_bclk, \
.ws = (gpio_num_t)_ws, \
.dout = (gpio_num_t)_dout, \
.din = (gpio_num_t)_din, \
.invert_flags = { \
.mclk_inv = _mclk_inv, \
.bclk_inv = _bclk_inv, \
.ws_inv = _ws_inv, \
}, \
}, \
}
#if SOC_I2S_SUPPORTS_TDM
#define I2S_TDM_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode, _mask) \
{ \
.clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_TDM_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode, _mask), \
.gpio_cfg = { \
.mclk = (gpio_num_t)_mclk, \
.bclk = (gpio_num_t)_bclk, \
.ws = (gpio_num_t)_ws, \
.dout = (gpio_num_t)_dout, \
.din = (gpio_num_t)_din, \
.invert_flags = { \
.mclk_inv = _mclk_inv, \
.bclk_inv = _bclk_inv, \
.ws_inv = _ws_inv, \
}, \
}, \
}
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
#if (SOC_I2S_PDM_MAX_TX_LINES > 1)
#define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \
{ \
.clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
.gpio_cfg = { \
.clk = (gpio_num_t)_tx_clk, \
.dout = (gpio_num_t)_tx_dout0, \
.dout2 = (gpio_num_t)_tx_dout1, \
.invert_flags = { \
.clk_inv = _tx_clk_inv, \
}, \
}, \
}
#else
#define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \
{ \
.clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
.gpio_cfg = { \
.clk = (gpio_num_t)_tx_clk, \
.dout = (gpio_num_t)_tx_dout0, \
.invert_flags = { \
.clk_inv = _tx_clk_inv, \
}, \
}, \
}
#endif
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
#if (SOC_I2S_PDM_MAX_RX_LINES > 1)
#define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \
{ \
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
.gpio_cfg = { \
.clk = (gpio_num_t)_rx_clk, \
.dins = { \
(gpio_num_t)_rx_din0, \
(gpio_num_t)_rx_din1, \
(gpio_num_t)_rx_din2, \
(gpio_num_t)_rx_din3, \
}, \
.invert_flags = { \
.clk_inv = _rx_clk_inv, \
}, \
}, \
}
#else
#define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \
{ \
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
.gpio_cfg = { \
.clk = (gpio_num_t)_rx_clk, \
.din = (gpio_num_t)_rx_din0, \
.invert_flags = { \
.clk_inv = _rx_clk_inv, \
}, \
}, \
}
#endif
#endif
#define I2S_ERROR_CHECK_RETURN(x,r) do { last_error = (x); if(unlikely(last_error != ESP_OK)){ log_e("ERROR: %s", esp_err_to_name(last_error)); return (r); } } while(0)
#define I2S_ERROR_CHECK_RETURN_FALSE(x) I2S_ERROR_CHECK_RETURN(x,false)
// Default read, no resmpling and temp buffer necessary
static esp_err_t i2s_channel_read_default(i2s_chan_handle_t handle, char * tmp_buf, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){
return i2s_channel_read(handle, (char *)dst, len, bytes_read, timeout_ms);
}
// Resample the 32bit SPH0645 microphone data into 16bit. SPH0645 is actually 18 bit, but this trick helps save some space
static esp_err_t i2s_channel_read_32_to_16(i2s_chan_handle_t handle, char * read_buff, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){
size_t out_len = 0;
size_t read_buff_len = len * 2;
if(read_buff == NULL){
log_e("Temp buffer is NULL!");
return ESP_FAIL;
}
esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms);
if (err != ESP_OK){
*bytes_read = 0;
return err;
}
out_len /= 4;
uint16_t * ds = (uint16_t*)dst;
uint32_t * src = (uint32_t*)read_buff;
for (size_t i = 0; i < out_len; i++){
ds[i] = src[i] >> 16;
}
*bytes_read = out_len * 2;
return ESP_OK;
}
// Resample the 16bit stereo microphone data into 16bit mono.
static esp_err_t i2s_channel_read_16_stereo_to_mono(i2s_chan_handle_t handle, char * read_buff, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){
size_t out_len = 0;
size_t read_buff_len = len * 2;
if(read_buff == NULL){
log_e("Temp buffer is NULL!");
return ESP_FAIL;
}
esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms);
if (err != ESP_OK){
*bytes_read = 0;
return err;
}
out_len /= 2;
uint16_t * ds = (uint16_t*)dst;
uint16_t * src = (uint16_t*)read_buff;
for (size_t i = 0; i < out_len; i+=2){
*ds++ = src[i];
}
*bytes_read = out_len;
return ESP_OK;
}
I2SClass::I2SClass(){
last_error = ESP_OK;
tx_chan = NULL;
tx_sample_rate = 0;
tx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
tx_slot_mode = I2S_SLOT_MODE_STEREO;
rx_fn = i2s_channel_read_default;
rx_transform = I2S_RX_TRANSFORM_NONE;
rx_transform_buf = NULL;
rx_transform_buf_len = 0;
rx_chan = NULL;
rx_sample_rate = 0;
rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
rx_slot_mode = I2S_SLOT_MODE_STEREO;
_mclk = -1;
_bclk = -1;
_ws = -1;
_dout = -1;
_din = -1;
_mclk_inv = false;
_bclk_inv = false;
_ws_inv = false;
#if SOC_I2S_SUPPORTS_PDM_TX
_tx_clk = -1;
_tx_dout0 = -1;
_tx_dout1 = -1;
_tx_clk_inv = false;
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
_rx_clk = -1;
_rx_din0 = -1;
_rx_din1 = -1;
_rx_din2 = -1;
_rx_din3 = -1;
_rx_clk_inv = false;
#endif
}
I2SClass::~I2SClass(){
end();
}
bool I2SClass::i2sDetachBus(void * bus_pointer){
I2SClass *bus = (I2SClass *) bus_pointer;
if(bus->tx_chan != NULL || bus->tx_chan != NULL){
bus->end();
}
return true;
}
// Set pins for STD and TDM mode
void I2SClass::setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din, int8_t mclk){
_mclk = mclk;
_bclk = bclk;
_ws = ws;
_dout = dout;
_din = din;
}
void I2SClass::setInverted(bool bclk, bool ws, bool mclk){
_mclk_inv = mclk;
_bclk_inv = bclk;
_ws_inv = ws;
}
// Set pins for PDM TX mode
#if SOC_I2S_SUPPORTS_PDM_TX
void I2SClass::setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1){
_tx_clk = clk;
_tx_dout0 = dout0;
#if (SOC_I2S_PDM_MAX_TX_LINES > 1)
_tx_dout1 = dout1;
#endif
}
#endif
// Set pins for PDM RX mode
#if SOC_I2S_SUPPORTS_PDM_RX
void I2SClass::setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1, int8_t din2, int8_t din3){
_rx_clk = clk;
_rx_din0 = din0;
#if (SOC_I2S_PDM_MAX_RX_LINES > 1)
_rx_din1 = din1;
_rx_din2 = din2;
_rx_din3 = din3;
#endif
}
#endif
#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX
void I2SClass::setInvertedPdm(bool clk){
#if SOC_I2S_SUPPORTS_PDM_TX
_tx_clk_inv = clk;
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
_rx_clk_inv = clk;
#endif
}
#endif
bool I2SClass::initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){
// Peripheral manager deinit previous peripheral if pin was used
if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
// I2S configuration
i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG();
if (_dout >= 0 && _din >= 0) {
I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan));
} else if (_dout >= 0) {
I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL));
} else if (_din >= 0) {
I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan));
}
i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch);
if (tx_chan != NULL) {
tx_sample_rate = rate;
tx_data_bit_width = bits_cfg;
tx_slot_mode = ch;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(tx_chan, &i2s_config));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan));
}
if (rx_chan != NULL) {
rx_sample_rate = rate;
rx_data_bit_width = bits_cfg;
rx_slot_mode = ch;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(rx_chan, &i2s_config));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
}
// Peripheral manager set bus type to I2S
if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; }
if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; }
if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; }
if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; }
if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; }
return true;
err:
log_e("Failed to set all pins bus to I2S_STD");
I2SClass::i2sDetachBus((void *)(this));
return false;
}
#if SOC_I2S_SUPPORTS_TDM
bool I2SClass::initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask){
// Peripheral manager deinit previous peripheral if pin was used
if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
// I2S configuration
i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG();
if (_dout >= 0 && _din >= 0) {
I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan));
} else if (_dout >= 0) {
I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL));
} else if (_din >= 0) {
I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan));
}
i2s_tdm_config_t i2s_tdm_config = I2S_TDM_CHAN_CFG(rate, bits_cfg, ch, (i2s_tdm_slot_mask_t)slot_mask);
if (tx_chan != NULL) {
tx_sample_rate = rate;
tx_data_bit_width = bits_cfg;
tx_slot_mode = ch;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(tx_chan, &i2s_tdm_config));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan));
}
if (rx_chan != NULL) {
rx_sample_rate = rate;
rx_data_bit_width = bits_cfg;
rx_slot_mode = ch;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(rx_chan, &i2s_tdm_config));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
}
// Peripheral manager set bus type to I2S
if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; }
if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; }
if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; }
if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; }
if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; }
return true;
err:
log_e("Failed to set all pins bus to I2S_TDM");
I2SClass::i2sDetachBus((void *)(this));
return false;
}
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
bool I2SClass::initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){
// Peripheral manager deinit previous peripheral if pin was used
if (_tx_clk >= 0) if (!perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_tx_dout0 >= 0) if (!perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_tx_dout1 >= 0) if (!perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
// I2S configuration
i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG();
I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL));
i2s_pdm_tx_config_t i2s_pdm_tx_config = I2S_PDM_TX_CHAN_CFG(rate, bits_cfg, ch);
if (tx_chan != NULL) {
tx_sample_rate = rate;
tx_data_bit_width = bits_cfg;
tx_slot_mode = ch;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_tx_mode(tx_chan, &i2s_pdm_tx_config));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan));
}
// Peripheral manager set bus type to I2S
if (_tx_clk >= 0) if (!perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; }
if (_tx_dout0 >= 0) if (!perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; }
if (_tx_dout1 >= 0) if (!perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; }
return true;
err:
log_e("Failed to set all pins bus to I2S_TDM");
I2SClass::i2sDetachBus((void *)(this));
return false;
}
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
bool I2SClass::initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){
// Peripheral manager deinit previous peripheral if pin was used
if (_rx_clk >= 0) if (!perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_rx_din0 >= 0) if (!perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_rx_din1 >= 0) if (!perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_rx_din2 >= 0) if (!perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
if (_rx_din3 >= 0) if (!perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_INIT, NULL)){ return false; }
// I2S configuration
i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG();
I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan));
i2s_pdm_rx_config_t i2s_pdf_rx_config = I2S_PDM_RX_CHAN_CFG(rate, bits_cfg, ch);
if (rx_chan != NULL) {
rx_sample_rate = rate;
rx_data_bit_width = bits_cfg;
rx_slot_mode = ch;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_rx_mode(rx_chan, &i2s_pdf_rx_config));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
}
// Peripheral manager set bus type to I2S
if (_rx_clk >= 0) if (!perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; }
if (_rx_din0 >= 0) if (!perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; }
if (_rx_din1 >= 0) if (!perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; }
if (_rx_din2 >= 0) if (!perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; }
if (_rx_din3 >= 0) if (!perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; }
return true;
err:
log_e("Failed to set all pins bus to I2S_TDM");
I2SClass::i2sDetachBus((void *)(this));
return false;
}
#endif
bool I2SClass::begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch
#if SOC_I2S_SUPPORTS_TDM
, int8_t slot_mask
#endif
){
/* Setup I2S peripheral */
if (mode >= I2S_MODE_MAX){
log_e("Invalid I2S mode selected.");
return false;
}
_mode = mode;
bool init = false;
switch (_mode){
case I2S_MODE_STD:
init = initSTD(rate, bits_cfg, ch);
break;
#if SOC_I2S_SUPPORTS_TDM
case I2S_MODE_TDM:
init = initTDM(rate, bits_cfg, ch, slot_mask);
break;
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
case I2S_MODE_PDM_TX:
init = initPDMtx(rate, bits_cfg, ch);
break;
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
case I2S_MODE_PDM_RX:
init = initPDMrx(rate, bits_cfg, ch);
break;
#endif
default:
break;
}
if (init == false){
log_e("I2S initialization failed.");
return false;
}
return true;
}
bool I2SClass::end(){
if (tx_chan != NULL){
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(tx_chan));
tx_chan = NULL;
}
if (rx_chan != NULL){
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(rx_chan));
rx_chan = NULL;
}
if(rx_transform_buf != NULL){
free(rx_transform_buf);
rx_transform_buf = NULL;
rx_transform_buf_len = 0;
}
//Peripheral manager deinit used pins
switch (_mode){
case I2S_MODE_STD:
#if SOC_I2S_SUPPORTS_TDM
case I2S_MODE_TDM:
#endif
perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL);
perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL);
perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL);
if (_dout >= 0) perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL);
if (_din >= 0) perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL);
break;
#if SOC_I2S_SUPPORTS_PDM_TX
case I2S_MODE_PDM_TX:
perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_INIT, NULL);
if (_tx_dout0 >= 0) perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_INIT, NULL);
if (_tx_dout1 >= 0) perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_INIT, NULL);
break;
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
case I2S_MODE_PDM_RX:
perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_INIT, NULL);
if (_rx_din0 >= 0) perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_INIT, NULL);
if (_rx_din1 >= 0) perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_INIT, NULL);
if (_rx_din2 >= 0) perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_INIT, NULL);
if (_rx_din3 >= 0) perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_INIT, NULL);
break;
#endif
default:
break;
}
return true;
}
bool I2SClass::configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){
/* Setup I2S channels */
if (tx_chan != NULL) {
if(tx_sample_rate == rate && tx_data_bit_width == bits_cfg && tx_slot_mode == ch){
return true;
}
i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch);
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(tx_chan, &i2s_config.clk_cfg));
tx_sample_rate = rate;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(tx_chan, &i2s_config.slot_cfg));
tx_data_bit_width = bits_cfg;
tx_slot_mode = ch;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan));
return true;
}
return false;
}
bool I2SClass::configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform){
/* Setup I2S channels */
if (rx_chan != NULL) {
if(rx_sample_rate != rate || rx_data_bit_width != bits_cfg || rx_slot_mode != ch){
i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch);
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan));
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(rx_chan, &i2s_config.clk_cfg));
rx_sample_rate = rate;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(rx_chan, &i2s_config.slot_cfg));
rx_data_bit_width = bits_cfg;
rx_slot_mode = ch;
I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
return transformRX(transform);
}
if(rx_transform != transform){
return transformRX(transform);
}
return true;
}
return false;
}
size_t I2SClass::readBytes(char *buffer, size_t size){
size_t bytes_read = 0;
size_t total_size = 0;
last_error = ESP_FAIL;
if(rx_chan == NULL){
return total_size;
}
while (total_size < size) {
bytes_read = size - total_size;
if(rx_transform_buf != NULL && bytes_read > I2S_READ_CHUNK_SIZE){ bytes_read = I2S_READ_CHUNK_SIZE; }
I2S_ERROR_CHECK_RETURN(rx_fn(rx_chan, rx_transform_buf, (char*)(buffer + total_size), bytes_read, &bytes_read, _timeout), 0);
total_size += bytes_read;
}
return total_size;
}
size_t I2SClass::write(uint8_t *buffer, size_t size){
size_t written = 0;
size_t bytes_sent = 0;
last_error = ESP_FAIL;
if(tx_chan == NULL){
return written;
}
while(written < size){
bytes_sent = size - written;
esp_err_t err = i2s_channel_write(tx_chan, (char*)(buffer + written), bytes_sent, &bytes_sent, _timeout);
setWriteError(err);
I2S_ERROR_CHECK_RETURN(err, written);
written += bytes_sent;
}
return written;
}
i2s_chan_handle_t I2SClass::txChan(){ return tx_chan; }
uint32_t I2SClass::txSampleRate(){ return tx_sample_rate; }
i2s_data_bit_width_t I2SClass::txDataWidth(){ return tx_data_bit_width; }
i2s_slot_mode_t I2SClass::txSlotMode(){ return tx_slot_mode; }
i2s_chan_handle_t I2SClass::rxChan(){ return rx_chan; }
uint32_t I2SClass::rxSampleRate(){ return rx_sample_rate; }
i2s_data_bit_width_t I2SClass::rxDataWidth(){ return rx_data_bit_width; }
i2s_slot_mode_t I2SClass::rxSlotMode(){ return rx_slot_mode; }
int I2SClass::lastError(){
return (int)last_error;
}
int I2SClass::available(){
if(rx_chan == NULL){
return -1;
}
return I2S_READ_CHUNK_SIZE;// / (rx_data_bit_width/8);
};
int I2SClass::peek(){
return -1;
};
int I2SClass::read(){
int out = 0;
if(readBytes((char *)&out, rx_data_bit_width/8) == (rx_data_bit_width/8)){
return out;
}
return -1;
};
size_t I2SClass::write(uint8_t d){
return write(&d, 1);
}
bool I2SClass::transformRX(i2s_rx_transform_t transform){
switch(transform){
case I2S_RX_TRANSFORM_NONE:
allocTranformRX(0);
rx_fn = i2s_channel_read_default;
break;
case I2S_RX_TRANSFORM_32_TO_16:
if(rx_data_bit_width != I2S_DATA_BIT_WIDTH_32BIT){
log_e("Wrong data width. Should be 32bit");
return false;
}
if(!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)){
return false;
}
rx_fn = i2s_channel_read_32_to_16;
rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
break;
case I2S_RX_TRANSFORM_16_STEREO_TO_MONO:
if(rx_slot_mode != I2S_SLOT_MODE_STEREO){
log_e("Wrong slot mode. Should be Stereo");
return false;
}
if(!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)){
return false;
}
rx_fn = i2s_channel_read_16_stereo_to_mono;
rx_slot_mode = I2S_SLOT_MODE_MONO;
break;
default:
log_e("Unknown RX Transform %d", transform);
return false;
}
rx_transform = transform;
return true;
}
bool I2SClass::allocTranformRX(size_t buf_len){
char* buf = NULL;
if(buf_len == 0){
if(rx_transform_buf != NULL){
free(rx_transform_buf);
rx_transform_buf = NULL;
rx_transform_buf_len = 0;
}
return true;
}
if(rx_transform_buf == NULL || rx_transform_buf_len != buf_len){
buf = (char*)malloc(buf_len);
if(buf == NULL){
log_e("malloc %u failed!", buf_len);
return false;
}
if(rx_transform_buf != NULL){
free(rx_transform_buf);
}
rx_transform_buf = buf;
rx_transform_buf_len = buf_len;
}
return true;
}
const int WAVE_HEADER_SIZE = PCM_WAV_HEADER_SIZE;
//Record PCM WAV with current RX settings
uint8_t * I2SClass::recordWAV(size_t rec_seconds, size_t * out_size){
uint32_t sample_rate = rxSampleRate();
uint16_t sample_width = (uint16_t)rxDataWidth();
uint16_t num_channels = (uint16_t)rxSlotMode();
size_t rec_size = rec_seconds * ((sample_rate * (sample_width / 8)) * num_channels);
const pcm_wav_header_t wav_header = PCM_WAV_HEADER_DEFAULT(rec_size, sample_width, sample_rate, num_channels);
*out_size = 0;
log_d("Record WAV: rate:%lu, bits:%u, channels:%u, size:%lu", sample_rate, sample_width, num_channels, rec_size);
uint8_t * wav_buf = (uint8_t*)malloc(rec_size + WAVE_HEADER_SIZE);
if(wav_buf == NULL){
log_e("Failed to allocate WAV buffer with size %u", rec_size + WAVE_HEADER_SIZE);
return NULL;
}
memcpy(wav_buf, &wav_header, WAVE_HEADER_SIZE);
size_t wav_size = readBytes((char*)(wav_buf + WAVE_HEADER_SIZE), rec_size);
if(wav_size < rec_size){
log_e("Recorded %u bytes from %u", wav_size, rec_size);
} else if(lastError()){
log_e("Read Failed! %d", lastError());
} else {
*out_size = rec_size + WAVE_HEADER_SIZE;
return wav_buf;
}
free(wav_buf);
return NULL;
}
void I2SClass::playWAV(uint8_t * data, size_t len){
pcm_wav_header_t * header = (pcm_wav_header_t*)data;
if(header->fmt_chunk.audio_format != 1){
log_e("Audio format is not PCM!");
return;
}
wav_data_chunk_t * data_chunk = &header->data_chunk;
size_t data_offset = 0;
while(memcmp(data_chunk->subchunk_id, "data", 4) != 0){
log_d("Skip chunk: %c%c%c%c, len: %lu", data_chunk->subchunk_id[0], data_chunk->subchunk_id[1], data_chunk->subchunk_id[2], data_chunk->subchunk_id[3], data_chunk->subchunk_size + 8);
data_offset += data_chunk->subchunk_size + 8;
data_chunk = (wav_data_chunk_t *)(data + WAVE_HEADER_SIZE + data_offset - 8);
}
log_d("Play WAV: rate:%lu, bits:%d, channels:%d, size:%lu", header->fmt_chunk.sample_rate, header->fmt_chunk.bits_per_sample, header->fmt_chunk.num_of_channels, data_chunk->subchunk_size);
configureTX(header->fmt_chunk.sample_rate, (i2s_data_bit_width_t)header->fmt_chunk.bits_per_sample, (i2s_slot_mode_t)header->fmt_chunk.num_of_channels);
write(data + WAVE_HEADER_SIZE + data_offset, data_chunk->subchunk_size);
}
bool I2SClass::playMP3(uint8_t *src, size_t src_len)
{
int16_t outBuf[MAX_NCHAN * MAX_NGRAN * MAX_NSAMP];
uint8_t *readPtr=NULL;
int bytesAvailable=0, err=0, offset=0;
MP3FrameInfo frameInfo;
HMP3Decoder decoder=NULL;
bytesAvailable = src_len;
readPtr = src;
decoder = MP3InitDecoder();
if (decoder == NULL){
log_e("Could not allocate decoder");
return false;
}
do {
offset = MP3FindSyncWord(readPtr, bytesAvailable);
if (offset < 0) {
break;
}
readPtr += offset;
bytesAvailable -= offset;
err = MP3Decode(decoder, &readPtr, &bytesAvailable, outBuf, 0);
if (err) {
log_e("Decode ERROR: %d", err);
MP3FreeDecoder(decoder);
return false;
} else {
MP3GetLastFrameInfo(decoder, &frameInfo);
configureTX(frameInfo.samprate, (i2s_data_bit_width_t)frameInfo.bitsPerSample, (i2s_slot_mode_t)frameInfo.nChans);
write((uint8_t*)outBuf, (size_t)((frameInfo.bitsPerSample / 8) * frameInfo.outputSamps));
}
} while (true);
MP3FreeDecoder(decoder);
return true;
}
#endif /* SOC_I2S_SUPPORTED */

View File

@ -0,0 +1,145 @@
#pragma once
#include "soc/soc_caps.h"
#if SOC_I2S_SUPPORTED
#include "Arduino.h"
#include "esp_err.h"
#include "driver/i2s_std.h"
#if SOC_I2S_SUPPORTS_TDM
#include "driver/i2s_tdm.h"
#endif
#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX
#include "driver/i2s_pdm.h"
#endif
typedef esp_err_t (*i2s_channel_read_fn)(i2s_chan_handle_t handle, char * tmp_buf, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms);
typedef enum {
I2S_MODE_STD,
#if SOC_I2S_SUPPORTS_TDM
I2S_MODE_TDM,
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
I2S_MODE_PDM_TX,
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
I2S_MODE_PDM_RX,
#endif
I2S_MODE_MAX
} i2s_mode_t;
typedef enum {
I2S_RX_TRANSFORM_NONE,
I2S_RX_TRANSFORM_32_TO_16,
I2S_RX_TRANSFORM_16_STEREO_TO_MONO,
I2S_RX_TRANSFORM_MAX
} i2s_rx_transform_t;
class I2SClass: public Stream {
public:
I2SClass();
~I2SClass();
//STD + TDM mode
void setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din=-1, int8_t mclk=-1);
void setInverted(bool bclk, bool ws, bool mclk=false);
//PDM TX + PDM RX mode
#if SOC_I2S_SUPPORTS_PDM_TX
void setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1=-1);
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
void setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1=-1, int8_t din2=-1, int8_t din3=-1);
#endif
#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX
void setInvertedPdm(bool clk);
#endif
bool begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch
#if SOC_I2S_SUPPORTS_TDM
, int8_t slot_mask=-1
#endif
);
bool configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch);
bool configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform=I2S_RX_TRANSFORM_NONE);
bool end();
size_t readBytes(char *buffer, size_t size);
size_t write(uint8_t *buffer, size_t size);
i2s_chan_handle_t txChan();
uint32_t txSampleRate();
i2s_data_bit_width_t txDataWidth();
i2s_slot_mode_t txSlotMode();
i2s_chan_handle_t rxChan();
uint32_t rxSampleRate();
i2s_data_bit_width_t rxDataWidth();
i2s_slot_mode_t rxSlotMode();
int lastError();
int available();
int peek();
int read();
size_t write(uint8_t d);
// Record short PCM WAV to memory with current RX settings. Returns buffer that must be freed by the user.
uint8_t * recordWAV(size_t rec_seconds, size_t * out_size);
// Play short PCM WAV from memory
void playWAV(uint8_t * data, size_t len);
// Play short MP3 from memory
bool playMP3(uint8_t *src, size_t src_len);
private:
esp_err_t last_error;
i2s_mode_t _mode;
i2s_chan_handle_t tx_chan;
uint32_t tx_sample_rate;
i2s_data_bit_width_t tx_data_bit_width;
i2s_slot_mode_t tx_slot_mode;
i2s_channel_read_fn rx_fn;
i2s_rx_transform_t rx_transform;
char * rx_transform_buf;
size_t rx_transform_buf_len;
i2s_chan_handle_t rx_chan;
uint32_t rx_sample_rate;
i2s_data_bit_width_t rx_data_bit_width;
i2s_slot_mode_t rx_slot_mode;
//STD and TDM mode
int8_t _mclk, _bclk, _ws, _dout, _din;
bool _mclk_inv, _bclk_inv, _ws_inv;
//PDM mode
#if SOC_I2S_SUPPORTS_PDM_RX
int8_t _rx_clk, _rx_din0, _rx_din1, _rx_din2, _rx_din3; //TODO: soc_caps.h 1/4
bool _rx_clk_inv;
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
int8_t _tx_clk, _tx_dout0, _tx_dout1;
bool _tx_clk_inv;
#endif
bool allocTranformRX(size_t buf_len);
bool transformRX(i2s_rx_transform_t transform);
static bool i2sDetachBus(void * bus_pointer);
bool initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch);
#if SOC_I2S_SUPPORTS_TDM
bool initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask);
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
bool initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch);
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
bool initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch);
#endif
};
#endif /* SOC_I2S_SUPPORTED */

View File

@ -0,0 +1,91 @@
#pragma once
#include <stdint.h>
/**
* @brief Header structure for WAV file with only one data chunk
*
* @note See this for reference: http://soundfile.sapp.org/doc/WaveFormat/
*
* @note Assignment to variables in this struct directly is only possible for little endian architectures
* (including Xtensa & RISC-V)
*/
typedef struct {
char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */
uint32_t chunk_size; /*!< This is the size of the rest of the chunk following this number */
char chunk_format[4]; /*!< Contains the letters "WAVE" */
} __attribute__((packed)) wav_descriptor_chunk_t; /*!< Canonical WAVE format starts with the RIFF header */
typedef struct {
char subchunk_id[4]; /*!< Contains the letters "fmt " */
uint32_t subchunk_size; /*!< PCM = 16, This is the size of the rest of the Subchunk which follows this number */
uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form of compression */
uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */
uint32_t sample_rate; /*!< 8000, 44100, etc. */
uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */
uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */
uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */
} __attribute__((packed)) pcm_wav_fmt_chunk_t; /*!< The "fmt " subchunk describes the sound data's format */
typedef struct {
char subchunk_id[4]; /*!< Contains the letters "fmt " */
uint32_t subchunk_size; /*!< ALAW/MULAW = 18, This is the size of the rest of the Subchunk which follows this number */
uint16_t audio_format; /*!< ALAW = 6, MULAW = 7, values other than 1 indicate some form of compression */
uint16_t num_of_channels; /*!< ALAW/MULAW = 1, Mono = 1, Stereo = 2, etc. */
uint32_t sample_rate; /*!< ALAW/MULAW = 8000, 8000, 44100, etc. */
uint32_t byte_rate; /*!< ALAW/MULAW = 8000, ==SampleRate * NumChannels * BitsPerSample s/ 8 */
uint16_t block_align; /*!< ALAW/MULAW = 1, ==NumChannels * BitsPerSample / 8 */
uint16_t bits_per_sample; /*!< ALAW/MULAW = 8, 8 bits = 8, 16 bits = 16, etc. */
uint16_t ext_size; /*!< ALAW/MULAW = 0, Size of the extension (0 or 22) */
} __attribute__((packed)) non_pcm_wav_fmt_chunk_t; /*!< The "fmt " subchunk describes the sound data's format */
typedef struct {
char subchunk_id[4]; /*!< Contains the letters "data" */
uint32_t subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */
} __attribute__((packed)) wav_data_chunk_t; /*!< The "data" subchunk contains the size of the data and the actual sound */
typedef struct {
wav_descriptor_chunk_t descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */
pcm_wav_fmt_chunk_t fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */
wav_data_chunk_t data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */
} __attribute__((packed)) pcm_wav_header_t;
typedef struct {
wav_descriptor_chunk_t descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */
non_pcm_wav_fmt_chunk_t fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */
wav_data_chunk_t data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */
} __attribute__((packed)) non_pcm_wav_header_t;
#define WAVE_FORMAT_PCM 1 // PCM
#define WAVE_FORMAT_IEEE_FLOAT 3 // IEEE float
#define WAVE_FORMAT_ALAW 6 // 8-bit ITU-T G.711 A-law
#define WAVE_FORMAT_MULAW 7 // 8-bit ITU-T G.711 µ-law
#define PCM_WAV_HEADER_SIZE 44
#define NON_PCM_WAV_HEADER_SIZE 46
/**
* @brief Default header for PCM format WAV files
*
*/
#define PCM_WAV_HEADER_DEFAULT(wav_sample_size, wav_sample_bits, wav_sample_rate, wav_channel_num) { \
.descriptor_chunk = { \
.chunk_id = {'R', 'I', 'F', 'F'}, \
.chunk_size = (wav_sample_size) + sizeof(pcm_wav_header_t) - 8, \
.chunk_format = {'W', 'A', 'V', 'E'} \
}, \
.fmt_chunk = { \
.subchunk_id = {'f', 'm', 't', ' '}, \
.subchunk_size = 16, /* 16 for PCM */ \
.audio_format = WAVE_FORMAT_PCM, /* 1 for PCM */ \
.num_of_channels = (uint16_t)(wav_channel_num), \
.sample_rate = (wav_sample_rate), \
.byte_rate = (wav_sample_bits) * (wav_sample_rate) * (wav_channel_num) / 8, \
.block_align = (uint16_t)((wav_sample_bits) * (wav_channel_num) / 8), \
.bits_per_sample = (uint16_t)(wav_sample_bits)\
}, \
.data_chunk = { \
.subchunk_id = {'d', 'a', 't', 'a'}, \
.subchunk_size = (wav_sample_size) \
} \
}

View File

@ -0,0 +1,92 @@
#include "ESP_I2S.h"
#include "ESP_SR.h"
#define I2S_PIN_BCK 17
#define I2S_PIN_WS 47
#define I2S_PIN_DIN 16
#define LIGHT_PIN 40
#define FAN_PIN 41
I2SClass i2s;
// Generated using the following command:
// python3 tools/gen_sr_commands.py "Turn on the light,Switch on the light;Turn off the light,Switch off the light,Go dark;Start fan;Stop fan"
enum {
SR_CMD_TURN_ON_THE_LIGHT,
SR_CMD_TURN_OFF_THE_LIGHT,
SR_CMD_START_FAN,
SR_CMD_STOP_FAN,
};
static const sr_cmd_t sr_commands[] = {
{ 0, "Turn on the light", "TkN nN jc LiT"},
{ 0, "Switch on the light", "SWgp nN jc LiT"},
{ 1, "Turn off the light", "TkN eF jc LiT"},
{ 1, "Switch off the light", "SWgp eF jc LiT"},
{ 1, "Go dark", "Gb DnRK"},
{ 2, "Start fan", "STnRT FaN"},
{ 3, "Stop fan", "STnP FaN"},
};
void onSrEvent(sr_event_t event, int command_id, int phrase_id){
switch(event){
case SR_EVENT_WAKEWORD:
Serial.println("WakeWord Detected!");
break;
case SR_EVENT_WAKEWORD_CHANNEL:
Serial.printf("WakeWord Channel %d Verified!\n", command_id);
ESP_SR.setMode(SR_MODE_COMMAND); // Switch to Command detection
break;
case SR_EVENT_TIMEOUT:
Serial.println("Timeout Detected!");
ESP_SR.setMode(SR_MODE_WAKEWORD); // Switch back to WakeWord detection
break;
case SR_EVENT_COMMAND:
Serial.printf("Command %d Detected! %s\n", command_id, sr_commands[phrase_id].str);
switch(command_id){
case SR_CMD_TURN_ON_THE_LIGHT:
digitalWrite(LIGHT_PIN, HIGH);
break;
case SR_CMD_TURN_OFF_THE_LIGHT:
digitalWrite(LIGHT_PIN, LOW);
break;
case SR_CMD_START_FAN:
digitalWrite(FAN_PIN, HIGH);
break;
case SR_CMD_STOP_FAN:
digitalWrite(FAN_PIN, LOW);
break;
default:
Serial.println("Unknown Command!");
break;
}
ESP_SR.setMode(SR_MODE_COMMAND); // Allow for more commands to be given, before timeout
// ESP_SR.setMode(SR_MODE_WAKEWORD); // Switch back to WakeWord detection
break;
default:
Serial.println("Unknown Event!");
break;
}
}
void setup(){
Serial.begin(115200);
pinMode(LIGHT_PIN, OUTPUT);
digitalWrite(LIGHT_PIN, LOW);
pinMode(FAN_PIN, OUTPUT);
digitalWrite(FAN_PIN, LOW);
i2s.setPins(I2S_PIN_BCK, I2S_PIN_WS, -1, I2S_PIN_DIN);
i2s.setTimeout(1000);
i2s.begin(I2S_MODE_STD, 16000, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO);
ESP_SR.onEvent(onSrEvent);
ESP_SR.begin(i2s, sr_commands, sizeof(sr_commands) / sizeof(sr_cmd_t), SR_CHANNELS_STEREO, SR_MODE_WAKEWORD);
}
void loop(){
}

View File

@ -0,0 +1,40 @@
#######################################
# Syntax Coloring Map For ESP_SR
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
ESP_SR KEYWORD1
ESP_SR_Class KEYWORD1
sr_cmd_t KEYWORD1
sr_event_t KEYWORD1
sr_mode_t KEYWORD1
sr_channels_t KEYWORD1
sr_cb KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
onEvent KEYWORD2
setMode KEYWORD2
pause KEYWORD2
resume KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
SR_EVENT_WAKEWORD LITERAL1
SR_EVENT_WAKEWORD_CHANNEL LITERAL1
SR_EVENT_COMMAND LITERAL1
SR_EVENT_TIMEOUT LITERAL1
SR_MODE_OFF LITERAL1
SR_MODE_WAKEWORD LITERAL1
SR_MODE_COMMAND LITERAL1
SR_MODE_MAX LITERAL1
SR_CHANNELS_MONO LITERAL1
SR_CHANNELS_STEREO LITERAL1
SR_CHANNELS_MAX LITERAL1

View File

@ -0,0 +1,9 @@
name=ESP_SR
version=1.0.0
author=me-no-dev
maintainer=me-no-dev
sentence=Library for ESP Sound Recognition
paragraph=Supports ESP32 Arduino platforms.
category=Sound
url=https://github.com/espressif/arduino-esp32/
architectures=esp32

View File

@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET)
#include "ESP_SR.h"
static esp_err_t on_sr_fill(void * arg, void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms){
return ((ESP_SR_Class*)arg)->_fill(out, len, bytes_read, timeout_ms);
}
static void on_sr_event(void * arg, sr_event_t event, int command_id, int phrase_id){
((ESP_SR_Class*)arg)->_sr_event(event, command_id, phrase_id);
}
ESP_SR_Class::ESP_SR_Class()
: cb(NULL)
, i2s(NULL)
{
}
ESP_SR_Class::~ESP_SR_Class(){
end();
}
void ESP_SR_Class::onEvent(sr_cb event_cb){
cb = event_cb;
}
bool ESP_SR_Class::begin(I2SClass & _i2s, const sr_cmd_t * sr_commands, size_t sr_commands_len, sr_channels_t rx_chan, sr_mode_t mode){
i2s = &_i2s;
esp_err_t err = sr_start(on_sr_fill, this, rx_chan, mode, sr_commands, sr_commands_len, on_sr_event, this);
return (err == ESP_OK);
}
bool ESP_SR_Class::end(void){
return sr_stop() == ESP_OK;
}
bool ESP_SR_Class::setMode(sr_mode_t mode){
return sr_set_mode(mode) == ESP_OK;
}
bool ESP_SR_Class::pause(void){
return sr_pause() == ESP_OK;
}
bool ESP_SR_Class::resume(void){
return sr_resume() == ESP_OK;
}
void ESP_SR_Class::_sr_event(sr_event_t event, int command_id, int phrase_id){
if(cb){
cb(event, command_id, phrase_id);
}
}
esp_err_t ESP_SR_Class::_fill(void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms){
if(i2s == NULL){
return ESP_FAIL;
}
i2s->setTimeout(timeout_ms);
*bytes_read = i2s->readBytes((char *)out, len);
return (esp_err_t)i2s->lastError();
}
ESP_SR_Class ESP_SR;
#endif // CONFIG_IDF_TARGET_ESP32S3

View File

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET)
#include "ESP_I2S.h"
#include "esp32-hal-sr.h"
typedef void (*sr_cb)(sr_event_t event, int command_id, int phrase_id);
class ESP_SR_Class {
private:
sr_cb cb;
I2SClass * i2s;
public:
ESP_SR_Class();
~ESP_SR_Class();
void onEvent(sr_cb cb);
bool begin(I2SClass & i2s, const sr_cmd_t * sr_commands, size_t sr_commands_len, sr_channels_t rx_chan=SR_CHANNELS_STEREO, sr_mode_t mode=SR_MODE_WAKEWORD);
bool end(void);
bool setMode(sr_mode_t mode);
bool pause(void);
bool resume(void);
void _sr_event(sr_event_t event, int command_id, int phrase_id);
esp_err_t _fill(void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms);
};
extern ESP_SR_Class ESP_SR;
#endif // CONFIG_IDF_TARGET_ESP32S3

View File

@ -0,0 +1,445 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET)
#if !defined(ARDUINO_PARTITION_esp_sr_32) && !defined(ARDUINO_PARTITION_esp_sr_16) && !defined(ARDUINO_PARTITION_esp_sr_8)
#warning Compatible partition must be selected for ESP_SR to work
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/queue.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_mn_speech_commands.h"
#include "esp_process_sdkconfig.h"
#include "esp_afe_sr_models.h"
#include "esp_mn_models.h"
#include "esp_wn_iface.h"
#include "esp_wn_models.h"
#include "esp_afe_sr_iface.h"
#include "esp_mn_iface.h"
#include "model_path.h"
#include "driver/i2s_common.h"
#include "esp32-hal-sr.h"
#include "esp32-hal-log.h"
#undef ESP_GOTO_ON_FALSE
#define ESP_GOTO_ON_FALSE(a, err_code, goto_tag, format, ...) do { \
if (unlikely(!(a))) { \
log_e(format, ##__VA_ARGS__); \
ret = err_code; \
goto goto_tag; \
} \
} while (0)
#undef ESP_RETURN_ON_FALSE
#define ESP_RETURN_ON_FALSE(a, err_code, format, ...) do { \
if (unlikely(!(a))) { \
log_e(format, ##__VA_ARGS__); \
return err_code; \
} \
} while(0)
#define NEED_DELETE BIT0
#define FEED_DELETED BIT1
#define DETECT_DELETED BIT2
#define PAUSE_FEED BIT3
#define PAUSE_DETECT BIT4
#define RESUME_FEED BIT5
#define RESUME_DETECT BIT6
typedef struct {
wakenet_state_t wakenet_mode;
esp_mn_state_t state;
int command_id;
int phrase_id;
} sr_result_t;
typedef struct {
model_iface_data_t *model_data;
const esp_mn_iface_t *multinet;
const esp_afe_sr_iface_t *afe_handle;
esp_afe_sr_data_t *afe_data;
int16_t *afe_in_buffer;
sr_mode_t mode;
uint8_t i2s_rx_chan_num;
sr_event_cb user_cb;
void * user_cb_arg;
sr_fill_cb fill_cb;
void * fill_cb_arg;
TaskHandle_t feed_task;
TaskHandle_t detect_task;
TaskHandle_t handle_task;
QueueHandle_t result_que;
EventGroupHandle_t event_group;
} sr_data_t;
static int SR_CHANNEL_NUM = 3;
static srmodel_list_t *models = NULL;
static sr_data_t *g_sr_data = NULL;
esp_err_t sr_set_mode(sr_mode_t mode);
void sr_handler_task(void *pvParam)
{
while (true) {
sr_result_t result;
if(xQueueReceive(g_sr_data->result_que, &result, portMAX_DELAY) != pdTRUE){
continue;
}
if (WAKENET_DETECTED == result.wakenet_mode) {
if(g_sr_data->user_cb){
g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_WAKEWORD, -1, -1);
}
continue;
}
if (WAKENET_CHANNEL_VERIFIED == result.wakenet_mode) {
if(g_sr_data->user_cb){
g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_WAKEWORD_CHANNEL, result.command_id, -1);
}
continue;
}
if (ESP_MN_STATE_DETECTED == result.state) {
if(g_sr_data->user_cb){
g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_COMMAND, result.command_id, result.phrase_id);
}
continue;
}
if (ESP_MN_STATE_TIMEOUT == result.state) {
if(g_sr_data->user_cb){
g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_TIMEOUT, -1, -1);
}
continue;
}
}
vTaskDelete(NULL);
}
static void audio_feed_task(void *arg)
{
size_t bytes_read = 0;
int audio_chunksize = g_sr_data->afe_handle->get_feed_chunksize(g_sr_data->afe_data);
log_i("audio_chunksize=%d, feed_channel=%d", audio_chunksize, SR_CHANNEL_NUM);
/* Allocate audio buffer and check for result */
int16_t *audio_buffer = heap_caps_malloc(audio_chunksize * sizeof(int16_t) * SR_CHANNEL_NUM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (NULL == audio_buffer) {
esp_system_abort("No mem for audio buffer");
}
g_sr_data->afe_in_buffer = audio_buffer;
while (true) {
EventBits_t bits = xEventGroupGetBits(g_sr_data->event_group);
if (NEED_DELETE & bits) {
xEventGroupSetBits(g_sr_data->event_group, FEED_DELETED);
break;
}
if (PAUSE_FEED & bits) {
xEventGroupWaitBits(g_sr_data->event_group, PAUSE_FEED | RESUME_FEED, 1, 1, portMAX_DELAY);
}
/* Read audio data from I2S bus */
//ToDo: handle error
if(g_sr_data->fill_cb == NULL){
vTaskDelay(100);
continue;
}
esp_err_t err = g_sr_data->fill_cb(g_sr_data->fill_cb_arg, (char *)audio_buffer, audio_chunksize * g_sr_data->i2s_rx_chan_num * sizeof(int16_t), &bytes_read, portMAX_DELAY);
if(err != ESP_OK){
vTaskDelay(100);
continue;
}
/* Channel Adjust */
if(g_sr_data->i2s_rx_chan_num == 1){
for (int i = audio_chunksize - 1; i >= 0; i--) {
audio_buffer[i * SR_CHANNEL_NUM + 2] = 0;
audio_buffer[i * SR_CHANNEL_NUM + 1] = 0;
audio_buffer[i * SR_CHANNEL_NUM + 0] = audio_buffer[i];
}
} else if(g_sr_data->i2s_rx_chan_num == 2){
for (int i = audio_chunksize - 1; i >= 0; i--) {
audio_buffer[i * SR_CHANNEL_NUM + 2] = 0;
audio_buffer[i * SR_CHANNEL_NUM + 1] = audio_buffer[i * 2 + 1];
audio_buffer[i * SR_CHANNEL_NUM + 0] = audio_buffer[i * 2 + 0];
}
} else {
vTaskDelay(100);
continue;
}
/* Feed samples of an audio stream to the AFE_SR */
g_sr_data->afe_handle->feed(g_sr_data->afe_data, audio_buffer);
}
vTaskDelete(NULL);
}
static void audio_detect_task(void *arg)
{
int afe_chunksize = g_sr_data->afe_handle->get_fetch_chunksize(g_sr_data->afe_data);
int mu_chunksize = g_sr_data->multinet->get_samp_chunksize(g_sr_data->model_data);
assert(mu_chunksize == afe_chunksize);
log_i("------------detect start------------");
while (true) {
EventBits_t bits = xEventGroupGetBits(g_sr_data->event_group);
if (NEED_DELETE & bits) {
xEventGroupSetBits(g_sr_data->event_group, DETECT_DELETED);
break;
}
if (PAUSE_DETECT & bits) {
xEventGroupWaitBits(g_sr_data->event_group, PAUSE_DETECT | RESUME_DETECT, 1, 1, portMAX_DELAY);
}
afe_fetch_result_t* res = g_sr_data->afe_handle->fetch(g_sr_data->afe_data);
if (!res || res->ret_value == ESP_FAIL) {
continue;
}
if(g_sr_data->mode == SR_MODE_WAKEWORD){
if (res->wakeup_state == WAKENET_DETECTED) {
log_d("wakeword detected");
sr_result_t result = {
.wakenet_mode = WAKENET_DETECTED,
.state = ESP_MN_STATE_DETECTING,
.command_id = 0,
.phrase_id = 0,
};
xQueueSend(g_sr_data->result_que, &result, 0);
}
else if (res->wakeup_state == WAKENET_CHANNEL_VERIFIED) {
sr_set_mode(SR_MODE_OFF);
log_d("AFE_FETCH_CHANNEL_VERIFIED, channel index: %d", res->trigger_channel_id);
sr_result_t result = {
.wakenet_mode = WAKENET_CHANNEL_VERIFIED,
.state = ESP_MN_STATE_DETECTING,
.command_id = res->trigger_channel_id,
.phrase_id = 0,
};
xQueueSend(g_sr_data->result_que, &result, 0);
}
}
if (g_sr_data->mode == SR_MODE_COMMAND) {
esp_mn_state_t mn_state = ESP_MN_STATE_DETECTING;
mn_state = g_sr_data->multinet->detect(g_sr_data->model_data, res->data);
if (ESP_MN_STATE_DETECTING == mn_state) {
continue;
}
if (ESP_MN_STATE_TIMEOUT == mn_state) {
sr_set_mode(SR_MODE_OFF);
log_d("Time out");
sr_result_t result = {
.wakenet_mode = WAKENET_NO_DETECT,
.state = mn_state,
.command_id = 0,
.phrase_id = 0,
};
xQueueSend(g_sr_data->result_que, &result, 0);
continue;
}
if (ESP_MN_STATE_DETECTED == mn_state) {
sr_set_mode(SR_MODE_OFF);
esp_mn_results_t *mn_result = g_sr_data->multinet->get_results(g_sr_data->model_data);
for (int i = 0; i < mn_result->num; i++) {
log_d("TOP %d, command_id: %d, phrase_id: %d, prob: %f",
i + 1, mn_result->command_id[i], mn_result->phrase_id[i], mn_result->prob[i]);
}
int sr_command_id = mn_result->command_id[0];
int sr_phrase_id = mn_result->phrase_id[0];
log_d("Deteted command : %d, phrase: %d", sr_command_id, sr_phrase_id);
sr_result_t result = {
.wakenet_mode = WAKENET_NO_DETECT,
.state = mn_state,
.command_id = sr_command_id,
.phrase_id = sr_phrase_id,
};
xQueueSend(g_sr_data->result_que, &result, 0);
continue;
}
log_e("Exception unhandled");
}
}
vTaskDelete(NULL);
}
esp_err_t sr_set_mode(sr_mode_t mode)
{
ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running");
switch(mode){
case SR_MODE_OFF:
if(g_sr_data->mode == SR_MODE_WAKEWORD){
g_sr_data->afe_handle->disable_wakenet(g_sr_data->afe_data);
}
break;
case SR_MODE_WAKEWORD:
if(g_sr_data->mode != SR_MODE_WAKEWORD){
g_sr_data->afe_handle->enable_wakenet(g_sr_data->afe_data);
}
break;
case SR_MODE_COMMAND:
if(g_sr_data->mode == SR_MODE_WAKEWORD){
g_sr_data->afe_handle->disable_wakenet(g_sr_data->afe_data);
}
break;
default:
return ESP_FAIL;
}
g_sr_data->mode = mode;
return ESP_OK;
}
esp_err_t sr_start(sr_fill_cb fill_cb, void * fill_cb_arg, sr_channels_t rx_chan, sr_mode_t mode, const sr_cmd_t sr_commands[], size_t cmd_number, sr_event_cb cb, void * cb_arg)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(NULL == g_sr_data, ESP_ERR_INVALID_STATE, "SR already running");
g_sr_data = heap_caps_calloc(1, sizeof(sr_data_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_NO_MEM, "Failed create sr data");
g_sr_data->result_que = xQueueCreate(3, sizeof(sr_result_t));
ESP_GOTO_ON_FALSE(NULL != g_sr_data->result_que, ESP_ERR_NO_MEM, err, "Failed create result queue");
g_sr_data->event_group = xEventGroupCreate();
ESP_GOTO_ON_FALSE(NULL != g_sr_data->event_group, ESP_ERR_NO_MEM, err, "Failed create event_group");
BaseType_t ret_val;
g_sr_data->user_cb = cb;
g_sr_data->user_cb_arg = cb_arg;
g_sr_data->fill_cb = fill_cb;
g_sr_data->fill_cb_arg = fill_cb_arg;
g_sr_data->i2s_rx_chan_num = rx_chan + 1;
g_sr_data->mode = mode;
// Init Model
log_d("init model");
models = esp_srmodel_init("model");
// Load WakeWord Detection
g_sr_data->afe_handle = (esp_afe_sr_iface_t *)&ESP_AFE_SR_HANDLE;
afe_config_t afe_config = AFE_CONFIG_DEFAULT();
afe_config.wakenet_model_name = esp_srmodel_filter(models, ESP_WN_PREFIX, "hiesp");
afe_config.aec_init = false;
log_d("load wakenet '%s'", afe_config.wakenet_model_name);
g_sr_data->afe_data = g_sr_data->afe_handle->create_from_config(&afe_config);
// Load Custom Command Detection
char *mn_name = esp_srmodel_filter(models, ESP_MN_PREFIX, ESP_MN_ENGLISH);
log_d("load multinet '%s'", mn_name);
g_sr_data->multinet = esp_mn_handle_from_name(mn_name);
log_d("load model_data '%s'", mn_name);
g_sr_data->model_data = g_sr_data->multinet->create(mn_name, 5760);
// Add commands
esp_mn_commands_alloc((esp_mn_iface_t *)g_sr_data->multinet, (model_iface_data_t *)g_sr_data->model_data);
log_i("add %d commands", cmd_number);
for (size_t i = 0; i < cmd_number; i++) {
esp_mn_commands_add(sr_commands[i].command_id, (char *)(sr_commands[i].phoneme));
log_i(" cmd[%d] phrase[%d]:'%s'", sr_commands[i].command_id, i, sr_commands[i].str);
}
// Load commands
esp_mn_error_t *err_id = esp_mn_commands_update();
if(err_id){
for (int i = 0; i < err_id->num; i++) {
log_e("err cmd id:%d", err_id->phrases[i]->command_id);
}
}
//Start tasks
log_d("start tasks");
ret_val = xTaskCreatePinnedToCore(&audio_feed_task, "SR Feed Task", 4 * 1024, NULL, 5, &g_sr_data->feed_task, 0);
ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio feed task");
vTaskDelay(10);
ret_val = xTaskCreatePinnedToCore(&audio_detect_task, "SR Detect Task", 8 * 1024, NULL, 5, &g_sr_data->detect_task, 1);
ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio detect task");
ret_val = xTaskCreatePinnedToCore(&sr_handler_task, "SR Handler Task", 6 * 1024, NULL, configMAX_PRIORITIES - 1, &g_sr_data->handle_task, 1);
//ret_val = xTaskCreatePinnedToCore(&sr_handler_task, "SR Handler Task", 6 * 1024, NULL, configMAX_PRIORITIES - 1, &g_sr_data->handle_task, 0);
ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio handler task");
return ESP_OK;
err:
sr_stop();
return ret;
}
esp_err_t sr_stop(void)
{
ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running");
/**
* Waiting for all task stoped
* TODO: A task creation failure cannot be handled correctly now
* */
vTaskDelete(g_sr_data->handle_task);
xEventGroupSetBits(g_sr_data->event_group, NEED_DELETE);
xEventGroupWaitBits(g_sr_data->event_group, NEED_DELETE | FEED_DELETED | DETECT_DELETED, 1, 1, portMAX_DELAY);
if (g_sr_data->result_que) {
vQueueDelete(g_sr_data->result_que);
g_sr_data->result_que = NULL;
}
if (g_sr_data->event_group) {
vEventGroupDelete(g_sr_data->event_group);
g_sr_data->event_group = NULL;
}
if (g_sr_data->model_data) {
g_sr_data->multinet->destroy(g_sr_data->model_data);
}
if (g_sr_data->afe_data) {
g_sr_data->afe_handle->destroy(g_sr_data->afe_data);
}
if (g_sr_data->afe_in_buffer) {
heap_caps_free(g_sr_data->afe_in_buffer);
}
heap_caps_free(g_sr_data);
g_sr_data = NULL;
return ESP_OK;
}
esp_err_t sr_pause(void)
{
ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running");
xEventGroupSetBits(g_sr_data->event_group, PAUSE_FEED | PAUSE_DETECT);
return ESP_OK;
}
esp_err_t sr_resume(void)
{
ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running");
xEventGroupSetBits(g_sr_data->event_group, RESUME_FEED | RESUME_DETECT);
return ESP_OK;
}
#endif // CONFIG_IDF_TARGET_ESP32S3

View File

@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET)
#include "driver/i2s_types.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SR_CMD_STR_LEN_MAX 64
#define SR_CMD_PHONEME_LEN_MAX 64
typedef struct sr_cmd_t {
int command_id;
char str[SR_CMD_STR_LEN_MAX];
char phoneme[SR_CMD_PHONEME_LEN_MAX];
} sr_cmd_t;
typedef enum {
SR_EVENT_WAKEWORD,//WakeWord Detected
SR_EVENT_WAKEWORD_CHANNEL,//WakeWord Channel Verified
SR_EVENT_COMMAND,//Command Detected
SR_EVENT_TIMEOUT,//Command Timeout
SR_EVENT_MAX
} sr_event_t;
typedef enum {
SR_MODE_OFF,//Detection Off
SR_MODE_WAKEWORD,//WakeWord Detection
SR_MODE_COMMAND,//Command Detection
SR_MODE_MAX
} sr_mode_t;
typedef enum {
SR_CHANNELS_MONO,
SR_CHANNELS_STEREO,
SR_CHANNELS_MAX
} sr_channels_t;
typedef void (*sr_event_cb)(void * arg, sr_event_t event, int command_id, int phrase_id);
typedef esp_err_t (*sr_fill_cb)(void * arg, void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms);
esp_err_t sr_start(sr_fill_cb fill_cb, void * fill_cb_arg, sr_channels_t rx_chan, sr_mode_t mode, const sr_cmd_t * sr_commands, size_t cmd_number, sr_event_cb cb, void * cb_arg);
esp_err_t sr_stop(void);
esp_err_t sr_pause(void);
esp_err_t sr_resume(void);
esp_err_t sr_set_mode(sr_mode_t mode);
// static const sr_cmd_t sr_commands[] = {
// {0, "Turn On the Light", "TkN nN jc LiT"},
// {0, "Switch On the Light", "SWgp nN jc LiT"},
// {1, "Switch Off the Light", "SWgp eF jc LiT"},
// {1, "Turn Off the Light", "TkN eF jc LiT"},
// {2, "Turn Red", "TkN RfD"},
// {3, "Turn Green", "TkN GRmN"},
// {4, "Turn Blue", "TkN BLo"},
// {5, "Customize Color", "KcSTcMiZ KcLk"},
// {6, "Sing a song", "Sgl c Sel"},
// {7, "Play Music", "PLd MYoZgK"},
// {8, "Next Song", "NfKST Sel"},
// {9, "Pause Playing", "PeZ PLdgl"},
// };
#ifdef __cplusplus
}
#endif
#endif // CONFIG_IDF_TARGET_ESP32S3

View File

@ -0,0 +1,73 @@
# pip3 install g2p_en
from g2p_en import G2p
import argparse
# python3 gen_sr_commands.py "Turn on the light,Switch on the light;Turn off the light,Switch off the light,Go dark;Start fan;Stop fan;Volume down,Turn down;Mute sound;Next song;Pause playback"
# enum {
# SR_CMD_TURN_ON_THE_LIGHT,
# SR_CMD_TURN_OFF_THE_LIGHT,
# SR_CMD_START_FAN,
# SR_CMD_STOP_FAN,
# SR_CMD_VOLUME_DOWN,
# SR_CMD_MUTE_SOUND,
# SR_CMD_NEXT_SONG,
# SR_CMD_PAUSE_PLAYBACK,
# };
# static const sr_cmd_t sr_commands[] = {
# { 0, "Turn on the light", "TkN nN jc LiT"},
# { 0, "Switch on the light", "SWgp nN jc LiT"},
# { 1, "Turn off the light", "TkN eF jc LiT"},
# { 1, "Switch off the light", "SWgp eF jc LiT"},
# { 1, "Go dark", "Gb DnRK"},
# { 2, "Start fan", "STnRT FaN"},
# { 3, "Stop fan", "STnP FaN"},
# { 4, "Volume down", "VnLYoM DtN"},
# { 4, "Turn down", "TkN DtN"},
# { 5, "Mute sound", "MYoT StND"},
# { 6, "Next song", "NfKST Sel"},
# { 7, "Pause playback", "PeZ PLdBaK"},
# };
def english_g2p(text):
g2p = G2p()
out = "static const sr_cmd_t sr_commands[] = {\n"
enum = "enum {\n"
alphabet={"AE1": "a", "N": "N", " ": " ", "OW1": "b", "V": "V", "AH0": "c", "L": "L", "F": "F", "EY1": "d", "S": "S", "B": "B", "R": "R", "AO1": "e", "D": "D", "AH1": "c", "EH1": "f", "OW0": "b", "IH0": "g", "G": "G", "HH": "h", "K": "K", "IH1": "g", "W": "W", "AY1": "i", "T": "T", "M": "M", "Z": "Z", "DH": "j", "ER0": "k", "P": "P", "NG": "l", "IY1": "m", "AA1": "n", "Y": "Y", "UW1": "o", "IY0": "m", "EH2": "f", "CH": "p", "AE0": "a", "JH": "q", "ZH": "r", "AA2": "n", "SH": "s", "AW1": "t", "OY1": "u", "AW2": "t", "IH2": "g", "AE2": "a", "EY2": "d", "ER1": "k", "TH": "v", "UH1": "w", "UW2": "o", "OW2": "b", "AY2": "i", "UW0": "o", "AH2": "c", "EH0": "f", "AW0": "t", "AO2": "e", "AO0": "e", "UH0": "w", "UH2": "w", "AA0": "n", "AY0": "i", "IY2": "m", "EY0": "d", "ER2": "k", "OY2": "u", "OY0": "u"}
cmd_id = 0
phrase_id = 0
text_list = text.split(";")
for item in text_list:
item = item.split(",")
phrase_id = 0
for phrase in item:
labels = g2p(phrase)
phoneme = ""
for char in labels:
if char not in alphabet:
print("skip %s, not found in alphabet")
continue
else:
phoneme += alphabet[char]
out += " { "+str(cmd_id)+", \""+phrase+"\", \""+phoneme+"\"},\n"
if phrase_id == 0:
enum += " SR_CMD_"+phrase.upper().replace(" ", "_")+",\n"
phrase_id += 1
cmd_id += 1
out += "};"
enum += "};"
# print(text)
print(enum)
print(out)
return out
if __name__ == "__main__":
parser = argparse.ArgumentParser(prog="English Speech Commands G2P")
parser.add_argument("text", type=str, default=None, help="input text")
args = parser.parse_args()
if args.text is not None:
english_g2p(args.text)

View File

@ -1,86 +0,0 @@
/*
This example is only for ESP devices.
This example demonstrates usage of integrated Digital to Analog Converter (DAC)
You can display sound wave from audio device, or just measure voltage.
To display audio prepare circuit found in following link or drafted as ASCII art
https://forum.arduino.cc/index.php?topic=567581.0
(!) Note that unlike in the link we are connecting the supply line to 3.3V (not 5V)
because ADC can measure only up to around 3V. Anything above 3V will be very inaccurate.
^ +3.3V
|
_
| |10k
|_|
| | |10uF
GPIO34-------------*------------| |----------- line in
(Default ADC pin) | +| |
|
_
| |10k
|_|
|
|
V GND
Connect hot wire of your audio to Line in and GNd wire of audio cable to common ground (GND)
Second option to measure voltage on trimmer / potentiometer has following connection
^ +3.3V
|
_
| |
GPIO34----------->| |
(Default ADC pin) |_|
|
|
_
| | optional resistor
|_|
|
|
V GND
Optional resistor will decrease read value.
Steps to run:
1. Select target board:
Tools -> Board -> ESP32 Arduino -> your board
2. Upload sketch
Press upload button (arrow in top left corner)
When you see in console line like this: "Connecting........_____.....__"
On your board press and hold Boot button and press EN button shortly. Now you can release both buttons.
You should see lines like this: "Writing at 0x00010000... (12 %)" with rising percentage on each line.
If this fails, try the board buttons right after pressing upload button, or reconnect the USB cable.
3. Open plotter
Tools -> Serial Plotter
Enjoy
Created by Tomas Pilny
on 17th June 2021
*/
#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 32-bits per sample
if (!I2S.begin(ADC_DAC_MODE, 8000, 16)) {
Serial.println("Failed to initialize I2S!");
while (1); // do nothing
}
}
void loop() {
// read a sample
int sample = I2S.read();
Serial.println(sample);
}

View File

@ -1,59 +0,0 @@
/*
This example is only for ESP
This example demonstrates simultaneous usage of microphone and speaker using single I2S module.
The application transfers data from input to output
Circuit:
* ESP32
* GND connected GND
* VIN connected 5V
* SCK 5
* FS 25
* DIN 35
* DOUT 26
* I2S microphone
* I2S decoder + headphones / speaker
created 8 October 2021
by Tomas Pilny
*/
#include <I2S.h>
const long sampleRate = 16000;
const int bitsPerSample = 32;
uint8_t *buffer;
void setup() {
Serial.begin(115200);
//I2S.setAllPins(5, 25, 35, 26); // you can change default pins; order of pins = (CLK, WS, IN, OUT)
if(!I2S.setDuplex()){
Serial.println("ERROR - could not set duplex");
while(true){
vTaskDelay(10); // Cannot continue
}
}
if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, bitsPerSample)) {
Serial.println("Failed to initialize I2S!");
while(true){
vTaskDelay(10); // Cannot continue
}
}
buffer = (uint8_t*) malloc(I2S.getBufferSize() * (bitsPerSample / 8));
if(buffer == NULL){
Serial.println("Failed to allocate buffer!");
while(true){
vTaskDelay(10); // Cannot continue
}
}
Serial.println("Setup done");
}
void loop() {
//I2S.write(I2S.read()); // primitive implementation sample-by-sample
// Buffer based implementation
I2S.read(buffer, I2S.getBufferSize() * (bitsPerSample / 8));
I2S.write(buffer, I2S.getBufferSize() * (bitsPerSample / 8));
//optimistic_yield(1000); // yield if last yield occurred before <parameter> CPU clock cycles ago
}

View File

@ -1,44 +0,0 @@
/*
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)
Circuit:
* Arduino/Genuino Zero, MKR family and Nano 33 IoT
* ICS43432:
* GND connected GND
* 3.3V connected to 3.3V (Zero, Nano, ESP32), VCC (MKR)
* WS connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32)
* CLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32)
* SD connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32)
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 32-bits per sample
if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) {
Serial.println("Failed to initialize I2S!");
while (1); // do nothing
}
}
void loop() {
// read a sample
int sample = I2S.read();
if (sample && sample != -1 && sample != 1) {
Serial.println(sample);
}
}

View File

@ -1,79 +0,0 @@
/*
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.
I2S Circuit:
* Arduino/Genuino Zero, MKR family and Nano 33 IoT
* MAX08357:
* GND connected GND
* VIN connected 5V
* LRC connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32)
* BCLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32)
* DIN connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32)
DAC Circuit:
* ESP32 or ESP32-S2
* Audio amplifier
- Note:
- ESP32 has DAC on GPIO pins 25 and 26.
- ESP32-S2 has DAC on GPIO pins 17 and 18.
- Connect speaker(s) or headphones.
created 17 November 2016
by Sandeep Mistry
For ESP extended
Tomas Pilny
2nd September 2021
*/
#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 bps = 16;
const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave
int32_t sample = amplitude; // current sample value
int count = 0;
i2s_mode_t mode = I2S_PHILIPS_MODE; // I2S decoder is needed
// i2s_mode_t mode = ADC_DAC_MODE; // Audio amplifier is needed
// Mono channel input
// This is ESP specific implementation -
// samples will be automatically copied to both channels inside I2S driver
// If you want to have true mono output use I2S_PHILIPS_MODE and interlay
// second channel with 0-value samples.
// The order of channels is RIGH followed by LEFT
//i2s_mode_t mode = I2S_RIGHT_JUSTIFIED_MODE; // I2S decoder is needed
void setup() {
Serial.begin(115200);
Serial.println("I2S simple tone");
// start I2S at the sample rate with 16-bits per sample
if (!I2S.begin(mode, sampleRate, bps)) {
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;
}
if(mode == I2S_PHILIPS_MODE || mode == ADC_DAC_MODE){ // write the same sample twice, once for Right and once for Left channel
I2S.write(sample); // Right channel
I2S.write(sample); // Left channel
}else if(mode == I2S_RIGHT_JUSTIFIED_MODE || mode == I2S_LEFT_JUSTIFIED_MODE){
// write the same only once - it will be automatically copied to the other channel
I2S.write(sample);
}
// increment the counter for the next sample
count++;
}

View File

@ -1,61 +0,0 @@
#######################################
# Syntax Coloring Map I2S
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
I2S KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
I2SClass KEYWORD2
begin KEYWORD2
end KEYWORD2
onReceive KEYWORD2
onTransmit KEYWORD2
setSckPin KEYWORD2
setFsPin KEYWORD2
setDataInPin KEYWORD2
setDataOutPin KEYWORD2
setAllPins KEYWORD2
getSckPin KEYWORD2
getFsPin KEYWORD2
getDataPin KEYWORD2
getDataInPin KEYWORD2
getDataOutPin KEYWORD2
setDuplex KEYWORD2
setSimplex KEYWORD2
isDuplex KEYWORD2
setBufferSize KEYWORD2
getBufferSize KEYWORD2
write KEYWORD2
availableForWrite KEYWORD2
read KEYWORD2
available KEYWORD2
gpioToAdcUnit KEYWORD2
gpioToAdcChannel KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
I2S_PHILIPS_MODE LITERAL1
I2S_RIGHT_JUSTIFIED_MODE LITERAL1
I2S_LEFT_JUSTIFIED_MODE LITERAL1
I2S_ADC_DAC LITERAL1
I2S_PDM LITERAL1
PIN_I2S_SCK LITERAL1
PIN_I2S_FS LITERAL1
PIN_I2S_SD LITERAL1
PIN_I2S_SD_OUT LITERAL1

View File

@ -1,9 +0,0 @@
name=I2S
version=1.0
author=Tomas Pilny
maintainer=Tomas Pilny <tomas.pilny@espressif.com>
sentence=Enables the communication with devices that use the Inter-IC Sound (I2S) Bus. Specific implementation for ESP.
paragraph=
category=Communication
url=http://www.arduino.cc/en/Reference/I2S
architectures=esp32

File diff suppressed because it is too large Load Diff

View File

@ -1,195 +0,0 @@
/*
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 "freertos/ringbuf.h"
namespace esp_i2s {
#include "driver/i2s.h" // ESP specific i2s driver
}
// Default pins
#ifndef PIN_I2S_SCK
#define PIN_I2S_SCK 14
#endif
#ifndef PIN_I2S_FS
#if CONFIG_IDF_TARGET_ESP32S2
#define PIN_I2S_FS 27
#else
#define PIN_I2S_FS 25
#endif
#endif
#ifndef PIN_I2S_SD
#define PIN_I2S_SD 26
#endif
#ifndef PIN_I2S_SD_OUT
#define PIN_I2S_SD_OUT 26
#endif
#ifndef PIN_I2S_SD_IN
#define PIN_I2S_SD_IN 35 // Pin 35 is only input!
#endif
typedef enum {
I2S_PHILIPS_MODE,
I2S_RIGHT_JUSTIFIED_MODE,
I2S_LEFT_JUSTIFIED_MODE,
ADC_DAC_MODE,
PDM_STEREO_MODE,
PDM_MONO_MODE
} i2s_mode_t;
class I2SClass : public Stream
{
public:
// The device index and pins must map to the "COM" pads in Table 6-1 of the datasheet
I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin);
// Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate
int begin(int mode, int sampleRate, int bitsPerSample);
// Init in SLAVE mode: the SCK and FS pins are inputs, other side controls sample rate
int begin(int mode, int bitsPerSample);
// change pin setup and mode (default is Half Duplex)
// Can be called only on initialized object (after begin)
int setSckPin(int sckPin);
int setFsPin(int fsPin);
int setDataPin(int sdPin); // shared data pin for simplex
int setDataOutPin(int outSdPin);
int setDataInPin(int inSdPin);
int setAllPins();
int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin);
int getSckPin();
int getFsPin();
int getDataPin();
int getDataOutPin();
int getDataInPin();
int setDuplex();
int setSimplex();
int isDuplex();
void end();
// from Stream
virtual int available();
virtual int read();
virtual int peek();
virtual void flush();
// from Print
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buffer, size_t size);
virtual int availableForWrite();
int read(void* buffer, size_t size);
//size_t write(int);
size_t write(int32_t);
size_t write(const void *buffer, size_t size);
size_t write_blocking(const void *buffer, size_t size);
size_t write_nonblocking(const void *buffer, size_t size);
void onTransmit(void(*)(void));
void onReceive(void(*)(void));
int setBufferSize(int bufferSize);
int getBufferSize();
private:
#if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC)
int _gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit);
int _gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc_channel);
#endif
int begin(int mode, int sampleRate, int bitsPerSample, bool driveClock);
int _enableTransmitter();
int _enableReceiver();
void _onTransferComplete();
int _createCallbackTask();
static void onDmaTransferComplete(void*);
int _installDriver();
void _uninstallDriver();
void _setSckPin(int sckPin);
void _setFsPin(int fsPin);
void _setDataPin(int sdPin);
void _setDataOutPin(int outSdPin);
void _setDataInPin(int inSdPin);
int _applyPinSetting();
private:
typedef enum {
I2S_STATE_IDLE,
I2S_STATE_TRANSMITTER,
I2S_STATE_RECEIVER,
I2S_STATE_DUPLEX
} i2s_state_t;
int _deviceIndex;
int _sdPin;
int _inSdPin;
int _outSdPin;
int _sckPin;
int _fsPin;
i2s_state_t _state;
int _bitsPerSample;
uint32_t _sampleRate;
int _mode;
uint16_t _buffer_byte_size;
bool _driverInstalled; // Is IDF I2S driver installed?
bool _initialized; // Is everything initialized (callback task, I2S driver, ring buffers)?
TaskHandle_t _callbackTaskHandle;
QueueHandle_t _i2sEventQueue;
SemaphoreHandle_t _i2s_general_mutex;
RingbufHandle_t _input_ring_buffer;
RingbufHandle_t _output_ring_buffer;
int _i2s_dma_buffer_size;
bool _driveClock;
uint32_t _peek_buff;
bool _peek_buff_valid;
void _tx_done_routine(uint8_t* prev_item);
void _rx_done_routine();
uint16_t _nesting_counter;
void _take_if_not_holding();
void _give_if_top_call();
void _post_read_data_fix(void *input, size_t *size);
void _fix_and_write(void *output, size_t size, size_t *bytes_written = NULL, size_t *actual_bytes_written = NULL);
void (*_onTransmit)(void);
void (*_onReceive)(void);
};
extern I2SClass I2S;
#endif