mirror of
https://github.com/espressif/arduino-esp32
synced 2024-09-21 02:18:29 +00:00
[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:
parent
4114c663b5
commit
601efed98f
1
.github/workflows/push.yml
vendored
1
.github/workflows/push.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
21
libraries/ESP_I2S/keywords.txt
Normal file
21
libraries/ESP_I2S/keywords.txt
Normal 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
|
9
libraries/ESP_I2S/library.properties
Executable file
9
libraries/ESP_I2S/library.properties
Executable 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
|
807
libraries/ESP_I2S/src/ESP_I2S.cpp
Normal file
807
libraries/ESP_I2S/src/ESP_I2S.cpp
Normal 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 */
|
145
libraries/ESP_I2S/src/ESP_I2S.h
Normal file
145
libraries/ESP_I2S/src/ESP_I2S.h
Normal 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 */
|
91
libraries/ESP_I2S/src/wav_header.h
Normal file
91
libraries/ESP_I2S/src/wav_header.h
Normal 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) \
|
||||
} \
|
||||
}
|
92
libraries/ESP_SR/examples/Basic/Basic.ino
Normal file
92
libraries/ESP_SR/examples/Basic/Basic.ino
Normal 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(){
|
||||
|
||||
}
|
40
libraries/ESP_SR/keywords.txt
Normal file
40
libraries/ESP_SR/keywords.txt
Normal 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
|
9
libraries/ESP_SR/library.properties
Executable file
9
libraries/ESP_SR/library.properties
Executable 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
|
72
libraries/ESP_SR/src/ESP_SR.cpp
Normal file
72
libraries/ESP_SR/src/ESP_SR.cpp
Normal 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
|
38
libraries/ESP_SR/src/ESP_SR.h
Normal file
38
libraries/ESP_SR/src/ESP_SR.h
Normal 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
|
445
libraries/ESP_SR/src/esp32-hal-sr.c
Normal file
445
libraries/ESP_SR/src/esp32-hal-sr.c
Normal 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
|
76
libraries/ESP_SR/src/esp32-hal-sr.h
Normal file
76
libraries/ESP_SR/src/esp32-hal-sr.h
Normal 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
|
73
libraries/ESP_SR/tools/gen_sr_commands.py
Normal file
73
libraries/ESP_SR/tools/gen_sr_commands.py
Normal 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)
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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++;
|
||||
}
|
@ -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
|
@ -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
@ -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
|
Loading…
Reference in New Issue
Block a user