Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(SOURCES "audio/audio_codec.cc"
"audio/codecs/es8388_audio_codec.cc"
"audio/codecs/es8389_audio_codec.cc"
"audio/codecs/dummy_audio_codec.cc"
"audio/codecs/max98357a_inmp441_codec.cc"
"audio/processors/audio_debugger.cc"
"led/single_led.cc"
"led/circular_strip.cc"
Expand Down Expand Up @@ -192,6 +193,10 @@ elseif(CONFIG_BOARD_TYPE_XMINI_C3)
set(BOARD_TYPE "xmini-c3")
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_ESP32_C3_MINI_KONAN)
set(BOARD_TYPE "esp32-c3-mini-konan")
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_ESP32S3_KORVO2_V3)
set(BOARD_TYPE "esp32s3-korvo2-v3")
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
Expand Down Expand Up @@ -648,8 +653,8 @@ endif()

file(GLOB COMMON_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/common/*.ogg)

# If target chip is ESP32, exclude specific files to avoid build errors
if(CONFIG_IDF_TARGET_ESP32)
# If target chip is ESP32 or ESP32-C3, exclude specific files to avoid build errors
if(CONFIG_IDF_TARGET_ESP32 OR CONFIG_IDF_TARGET_ESP32C3)
list(REMOVE_ITEM SOURCES "audio/codecs/box_audio_codec.cc"
"audio/codecs/es8388_audio_codec.cc"
"audio/codecs/es8389_audio_codec.cc"
Expand Down
3 changes: 3 additions & 0 deletions main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ choice BOARD_TYPE
config BOARD_TYPE_XMINI_C3
bool "Xmini C3"
depends on IDF_TARGET_ESP32C3
config BOARD_TYPE_ESP32_C3_MINI_KONAN
bool "ESP32-C3 Mini Konan (MAX98357A + INMP441)"
depends on IDF_TARGET_ESP32C3
config BOARD_TYPE_ESP32S3_KORVO2_V3
bool "ESP32S3 KORVO2 V3"
depends on IDF_TARGET_ESP32S3
Expand Down
196 changes: 196 additions & 0 deletions main/audio/codecs/max98357a_inmp441_codec.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#include "max98357a_inmp441_codec.h"
#include <esp_log.h>
#include <driver/i2s_std.h>

#define TAG "Max98357aInmp441Codec"

Max98357aInmp441Codec::Max98357aInmp441Codec(int input_sample_rate, int output_sample_rate,
gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t sd_mode_pin) : sd_mode_pin_(sd_mode_pin) {

duplex_ = true;
input_reference_ = false;
input_channels_ = 1;
output_channels_ = 1;
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
input_gain_ = 0.0;

// Configure SD_MODE pin for MAX98357A if provided
if (sd_mode_pin_ != GPIO_NUM_NC) {
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << sd_mode_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
gpio_set_level(sd_mode_pin_, 1); // Enable MAX98357A
}

CreateDuplexChannels(bclk, ws, dout, din);
ESP_LOGI(TAG, "MAX98357A + INMP441 codec initialized");
}

Max98357aInmp441Codec::~Max98357aInmp441Codec() {
if (tx_handle_) {
i2s_channel_disable(tx_handle_);
i2s_del_channel(tx_handle_);
}
if (rx_handle_) {
i2s_channel_disable(rx_handle_);
i2s_del_channel(rx_handle_);
}

// Shutdown MAX98357A
if (sd_mode_pin_ != GPIO_NUM_NC) {
gpio_set_level(sd_mode_pin_, 0);
}
}

void Max98357aInmp441Codec::CreateDuplexChannels(gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
// Create I2S channels for duplex operation
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
.auto_clear_after_cb = true,
.auto_clear_before_cb = false,
.intr_priority = 0,
};
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));

// Configure TX (output to MAX98357A)
i2s_std_config_t tx_std_cfg = {
.clk_cfg = {
.sample_rate_hz = (uint32_t)output_sample_rate_,
.clk_src = I2S_CLK_SRC_DEFAULT,
.ext_clk_freq_hz = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256
},
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = bclk,
.ws = ws,
.dout = dout,
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false
}
}
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg));

// Configure RX (input from INMP441)
i2s_std_config_t rx_std_cfg = {
.clk_cfg = {
.sample_rate_hz = (uint32_t)input_sample_rate_,
.clk_src = I2S_CLK_SRC_DEFAULT,
.ext_clk_freq_hz = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256
},
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = bclk,
.ws = ws,
.dout = I2S_GPIO_UNUSED,
.din = din,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false
}
}
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &rx_std_cfg));

ESP_LOGI(TAG, "Duplex I2S channels created");
}

void Max98357aInmp441Codec::SetOutputVolume(int volume) {
// MAX98357A doesn't support software volume control
// Volume is controlled by SD_MODE pin (3 levels) or external circuit
ESP_LOGW(TAG, "MAX98357A doesn't support software volume control");
AudioCodec::SetOutputVolume(volume);
}

void Max98357aInmp441Codec::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
if (enable) {
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
ESP_LOGI(TAG, "INMP441 input enabled");
} else {
ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_));
ESP_LOGI(TAG, "INMP441 input disabled");
}
AudioCodec::EnableInput(enable);
}

void Max98357aInmp441Codec::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
if (enable) {
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
if (sd_mode_pin_ != GPIO_NUM_NC) {
gpio_set_level(sd_mode_pin_, 1);
}
ESP_LOGI(TAG, "MAX98357A output enabled");
} else {
ESP_ERROR_CHECK(i2s_channel_disable(tx_handle_));
if (sd_mode_pin_ != GPIO_NUM_NC) {
gpio_set_level(sd_mode_pin_, 0);
}
ESP_LOGI(TAG, "MAX98357A output disabled");
}
AudioCodec::EnableOutput(enable);
}

int Max98357aInmp441Codec::Read(int16_t* dest, int samples) {
if (!input_enabled_) {
return 0;
}

size_t bytes_read = 0;
// INMP441 outputs 32-bit data, we need to convert to 16-bit
int32_t temp_buffer[AUDIO_CODEC_DMA_FRAME_NUM];
size_t bytes_to_read = samples * sizeof(int32_t);

esp_err_t ret = i2s_channel_read(rx_handle_, temp_buffer, bytes_to_read, &bytes_read, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2S read failed: %d", ret);
return 0;
}

// Convert 32-bit to 16-bit (take upper 16 bits)
int samples_read = bytes_read / sizeof(int32_t);
for (int i = 0; i < samples_read; i++) {
dest[i] = (int16_t)(temp_buffer[i] >> 16);
}

return samples_read;
}

int Max98357aInmp441Codec::Write(const int16_t* data, int samples) {
if (!output_enabled_) {
return 0;
}

size_t bytes_written = 0;
size_t bytes_to_write = samples * sizeof(int16_t);

esp_err_t ret = i2s_channel_write(tx_handle_, data, bytes_to_write, &bytes_written, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2S write failed: %d", ret);
return 0;
}

return bytes_written / sizeof(int16_t);
}
27 changes: 27 additions & 0 deletions main/audio/codecs/max98357a_inmp441_codec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef _MAX98357A_INMP441_CODEC_H
#define _MAX98357A_INMP441_CODEC_H

#include "audio_codec.h"
#include <driver/gpio.h>

class Max98357aInmp441Codec : public AudioCodec {
private:
gpio_num_t sd_mode_pin_;

void CreateDuplexChannels(gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);

virtual int Read(int16_t* dest, int samples) override;
virtual int Write(const int16_t* data, int samples) override;

public:
Max98357aInmp441Codec(int input_sample_rate, int output_sample_rate,
gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t sd_mode_pin = GPIO_NUM_NC);
virtual ~Max98357aInmp441Codec();

virtual void SetOutputVolume(int volume) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
};

#endif // _MAX98357A_INMP441_CODEC_H
30 changes: 30 additions & 0 deletions main/boards/esp32-c3-mini-konan/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_

#include <driver/gpio.h>

#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000

// I2S pins for MAX98357A (output) and INMP441 (input)
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_4 // Bit Clock
#define AUDIO_I2S_GPIO_WS GPIO_NUM_5 // Word Select (LRC)
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6 // Data Out to MAX98357A
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 // Data In from INMP441

// MAX98357A gain control (optional, can be tied to VDD/GND for fixed gain)
#define MAX98357A_SD_MODE_PIN GPIO_NUM_8 // Shutdown/Gain control

// Built-in LED and button
#define BUILTIN_LED_GPIO GPIO_NUM_2
#define BOOT_BUTTON_GPIO GPIO_NUM_9

// Display configuration (SSD1306 OLED)
#define DISPLAY_I2C_SDA_PIN GPIO_NUM_10
#define DISPLAY_I2C_SCL_PIN GPIO_NUM_3
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true

#endif // _BOARD_CONFIG_H_
32 changes: 32 additions & 0 deletions main/boards/esp32-c3-mini-konan/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"target": "esp32c3",
"builds": [
{
"name": "esp32-c3-mini-konan",
"sdkconfig_append": [
"CONFIG_PM_ENABLE=y",
"CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y",
"CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER=n",
"CONFIG_LVGL_USE_DRAW_SW=y",
"CONFIG_LVGL_USE_DRAW_VGLITE=n",
"CONFIG_LVGL_USE_DRAW_SDL=n",
"CONFIG_LVGL_USE_FREETYPE=n",
"CONFIG_SPIRAM=n",
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"main/boards/esp32-c3-mini-konan/partitions_4mb.csv\"",
"CONFIG_COMPILER_OPTIMIZATION_SIZE=y",
"CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y",
"CONFIG_LOG_DEFAULT_LEVEL_INFO=y",
"CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y",
"CONFIG_BT_ENABLED=n",
"CONFIG_BT_BLUEDROID_ENABLED=n",
"CONFIG_BT_NIMBLE_ENABLED=n",
"CONFIG_LWIP_IPV6=n",
"CONFIG_LWIP_NETIF_LOOPBACK=n"
]
}
]
}
Loading