Audio System + Drivers (#562)

This commit is contained in:
Shadowtrance
2026-07-14 16:34:29 +10:00
committed by GitHub
parent fa4a6e255c
commit 955416dac8
137 changed files with 8847 additions and 387 deletions
+1 -1
View File
@@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ILI934x FT6x36 AXP2101 AW9523 driver vfs fatfs ina226-module py32ioexpander-module
REQUIRES Tactility esp_lvgl_port ILI934x FT6x36 AXP2101 driver vfs fatfs ina226-module py32ioexpander-module
)
@@ -2,6 +2,7 @@
#include "devices/Power.h"
#include <tactility/device.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#include <drivers/py32ioexpander.h>
@@ -20,38 +21,45 @@ static const auto* TAG = "StackChan";
// I2C addresses
// ---------------------------------------------------------------------------
static constexpr uint8_t AXP2101_ADDR = 0x34;
static constexpr uint8_t AW9523B_ADDR = 0x58;
static constexpr uint8_t AW88298_ADDR = 0x36;
static constexpr uint8_t ES7210_ADDR = 0x40;
// ---------------------------------------------------------------------------
// AW9523B GPIO expander — same wiring as CoreS3
// AW9523B GPIO expander pin map — same wiring as CoreS3.
// AW88298 reset (P0_2) is driven directly by the aw88298-module driver itself
// (see m5stack,stackchan.dts pin-reset property), not from here.
// ---------------------------------------------------------------------------
// P0 pins: 0=touch reset, 1=bus out enable, 2=AW88298 reset, 4=SD card, 5=USB OTG
// P1 pins: 0=cam reset, 1=LCD reset, 7=boost enable (SY7088)
static constexpr uint8_t AW9523B_CTL_REG = 0x11; // P0 push-pull mode
static constexpr uint8_t AW9523B_P0_REG = 0x02;
static constexpr uint8_t AW9523B_P1_REG = 0x03;
constexpr auto AW9523B_PIN_TOUCH_RESET = 0; // P0_0
constexpr auto AW9523B_PIN_BUS_OUT_ENABLE = 1; // P0_1
constexpr auto AW9523B_PIN_SD_CARD_SWITCH = 4; // P0_4
constexpr auto AW9523B_PIN_LCD_RESET = 8 + 1; // P1_1
constexpr auto AW9523B_PIN_BOOST_ENABLE = 8 + 7; // P1_7 (SY7088)
static bool initGpioExpander(::Device* i2c) {
// P0: touch, bus enable, AW88298 reset, SD card switch
constexpr uint8_t p0 = (1U << 0U) | (1U << 1U) | (1U << 2U) | (1U << 4U);
// P1: LCD reset, boost enable
constexpr uint8_t p1 = (1U << 1U) | (1U << 7U);
static bool initGpioExpander(::Device* aw9523b) {
struct PinInit { uint8_t pin; bool level; };
static constexpr PinInit pins[] = {
{ AW9523B_PIN_TOUCH_RESET, true },
{ AW9523B_PIN_BUS_OUT_ENABLE, true },
{ AW9523B_PIN_SD_CARD_SWITCH, true },
{ AW9523B_PIN_LCD_RESET, true },
{ AW9523B_PIN_BOOST_ENABLE, true },
};
// Set P0 to push-pull mode
if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_CTL_REG, 0x10, pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: Failed to set CTL");
return false;
}
if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_P0_REG, p0, pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: Failed to set P0");
return false;
}
if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_P1_REG, p1, pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: Failed to set P1");
return false;
for (const auto& pinInit : pins) {
auto* descriptor = gpio_descriptor_acquire(aw9523b, pinInit.pin, GPIO_OWNER_GPIO);
if (descriptor == nullptr) {
LOG_E(TAG, "AW9523B: Failed to acquire pin %u", pinInit.pin);
return false;
}
error_t error = gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT);
if (error == ERROR_NONE) {
error = gpio_descriptor_set_level(descriptor, pinInit.level);
}
gpio_descriptor_release(descriptor);
if (error != ERROR_NONE) {
LOG_E(TAG, "AW9523B: Failed to configure pin %u", pinInit.pin);
return false;
}
}
return true;
}
@@ -80,100 +88,7 @@ static bool initPowerControl(::Device* i2c) {
}
// ---------------------------------------------------------------------------
// AW88298 speaker amplifier
// Called once in initBoot() at 16kHz. AW88298 default state after hardware reset
// is sufficient for SfxEngine. Sequence mirrors M5Unified _speaker_enabled_cb_cores3().
// NOTE: 44100Hz (AudioPlayer/MusicPlayer) needs esp_codec_dev integration — see memory notes.
// ---------------------------------------------------------------------------
static bool initSpeaker(::Device* i2c, uint32_t sample_rate_hz) {
// M5Unified rate table: (sample_rate + 1102) / 2205 steps, first entry >= result
static constexpr uint8_t rate_tbl[] = { 4, 5, 6, 8, 10, 11, 15, 20, 22, 44 };
size_t reg06_idx = 0;
size_t rate = (sample_rate_hz + 1102) / 2205;
while (rate > rate_tbl[reg06_idx] && ++reg06_idx < sizeof(rate_tbl)) {}
if (reg06_idx >= sizeof(rate_tbl)) {
reg06_idx = sizeof(rate_tbl) - 1; // clamp to max supported rate
}
// 0x14C0: M5Unified's upper byte for CoreS3, I2SBCK=0 (BCK 16*2)
const uint16_t reg06 = static_cast<uint16_t>(0x14C0U | reg06_idx);
// Hardware reset AW88298 via AW9523B P0 bit 2: pull LOW (reset), then HIGH (release).
if (i2c_controller_register8_reset_bits(i2c, AW9523B_ADDR, AW9523B_P0_REG, 0b00000100, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: failed to assert AW88298 reset");
return false;
}
vTaskDelay(pdMS_TO_TICKS(10));
if (i2c_controller_register8_set_bits(i2c, AW9523B_ADDR, AW9523B_P0_REG, 0b00000100, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "AW9523B: failed to release AW88298 reset");
return false;
}
vTaskDelay(pdMS_TO_TICKS(50));
// Exact sequence from M5Unified _speaker_enabled_cb_cores3() — no I2C software reset.
struct { uint8_t reg; uint16_t val; } regs[] = {
{ 0x61, 0x0673 }, // boost mode disabled
{ 0x04, 0x4040 }, // I2SEN=1, AMPPD=0, PWDN=0
{ 0x05, 0x0008 }, // RMSE=0, HAGCE=0, HDCCE=0, HMUTE=0
{ 0x06, reg06 }, // I2S mode + sample rate
{ 0x0C, 0x3064 }, // volume -24dB
};
for (auto& r : regs) {
if (i2c_controller_register16be_set(i2c, AW88298_ADDR, r.reg, r.val, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "AW88298: failed reg 0x%02X", r.reg);
return false;
}
}
LOG_I(TAG, "AW88298 initialized (%luHz, reg06=0x%04X)", (unsigned long)sample_rate_hz, reg06);
return true;
}
// ---------------------------------------------------------------------------
// ES7210 microphone ADC
// Source: https://github.com/m5stack/M5Unified
// ---------------------------------------------------------------------------
static bool initMicrophone(::Device* i2c) {
static constexpr uint8_t reg_data[] = {
0x00, 0x41, // RESET_CTL
0x01, 0x1F, // CLK_ON_OFF (initial)
0x06, 0x00, // DIGITAL_PDN
0x07, 0x20, // ADC_OSR
0x08, 0x10, // MODE_CFG
0x09, 0x30, // TCT0_CHPINI
0x0A, 0x30, // TCT1_CHPINI
0x20, 0x0A, // ADC34_HPF2
0x21, 0x2A, // ADC34_HPF1
0x22, 0x0A, // ADC12_HPF2
0x23, 0x2A, // ADC12_HPF1
0x02, 0xC1,
0x04, 0x01,
0x05, 0x00,
0x11, 0x60,
0x40, 0x42, // ANALOG_SYS
0x41, 0x70, // MICBIAS12
0x42, 0x70, // MICBIAS34
0x43, 0x1B, // MIC1_GAIN
0x44, 0x1B, // MIC2_GAIN
0x45, 0x00, // MIC3_GAIN
0x46, 0x00, // MIC4_GAIN
0x47, 0x00, // MIC1_LP
0x48, 0x00, // MIC2_LP
0x49, 0x00, // MIC3_LP
0x4A, 0x00, // MIC4_LP
0x4B, 0x00, // MIC12_PDN
0x4C, 0xFF, // MIC34_PDN
0x01, 0x14, // CLK_ON_OFF (final)
};
if (i2c_controller_write_register_array(i2c, ES7210_ADDR, reg_data, sizeof(reg_data), pdMS_TO_TICKS(1000)) != ERROR_NONE) {
LOG_E(TAG, "ES7210: Failed to set registers");
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// initBoot — called before device tree is started
// initBoot — called after devicetree devices are constructed/started
// ---------------------------------------------------------------------------
static std::shared_ptr<Axp2101> axp2101;
@@ -183,26 +98,22 @@ bool initBoot() {
LOG_E(TAG, "i2c_internal not found");
return false;
}
// Boost enable via AXP2101 before GPIO expander init (same as CoreS3)
// AW9523B P1 bit 7 = SY7088 boost enable — set after AXP2101 is configured
if (!initPowerControl(i2c)) {
LOG_E(TAG, "AXP2101 init failed");
return false;
}
if (!initGpioExpander(i2c)) {
LOG_E(TAG, "AW9523B init failed");
auto* aw9523b = device_find_by_name("aw9523b");
if (aw9523b == nullptr) {
LOG_E(TAG, "aw9523b not found");
return false;
}
// AW88298 default state after reset is sufficient for SfxEngine (16kHz).
// Full 44100Hz support requires esp_codec_dev integration (see memory notes).
if (!initSpeaker(i2c, 16000)) {
LOG_W(TAG, "AW88298 init failed (non-fatal)");
}
if (!initMicrophone(i2c)) {
LOG_W(TAG, "ES7210 init failed (non-fatal)");
if (!initGpioExpander(aw9523b)) {
LOG_E(TAG, "AW9523B init failed");
return false;
}
// Boot LED pattern — confirms PY32IOExpander is working.
+9 -5
View File
@@ -1,7 +1,11 @@
dependencies:
- Platforms/platform-esp32
- Drivers/bmi270-module
- Drivers/bm8563-module
- Drivers/ina226-module
- Drivers/py32ioexpander-module
- Platforms/platform-esp32
- Drivers/bmi270-module
- Drivers/bm8563-module
- Drivers/ina226-module
- Drivers/py32ioexpander-module
- Drivers/aw9523b-module
- Drivers/aw88298-module
- Drivers/es7210-module
- Drivers/audio-stream-module
dts: m5stack,stackchan.dts
+44 -14
View File
@@ -13,6 +13,9 @@
#include <bindings/bm8563.h>
#include <bindings/ina226.h>
#include <bindings/py32ioexpander.h>
#include <bindings/aw9523b.h>
#include <bindings/aw88298.h>
#include <bindings/es7210.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
@@ -36,6 +39,17 @@
gpio-count = <49>;
};
// AW88298 speaker + ES7210 microphone
i2s0: i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
pin-bclk = <&gpio0 34 GPIO_FLAG_NONE>;
pin-ws = <&gpio0 33 GPIO_FLAG_NONE>;
pin-data-out = <&gpio0 13 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>;
pin-mclk = <&gpio0 0 GPIO_FLAG_NONE>;
};
i2c_internal {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
@@ -65,10 +79,37 @@
shunt-milliohms = <10>;
};
// AW9523B pin map (same wiring as CoreS3):
// P0_0 = touch reset, P0_1 = bus-out enable, P0_2 = AW88298 reset,
// P0_4 = SD card switch, P1_1 = LCD reset, P1_7 = boost enable (SY7088)
aw9523b: aw9523b {
compatible = "awinic,aw9523b";
reg = <0x58>;
};
aw88298 {
compatible = "awinic,aw88298";
reg = <0x36>;
i2s = <&i2s0>;
pin-reset = <&aw9523b 2 GPIO_FLAG_NONE>;
};
es7210 {
compatible = "everest,es7210";
reg = <0x40>;
i2s = <&i2s0>;
// Only MIC1/MIC2 are wired to real capsules (see schematic); MIC3/MIC4
// slots carry AEC_P/AEC_N reference signal, not microphone audio.
mic-mask = <3>;
// M5Unified applies a 4x post-gain "magnification" for this exact mic
// wiring (see M5Unified.cpp _mic_data_cb_cores3): the ES7210's own
// hardware ADC gain (already near-max via the default 90% input volume
// setting, ~34dB of its 0-37.5dB range) still isn't enough for these
// capsules on their own.
input-gain-percent = <400>;
};
// AXP2101 PMIC @ 0x34 — initialized manually in initBoot()
// AW9523B GPIO expander @ 0x58 — initialized manually in initBoot() (same as CoreS3)
// AW88298 speaker amp @ 0x36 — initialized manually in initBoot()
// ES7210 microphone ADC @ 0x40 — initialized manually in initBoot()
// FT6336U capacitive touch @ 0x38 — used by Display driver (FT6x36 library)
// TODO: Si12T 3-zone head touch @ 0x68 — INT active-low, 10kΩ pull-up to 3.3V; driver not yet implemented
@@ -135,15 +176,4 @@
frequency-khz = <20000>;
};
};
// AW88298 speaker + ES7210 microphone
i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
pin-bclk = <&gpio0 34 GPIO_FLAG_NONE>;
pin-ws = <&gpio0 33 GPIO_FLAG_NONE>;
pin-data-out = <&gpio0 13 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>;
pin-mclk = <&gpio0 0 GPIO_FLAG_NONE>;
};
};