Acoustic data modem operating in air, built on ESP32. Two identical half-duplex nodes exchange short ASCII messages over an audio link (BFSK), with framing, CRC error detection and stop-and-wait ARQ.
Designed as a proof of concept of the techniques used in underwater acoustic communication, transposed to air to remove the transducer and channel complexity.
Use of the ESP32 with ESP-IDF for firmware development.
References:
- BlueBuzz, an Open-Source Acoustic Modem — Mayberry et al., Georgia Tech, OCEANS 2022
- Acoustic Data Modem — Malysa & Romanenko, Cornell ECE 4760, 2010
The first reference is mainly used for understanding the architecture and operation of a marine acoustic modem. Several design choices were derived from these references. Goertzel detection instead of FFT, low and deterministic CPU cost for a two-tone problem ; BFSK instead of OOK, magnitude-ratio decision, insensitive to distance and volume, which was the main weakness reported by the Cornell project ; and Hann-windowed bursts on TX to limit spectral leakage.
The sample rate is 16 kHz, well above the Nyquist limit for the highest tone of 1.9 kHz. The two BFSK frequencies are 1100 Hz (space, bit 0) and 1900 Hz (mark, bit 1), chosen so that the Goertzel bin index k = N*f/Fs is an exact integer for both, k = 11 and k = 19 with a block size of N = 160 samples. With N = 160 at 16 kHz gives a bit duration of 10 ms and a raw bit rate of 100 bps, consistent with short-range acoustic modems.
lib/ portable C, signal processing and framing
firmware/ ESP32 application, I2S drivers, FreeRTOS tasks, ARQ
simulation/ Python loopback, modulator, AWGN channel, demodulator
webapp/ Flask web interface, serial bridge to the modem
| File | Role |
|---|---|
modem_config.h |
Definition of all signal and frame parameters |
goertzel.c |
Coefficient computation and power estimation at a single frequency |
crc8.c |
CRC-8/CCITT error detection (poly 0x07, init 0x00) |
frame.c |
Frame encoder and byte-by-byte decoder state machine |
bitsync.c |
Bit-to-byte assembly with silence detection and reset |
modulate.c |
BFSK modulator, generates Hann-windowed int16 samples for a byte buffer |
Each module has a corresponding test in lib/tests/. Run on host with:
cmake -B build && cmake --build build && ctest --test-dir buildpayload (bytes)
│
frame_encode [0xAA 0xAA | 0x7E | LEN | payload | CRC8]
│
modulate (Hann-windowed BFSK bursts, 160 * int16 per bit)
│
signal data
I2S samples (160 × int16)
│
goertzel_power on both frequency
│
goertzel_decide (-1 / 0 / 1)
│
bitsync_feed (8 bits grouped by byte)
│
frame_decode (state machine)
│
frame_t
Two FreeRTOS tasks share the acoustic channel through mutex s_channel so that RX and TX never overlap.
| Task | Role |
|---|---|
rx_task |
Reads I2S samples, runs Goertzel, feeds bits to bitsync. On a DATA frame, prints [RX] <message> and emits an ACK. On an ACK frame, gives s_ack_sem to unblock tx_task. |
tx_task |
Waits for a line on stdin (USB serial), encodes and modulates it, transmits. Retries up to ARQ_MAX_RETRIES times with a ARQ_TIMEOUT_MS timeout. |
Pin wiring
| Signal | GPIO |
|---|---|
| INMP441 SCK | 14 |
| INMP441 WS | 27 |
| INMP441 SD | 16 |
| MAX98357A BCLK | 19 |
| MAX98357A LRC | 32 |
| MAX98357A DIN | 21 |
INMP441 powered at 3.3 V, MAX98357A at 5 V, common GND.
Build and flash
cd firmware
idf.py build flash monitorWeb interface to send and receive messages over USB serial.
cd webapp
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python app.pyOpen http://localhost:5000, select the serial port of the target ESP32, connect, then type and send messages.
| File | Role |
|---|---|
config.py |
Shared signal parameters |
bindings.py |
ctypes declarations, loads libairmodem_lib.so and exposes C functions |
simulate.py |
End-to-end test, encode, BFSK modulate, AWGN channel, demodulate, decode |
cd simulation
python3 simulate.py --snr 6 --wav demo.wavEncodes a message, applies AWGN noise at the given SNR, decodes and verifies.
| Qty | Part | Role |
|---|---|---|
| 1 | ESP32 DevKit (WROOM-32) | MCU, two I2S peripherals, FreeRTOS |
| 1 | INMP441 | I2S MEMS microphone (RX) |
| 1 | MAX98357A breakout | I2S DAC + 3 W class-D amplifier (TX) |
| 1 | Speaker 4 ohm / 3 W | Acoustic emission |
| 1 | Perfboard and soldering stuff | Wiring |

