Add SPM1423 PDM microphone driver, fix speaker I2S port

Microphone on I2S_NUM_0 (PDM hardware constraint), speaker moved to
I2S_NUM_1. Both init at boot, shut down before deep sleep.
Mic provides raw audio read and RMS level measurement.
Boot beep disabled (speaker confirmed working).
This commit is contained in:
GlassOnTin 2026-03-27 19:08:26 +00:00
commit c31b1908a7
3 changed files with 88 additions and 4 deletions

82
Microphone.h Normal file
View file

@ -0,0 +1,82 @@
// SPM1423 PDM Microphone Driver — Minimal audio input for T-Watch Ultra
// PDM input on I2S_NUM_0 (hardware constraint for PDM on ESP32-S3)
#ifndef MICROPHONE_H
#define MICROPHONE_H
#if BOARD_MODEL == BOARD_TWATCH_ULT
#include "driver/i2s.h"
#define MIC_CLK_PIN 17 // PDM clock (WS pin in I2S terms)
#define MIC_DAT_PIN 18 // PDM data input
#define MIC_I2S_PORT I2S_NUM_0 // PDM only works on I2S0
#define MIC_SAMPLE_RATE 16000 // 16kHz max for PDM mic
#define MIC_BUF_SIZE 1024
static bool mic_ready = false;
bool mic_init() {
i2s_config_t i2s_config = {};
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
i2s_config.sample_rate = MIC_SAMPLE_RATE;
i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_PCM_SHORT;
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1;
i2s_config.dma_buf_count = 6;
i2s_config.dma_buf_len = 512;
i2s_config.use_apll = true;
i2s_pin_config_t pin_config = {};
pin_config.bck_io_num = I2S_PIN_NO_CHANGE;
pin_config.ws_io_num = MIC_CLK_PIN;
pin_config.data_out_num = I2S_PIN_NO_CHANGE;
pin_config.data_in_num = MIC_DAT_PIN;
pin_config.mck_io_num = I2S_PIN_NO_CHANGE;
if (i2s_driver_install(MIC_I2S_PORT, &i2s_config, 0, NULL) != ESP_OK) {
return false;
}
if (i2s_set_pin(MIC_I2S_PORT, &pin_config) != ESP_OK) {
i2s_driver_uninstall(MIC_I2S_PORT);
return false;
}
mic_ready = true;
return true;
}
void mic_end() {
if (mic_ready) {
i2s_driver_uninstall(MIC_I2S_PORT);
mic_ready = false;
}
}
// Read raw audio samples into buffer. Returns bytes read.
size_t mic_read(int16_t *buf, size_t samples) {
if (!mic_ready) return 0;
size_t bytes_read = 0;
i2s_read(MIC_I2S_PORT, buf, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY);
return bytes_read / sizeof(int16_t);
}
// Get current audio level (RMS of a short sample). Returns 0-32767.
uint16_t mic_level() {
if (!mic_ready) return 0;
int16_t buf[256];
size_t samples = mic_read(buf, 256);
if (samples == 0) return 0;
uint64_t sum_sq = 0;
for (size_t i = 0; i < samples; i++) {
int32_t s = buf[i];
sum_sq += s * s;
}
return (uint16_t)sqrt((double)sum_sq / samples);
}
#endif // BOARD_MODEL == BOARD_TWATCH_ULT
#endif // MICROPHONE_H

View file

@ -29,8 +29,9 @@
SensorBHI260AP *bhi260 = NULL;
bool bhi260_ready = false;
// MAX98357A I2S speaker
// MAX98357A I2S speaker + SPM1423 PDM microphone
#include "Speaker.h"
#include "Microphone.h"
// CST9217 capacitive touch panel
#include <touch/TouchDrvCST92xx.h>
@ -307,9 +308,9 @@ void setup() {
attachInterrupt(TP_INT, touch_isr, FALLING);
}
// Init speaker (BLDO2 already enabled by PMU init)
// Init speaker (BLDO2 already enabled by PMU init) and microphone
speaker_init();
if (speaker_ready) speaker_beep(); // Boot audio feedback
mic_init();
// BHI260AP init deferred — firmware upload takes ~10s at 1MHz I2C
// and blocks serial communication during boot. Will be initialized
@ -2078,6 +2079,7 @@ void twatch_enter_deep_sleep(bool beacon_timer) {
}
// 1. Shut down audio and display before closing buses
mic_end();
speaker_end();
#if HAS_DISPLAY
co5300_sleep();

View file

@ -12,7 +12,7 @@
#define SPK_BCLK I2S_BCLK
#define SPK_WCLK I2S_WCLK
#define SPK_DOUT I2S_DOUT
#define SPK_I2S_PORT I2S_NUM_0
#define SPK_I2S_PORT I2S_NUM_1 // I2S_NUM_0 reserved for PDM microphone
#define SPK_SAMPLE_RATE 16000
#define SPK_TONE_BUF_SIZE 512