uart-jtag

Example of the component embedblocks/i2s_mic v0.1.3
# i2s_mic streaming example — USB-Serial-JTAG variant

**For ESP32-C3, ESP32-S3, ESP32-C6, and other boards with a native
USB-Serial-JTAG peripheral and no separate USB-to-UART bridge chip** — the
single USB cable you already flash/monitor through is used for the audio
stream too. No extra hardware needed.

If your board instead has a separate CP2102/CH340-style bridge chip (a
second, distinct COM port appears when you plug in — true of most classic
ESP32 devkits), use the sibling example **`i2s_mic_uart_bridge_example`**
instead. The two are not interchangeable: this one only works on chips
with a native USB-Serial-JTAG peripheral, and reuses the exact same cable
your programming/console connection already uses.

Captures audio from an I2S digital MEMS microphone using the `i2s_mic`
component and streams it to a PC, which either plays it back in real time
or records it to a WAV file. Developed and directly tested with an
**MSM261S4030H0**, which worked unmodified. See "Microphone compatibility" below.

**Everything about the transport (handshake, sync bytes, header layout,
16-bit downconversion) lives in this example, not in `i2s_mic` itself.**
The component only knows about I2S and filled buffers.

## Wiring

| Mic pin | Example default (adjust for your board) |
|---|---|
| SCK (BCLK) | GPIO 7 |
| WS (LRCLK) | GPIO 8 |
| SD (DOUT)  | GPIO 9 |
| L/R        | GND |
| VDD        | 3.3V |
| GND        | GND |

Change `GPIO_BCK` / `GPIO_WS` / `GPIO_DATA` in `main/app_main.c` if your
board uses different pins. **Make sure L/R is actually tied to GND or
VDD, never left floating** — a floating L/R pin is a common source of
hum/noise on its own, independent of anything else in this README.

## Microphone compatibility

`i2s_mic` has no part-specific logic at all — it's a generic I2S receiver
that copies whatever the DMA hands it. This example was developed and
directly tested with an **MSM261S4030H0**, which worked unmodified. The
INMP441 is used as the reference part in the wiring diagram and text below
simply because it's the most widely documented I2S MEMS mic — but it has
**not** actually been tested against this example. Compatibility with it
is expected, not confirmed, since it shares the same 24-bit-in-32-bit
Philips format as the MSM261S4030H0. Other parts using that same format
(e.g. ICS-43434, SPH0645) are likely compatible for the same reason, also
untested.

**About mono capture — read this if your recording is distorted or
hum-only.** On at least one tested ESP32-C3 / ESP-IDF v6.0.x combination,
requesting `I2S_SLOT_MODE_MONO` for RX did **not** give a compact
single-slot stream — the DMA buffer still contained both I2S slots
interleaved, and only one of every two 32-bit words was real mic audio.
This example works around it by requesting **STEREO** explicitly and
discarding the unwanted slot in `audio_sender_task()`, controlled by the
`KEEP_SLOT` macro (0 = first/even word of each pair, 1 = second/odd word).
With L/R tied to GND, the real signal showed up in slot 0. If your
recording comes out silent or hum-only instead of clean, try `KEEP_SLOT 1`.

## Build and flash

This example vendors a local copy of `i2s_mic` under `components/i2s_mic/`
so it builds standalone without needing the component published to the
ESP Component Registry first. Requires ESP-IDF v6.0.x.

```bash
idf.py set-target esp32c3       # or esp32s3 / esp32c6
idf.py build
idf.py -p /dev/ttyACM0 flash monitor
```

Once you see `=== READY, waiting for trigger ===`, quit the monitor
(Ctrl+], or Ctrl+T then Ctrl+X) so the PC script can open the port itself —
the ESP32 and the monitor can't both hold it at once.

## Run the PC-side script

```bash
pip install pyserial
pip install sounddevice   # only needed for --mode play

# Record to a WAV file (Ctrl+C to stop and finalize the file):
python pc/mic_audio_rcv.py --port COM5 --mode record --out capture.wav

# Or listen live:
python pc/mic_audio_rcv.py --port COM5 --mode play
```

(`COM5` on Windows; `/dev/ttyACM0` or similar on Linux/macOS.)

## What's actually happening

1. `main/app_main.c` configures `i2s_mic` for 16 kHz, **stereo**, **32-bit**
   samples — INMP441 outputs 24-bit audio MSB-justified in a 32-bit slot,
   and `i2s_mic` only supports 16- or 32-bit slots (24-bit is rejected at
   `init()`), so 32-bit is the correct choice there. Stereo (rather than
   mono) is requested for the reason explained above.
2. Filled buffers are handed from the ISR callback to a FreeRTOS queue; a
   worker task (`audio_sender_task`) keeps only `KEEP_SLOT` of each
   interleaved stereo pair and downconverts that 32-bit sample to 16-bit
   (`sample >> 16`) before writing it out over USB-Serial-JTAG. If your
   recordings are too quiet or too loud, adjust the shift amount or add
   explicit gain there — it's a one-line change and entirely independent
   of `i2s_mic`.
3. The ISR **re-posts a different buffer from the pool**, not the one it
   just delivered — that buffer is still awaiting conversion/transmission
   by `audio_sender_task`. If the pool is briefly empty (consumer running
   behind), the ISR simply skips re-posting for that cycle; `i2s_mic`
   surfaces the resulting drop through `overflow_cb`, logged periodically
   by `overflow_report_task` whenever logging is enabled (it's silenced
   once the binary PCM stream starts, to avoid corrupting it).
4. `host_comm_init()` calls `usb_serial_jtag_driver_install()` to get
   interrupt-driven, buffered reads and writes on top of the same
   USB-Serial-JTAG cable that already carries the console's log output.
   These two coexist by design — ESP-IDF's console output uses a separate,
   lower-level path, which is why you already saw `=== READY ===` in the
   log before this driver was even installed.
5. The trigger read loops until it sees the specific `TRIGGER_BYTE` value
   (`0xA5`) rather than accepting the first byte it gets. This matters
   because the header is only ever sent once — if a stray byte were
   accepted as the trigger prematurely, the PC script would be left
   waiting forever for a header that already went out before it connected.

## Known limitations of this example (not of `i2s_mic`)

- Streams indefinitely; there's no clean remote "stop" — just stop the PC
  script (Ctrl+C, which finalizes the WAV correctly) or reset the board.
- No compression, no VAD — deliberately minimal, to keep the focus on how
  `i2s_mic` itself is used.
- Sharing the console channel for both logging and the audio stream means
  you lose console log visibility for the duration of a capture.

To create a project from this example, run:

idf.py create-project-from-example "embedblocks/i2s_mic=0.1.3:uart-jtag"

or download archive (~12.88 KB)