SPH0645-Microphone-Integration
Skill from synqing/K1.hardware
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
---
name: SPH0645-Microphone-Integration
keywords: SPH0645, I2S, microphone, audio input, PDM, PCM, ADC, I2S clock, DMA buffer, noise floor, gain, calibration, audio capture
---
# SPH0645 Microphone Integration
## Quick Reference
SPH0645 is a digital MEMS microphone that outputs PDM (Pulse Density Modulation) audio via I2S interface. Convert PDM to PCM for DSP processing.
## SPH0645 Pin Configuration
| Pin | Function | Notes |
|-----|----------|-------|
| VDD | Power (3.3V) | Bypass capacitor recommended |
| GND | Ground | Power ground |
| CLK | I2S Clock Input | From ESP32 I2S_CLK |
| DIN | Data Out (PDM) | To ESP32 I2S_DIN |
| CS | Chip Select | Usually pulled HIGH or tied to VDD |
| L/R | Channel Select | LOW=left, HIGH=right (tie to GND for mono) |
## I2S Configuration for PDM Input
### 1. I2S Driver Setup
```c
#include "driver/i2s.h"
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_RX,
.sample_rate = 16000, // PCM output rate (after decimation)
.bits_per_sample = I2S_BITS_16,
.channel_format = I2S_CHANNEL_MONO,
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2, // Double buffering
.dma_buf_len = 256, // Samples per DMA buffer
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0,
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
```
### 2. Pin Configuration
```c
i2s_pin_config_t pin_config = {
.bck_io_num = GPIO_NUM_14, // I2S Bit Clock
.ws_io_num = GPIO_NUM_13, // I2S Word Select (not used in PDM, but required)
.data_out_num = I2S_PIN_NO_CHANGE, // Not used for input
.data_in_num = GPIO_NUM_12, // PDM data from SPH0645
};
i2s_set_pin(I2S_NUM_0, &pin_config);
```
### 3. Start I2S Capture
```c
i2s_start(I2S_NUM_0);
```
## PDM to PCM Decimation
PDM stream at 2.4MHz requires decimation filter to convert to usable PCM:
### Simple Moving Average Filter
```c
#define PDM_SAMPLE_RATE 2400000 // 2.4MHz PDM
#define PCM_SAMPLE_RATE 16000 // Target 16kHz PCM
#define DECIMATION_RATIO (PDM_SAMPLE_RATE / PCM_SAMPLE_RATE) // 150
int16_t pdm_to_pcm(const int16_t *pdm_samples, size_t pdm_count,
int16_t *pcm_out) {
static int32_t accumulator = 0;
static int count = 0;
int16_t pcm_sample_count = 0;
for (size_t i = 0; i < pdm_count; i++) {
accumulator += pdm_samples[i];
count++;
if (count == DECIMATION_RATIO) {
pcm_out[pcm_sample_count++] = (int16_t)(accumulator / DECIMATION_RATIO);
accumulator = 0;
count = 0;
}
}
return pcm_sample_count; // Number of PCM samples produced
}
```
## Audio Capture with DMA Buffers
### Circular Dual-Buffer Pattern
```c
#define DMA_BUF_SIZE 512
static int16_t dma_buf1[DMA_BUF_SIZE];
static int16_t dma_buf2[DMA_BUF_SIZE];
static int16_t *current_read_buf = dma_buf1;
static int16_t *current_capture_buf = dma_buf2;
void audio_capture_task(void *arg) {
size_t bytes_read;
while (1) {
// Read from I2S DMA buffer
i2s_read(I2S_NUM_0, current_capture_buf,
DMA_BUF_SIZE * sizeof(int16_t), &bytes_read, portMAX_DELAY);
// Switch buffers for processing
int16_t *temp = current_read_buf;
current_read_buf = current_capture_buf;
current_capture_buf = temp;
// Signal processing task that data is ready
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(dsp_task_handle, 1, eSetBits, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
void dsp_processing_task(void *arg) {
int16_t pcm_buf[DMA_BUF_SIZE / DECIMATION_RATIO];
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Wait for audio data
// Convert PDM to PCM
int pcm_count = pdm_to_pcm(current_read_buf, DMA_BUF_SIZE, pcm_buf);
// Send to FFT/beat detection pipeline
process_audio_samples(pcm_buf, pcm_count);
}
}
```
## Noise Floor Calibration
### Calibration on Startup
```c
#define CALIBRATION_SAMPLES 2000
int16_t noise_floor = 0;
void calibrate_microphone(void) {
int32_t sum = 0;
int16_t max_amplitude = 0;
ESP_LOGI(TAG, "Calibrating microphone (quiet environment required)...");
for (int i = 0; i < CALIBRATION_SAMPLES; i++) {
size_t bytes_read;
int16_t sample;
i2s_read(I2S_NUM_0, &sample, sizeof(int16_t), &bytes_read, portMAX_DELAY);
int16_t abs_sample = (sample < 0) ? -sample : sample;
sum += abs_sample;
if (abs_sample > max_amplitude) max_amplitude = abs_sample;
}
int16_t avg_amplitude = sum / CALIBRATION_SAMPLES;
noise_floor = avg_amplitude * 2; // 2x average = threshold
ESP_LOGI(TAG, "Noise floor: %d (max observed: %d)", noise_floor, max_amplitude);
}
```
### Dynamic Gain Control
```c
#define TARGET_LEVEL 20000 // Target RMS level
#define GAIN_FACTOR 1.5f
float calculate_gain(int16_t current_rms) {
if (current_rms == 0) return 1.0f;
float gain = (float)TARGET_LEVEL / current_rms;
return (gain > GAIN_FACTOR) ? GAIN_FACTOR : gain; // Limit max gain
}
void apply_gain(int16_t *samples, size_t count, float gain) {
for (size_t i = 0; i < count; i++) {
int32_t amplified = (int32_t)samples[i] * gain;
// Clip to prevent overflow
if (amplified > INT16_MAX) {
samples[i] = INT16_MAX;
} else if (amplified < INT16_MIN) {
samples[i] = INT16_MIN;
} else {
samples[i] = (int16_t)amplified;
}
}
}
```
## Audio Level Monitoring
### RMS Calculation
```c
int16_t calculate_rms(const int16_t *samples, size_t count) {
int64_t sum_squares = 0;
for (size_t i = 0; i < count; i++) {
int32_t sample = samples[i];
sum_squares += sample * sample;
}
int32_t mean_square = sum_squares / count;
return (int16_t)sqrt(mean_square);
}
```
### Peak Detection
```c
int16_t find_peak(const int16_t *samples, size_t count) {
int16_t peak = 0;
for (size_t i = 0; i < count; i++) {
int16_t abs_sample = (samples[i] < 0) ? -samples[i] : samples[i];
if (abs_sample > peak) peak = abs_sample;
}
return peak;
}
```
### dB Conversion
```c
float sample_to_db(int16_t sample) {
if (sample == 0) return -96.0f; // Min for 16-bit
float normalized = (float)sample / 32768.0f;
return 20.0f * log10(normalized);
}
```
## Anti-Patterns to Avoid
### ❌ WRONG: Blocking I2S read in main loop
```c
void bad_audio_capture(void) {
int16_t sample;
size_t bytes_read;
i2s_read(I2S_NUM_0, &sample, 2, &bytes_read, portMAX_DELAY);
// ← Blocks for 125µs per sample!
}
```
### ✅ CORRECT: DMA-based capture with task notification
```c
void good_audio_capture(void) {
// i2s_read() with DMA handles buffering automatically
// Process in separate task, no blocking on main thread
}
```
### ❌ WRONG: Allocating audio buffers on stack
```c
void bad_buffer_allocation(void) {
int16_t audio_buf[4096]; // ← Stack overflow!
i2s_read(I2S_NUM_0, audio_buf, 8192, ...);
}
```
### ✅ CORRECT: Static/global buffers
```c
static int16_t audio_buf[4096]; // Global memory
void good_buffer_allocation(void) {
i2s_read(I2S_NUM_0, audio_buf, 8192, ...);
}
```
## Troubleshooting
### No Audio Captured
- Check I2S pins are correct (CLK on GPIO_NUM_14, DIN on GPIO_NUM_12)
- Verify SPH0645 power supply (3.3V stable)
- Check L/R pin (should be tied LOW for mono left channel)
- Test with oscilloscope: verify CLK frequency
### Only Silence Captured
- Verify microphone PCB assembly (check solder joints)
- SPH0645 might need power-up delay (add 100ms)
- Check I2S sample rate configuration
### Audio Distortion/Clipping
- Reduce gain or check for input overload
- Verify audio buffer size (larger buffer = lower dropout risk)
- Check DMA buffer count (2 buffers minimum)
### Noise Too High
- Run calibration in quiet environment
- Check for RF interference near microphone
- Verify power supply filtering
## Performance Notes
| Configuration | Latency | CPU Load |
|---|---|---|
| PDM raw (2.4MHz) | <1ms | ~5% |
| PDM→PCM decimation | ~1-5ms | ~8% |
| Full pipeline (capture+DSP) | ~10-20ms | ~15% |
## Key Parameters to Tune
```c
#define I2S_SAMPLE_RATE 16000 // PCM output rate
#define DMA_BUF_SIZE 512 // Increase for stability
#define DMA_BUF_COUNT 2 // Minimum 2 for double-buffering
#define NOISE_FLOOR_THRESHOLD 2.0f // Multiplier above baseline
#define GAIN_TARGET 20000 // Target RMS level
#define MAX_GAIN 1.5f // Prevent over-amplification
```
## References
- SPH0645 Datasheet: Timing, pinout, electrical specifications
- ESP32-S3 I2S Driver: https://github.com/espressif/esp-idf/tree/master/components/driver/i2s
- PDM Filtering Theory: https://en.wikipedia.org/wiki/Pulse-density_modulation