ATOM-ECHO
ATOM-ECHO copied to clipboard
Is full duplex with the mic and speaker possible?
Hi,
I have several Echos and the examples, especially the record/play, work fine. Now I would like to build intercoms like Echo1 <-> serial or wifi <-> Echo2. It seems this is impossible by hardware design because mic and DAC share same clock pins (shared i2s port?) but have different signal modes (different i2s ports?) Or I just don't get it... In that case please help with a simple (even local mic -> dac) feedback example or fix my attempt below. Thank you!
#include <Arduino.h>
/* Press button to record, release button to playback */
#include <driver/i2s.h>
#include <M5Atom.h>
#include <array>
#define CONFIG_I2S_BCK_PIN 19
#define CONFIG_I2S_LRCK_PIN 33
#define CONFIG_I2S_DATA_PIN 22
#define CONFIG_I2S_DATA_IN_PIN 23
#define SPEAKER_I2S_NUMBER I2S_NUM_1
#define MIC_I2S_NUMBER I2S_NUM_0
#define DATA_SIZE 1024
#define BUFFERS 80
std::array<uint8_t, DATA_SIZE> buffers[BUFFERS];
volatile size_t read_buffer = 0;
size_t write_buffer = 0;
enum intercom_state { TO_IDLE, IDLE, MIC, RELEASE, DELAY, SPEAKER };
const char *intercom_states[] = {"TO_IDLE", "IDLE", "MIC", "RELEASE", "DELAY", "SPEAKER"};
class IntercomState {
public:
IntercomState() : state(TO_IDLE) {}
IntercomState &operator=(enum intercom_state new_state) {
Serial.printf("State %s -> %s\n", intercom_states[state], intercom_states[new_state]);
state = new_state;
return *this;
}
operator enum intercom_state() const {
return state;
}
private:
enum intercom_state state;
};
IntercomState state;
TaskHandle_t micTask;
void micTaskCode( void * parameter ) {
delay(100); // let setup() print complete before we do something
Serial.printf("Mic core: %d\n", xPortGetCoreID());
for(;;) {
if (read_buffer < BUFFERS) {
size_t bytes_read;
i2s_read(MIC_I2S_NUMBER,
(char *)buffers[read_buffer].data(), DATA_SIZE,
&bytes_read, (100 / portTICK_RATE_MS));
read_buffer++;
}
else {
delay(10);
}
}
}
void InstallI2SDriver(i2s_port_t port) {
esp_err_t err = ESP_OK;
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER),
.sample_rate = 16000,
.bits_per_sample =
I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
.channel_format = I2S_CHANNEL_FMT_ALL_RIGHT,
#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(4, 1, 0)
.communication_format =
I2S_COMM_FORMAT_STAND_I2S, // Set the format of the communication.
#else // 设置通讯格式
.communication_format = I2S_COMM_FORMAT_I2S,
#endif
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 6,
.dma_buf_len = 60,
};
// using TX or RX or both seems not to make a difference
if (port == MIC_I2S_NUMBER) {
i2s_config.mode =
(i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX | I2S_MODE_PDM);
} else {
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX);
i2s_config.use_apll = false;
i2s_config.tx_desc_auto_clear = true;
}
err = i2s_driver_install(port, &i2s_config, 0, NULL);
if (err != ESP_OK) {
Serial.printf("Install I2S driver %d failed with rc = %d\n", port, err);
}
err = i2s_set_clk(port, 16000, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
if (err != ESP_OK) {
Serial.printf("Set I2S clock %d failed with rc = %d\n", port, err);
}
}
void SetI2SPins(i2s_port_t port) {
esp_err_t err = ESP_OK;
i2s_pin_config_t tx_pin_config;
#if (ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(4, 3, 0))
tx_pin_config.mck_io_num = I2S_PIN_NO_CHANGE;
#endif
tx_pin_config.bck_io_num = CONFIG_I2S_BCK_PIN;
tx_pin_config.ws_io_num = CONFIG_I2S_LRCK_PIN;
if (port == MIC_I2S_NUMBER) {
tx_pin_config.data_out_num = I2S_PIN_NO_CHANGE;
tx_pin_config.data_in_num = CONFIG_I2S_DATA_IN_PIN;
} else {
tx_pin_config.data_out_num = CONFIG_I2S_DATA_PIN;
tx_pin_config.data_in_num = I2S_PIN_NO_CHANGE;
}
err = i2s_set_pin(port, &tx_pin_config);
if (err != ESP_OK) {
Serial.printf("Set I2S pins %d failed with rc = %d\n", port, err);
}
}
void setup() {
M5.begin(true, false, true);
Serial.printf("Main core: %d\n", xPortGetCoreID());
i2s_driver_uninstall(MIC_I2S_NUMBER);
i2s_driver_uninstall(SPEAKER_I2S_NUMBER);
InstallI2SDriver(SPEAKER_I2S_NUMBER);
InstallI2SDriver(MIC_I2S_NUMBER);
SetI2SPins(SPEAKER_I2S_NUMBER);
SetI2SPins(MIC_I2S_NUMBER);
xTaskCreatePinnedToCore(
micTaskCode, /* Function to implement the task */
"MicTask", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
0, /* Priority of the task */
&micTask, /* Task handle. */
0); /* Core where the task should run */
}
void loop() {
uint32_t delay_start;
switch (state) {
case TO_IDLE:
M5.dis.drawpix(0, CRGB(0, 0, 255));
state = IDLE;
break;
case IDLE:
if (M5.Btn.isPressed()) {
// start recording
M5.dis.drawpix(0, CRGB(255, 0, 0));
read_buffer = 0;
SetI2SPins(MIC_I2S_NUMBER); // TODO remove for continuous operation
state = MIC;
}
break;
case MIC:
if (M5.Btn.isReleased() || read_buffer >= BUFFERS) {
write_buffer = read_buffer;
read_buffer = BUFFERS;
M5.dis.drawpix(0, CRGB(255, 255, 255));
state = RELEASE;
}
break;
case RELEASE:
if (M5.Btn.isReleased()) {
delay_start = millis();
// amplify low sounds
int16_t maxAmplitude = 0;
for (size_t i = 0; i < write_buffer; i++) {
int16_t *sample = (int16_t *)buffers[i].data();
int16_t *end = (int16_t *)(buffers[i].data() + DATA_SIZE);
while (sample < end) {
if (*sample > maxAmplitude) maxAmplitude = *sample;
double amplitude = abs((double)*sample)/INT16_MAX;
amplitude = (log10(amplitude+0.001)+3)/(log10(1+0.001)+3)*INT16_MAX;
*sample = *sample < 0 ? (int16_t)-amplitude : (int16_t)amplitude;
sample++;
}
}
Serial.println(maxAmplitude);
state = DELAY;
}
break;
case DELAY:
if (millis() - delay_start > 100) {
M5.dis.drawpix(0, CRGB(0, 255, 0));
SetI2SPins(SPEAKER_I2S_NUMBER); // TODO remove for continuous operation
state = SPEAKER;
}
break;
case SPEAKER:
for (size_t i = 0; i < write_buffer; i++) {
size_t bytes_written;
i2s_write(SPEAKER_I2S_NUMBER, (char *)buffers[i].data(),
DATA_SIZE, &bytes_written, portMAX_DELAY);
}
state = TO_IDLE;
break;
}
M5.update();
}