add command rasp to esp32
This commit is contained in:
parent
c19d9a7cf4
commit
28d5bd44e0
@ -30,11 +30,6 @@ build_flags =
|
||||
-DHW_SERIAL_BAUD=921600
|
||||
; Idle timeout before the eyes fall back to the default animation (ms)
|
||||
-DHW_HEARTBEAT_TIMEOUT_MS=5000
|
||||
; Hardware UART2 pins used to talk to the Raspberry Pi.
|
||||
; The OLED eyes already claim GPIO 16/17 (UART2 default pins),
|
||||
; so Serial2 is remapped to these two free pins instead.
|
||||
-DHW_UART_RX_PIN=27
|
||||
-DHW_UART_TX_PIN=13
|
||||
build_unflags =
|
||||
-std=gnu++11
|
||||
|
||||
|
||||
@ -130,8 +130,7 @@ async function main(): Promise<void> {
|
||||
port.open((err) => (err ? reject(err) : resolve()));
|
||||
});
|
||||
|
||||
let ready = false;
|
||||
const readyWaiters: Array<() => void> = [];
|
||||
const pongWaiters: Array<() => void> = [];
|
||||
|
||||
const finished = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
@ -151,9 +150,13 @@ async function main(): Promise<void> {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
if (line === 'PONG') {
|
||||
while (pongWaiters.length) pongWaiters.shift()!();
|
||||
continue;
|
||||
}
|
||||
if (line === 'READY') {
|
||||
ready = true;
|
||||
while (readyWaiters.length) readyWaiters.shift()!();
|
||||
// Firmware just booted (USB DTR reset). Ignore — the
|
||||
// PING/PONG handshake is our real readiness signal.
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('ERR ')) {
|
||||
@ -168,19 +171,26 @@ async function main(): Promise<void> {
|
||||
port.on('error', reject);
|
||||
});
|
||||
|
||||
// Wait for READY so we don't send PLAY into the bootloader.
|
||||
// PING/PONG handshake — validates the link in both directions
|
||||
// regardless of whether the firmware was recently rebooted or not.
|
||||
console.log('→ PING');
|
||||
port.write('PING\n');
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
if (ready) return resolve();
|
||||
const timer = setTimeout(
|
||||
() => reject(new Error('timeout waiting for READY from firmware')),
|
||||
5000,
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
'no PONG from firmware — check TX/RX wiring (cross!), baud rate, and that the ESP32 is powered',
|
||||
),
|
||||
),
|
||||
3000,
|
||||
);
|
||||
readyWaiters.push(() => {
|
||||
pongWaiters.push(() => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
console.log('→ PONG received');
|
||||
|
||||
console.log(`→ PLAY ${pcm.length} bytes`);
|
||||
port.write(`PLAY ${pcm.length}\n`);
|
||||
|
||||
@ -78,8 +78,7 @@ async function main(): Promise<void> {
|
||||
let remaining = 0;
|
||||
const chunks: Buffer[] = [];
|
||||
let lineBuf = '';
|
||||
let ready = false;
|
||||
const readyWaiters: Array<() => void> = [];
|
||||
const pongWaiters: Array<() => void> = [];
|
||||
|
||||
const finished = new Promise<Buffer>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
@ -122,9 +121,12 @@ async function main(): Promise<void> {
|
||||
clearTimeout(timeout);
|
||||
const pcm = Buffer.concat(chunks);
|
||||
resolve(pcm);
|
||||
} else if (line === 'PONG') {
|
||||
while (pongWaiters.length) pongWaiters.shift()!();
|
||||
} else if (line === 'READY') {
|
||||
ready = true;
|
||||
while (readyWaiters.length) readyWaiters.shift()!();
|
||||
// Firmware just booted. Ignore, the PING/PONG handshake
|
||||
// below handles both "just booted" and "been running for
|
||||
// hours" alike.
|
||||
} else if (line.startsWith('LOG ')) {
|
||||
console.log(`[esp] ${line.slice(4)}`);
|
||||
} else if (line.startsWith('ERR ')) {
|
||||
@ -139,20 +141,28 @@ async function main(): Promise<void> {
|
||||
port.on('error', reject);
|
||||
});
|
||||
|
||||
// The ESP32 resets on port open (DTR/RTS). Wait until it prints
|
||||
// READY so we don't send commands into the bootloader.
|
||||
// PING/PONG handshake. Works whether the ESP32 just booted
|
||||
// (USB/DTR reset) or has been running for hours (UART wiring
|
||||
// never resets anything). Also validates that TX and RX are
|
||||
// correctly crossed — a one-way wiring error would time out here.
|
||||
console.log('→ PING');
|
||||
port.write('PING\n');
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
if (ready) return resolve();
|
||||
const timer = setTimeout(
|
||||
() => reject(new Error('timeout waiting for READY from firmware')),
|
||||
5000,
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
'no PONG from firmware — check TX/RX wiring (cross!), baud rate, and that the ESP32 is powered',
|
||||
),
|
||||
),
|
||||
3000,
|
||||
);
|
||||
readyWaiters.push(() => {
|
||||
pongWaiters.push(() => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
console.log('→ PONG received');
|
||||
|
||||
console.log(`→ REC ${durationMs} ms — speak now!`);
|
||||
port.write(`REC ${durationMs}\n`);
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
// Ti-Pote — Minimal audio bring-up firmware (ESP32-WROOM-32)
|
||||
//
|
||||
// GOAL: prove the I2S audio chain (INMP441 + MAX98357A) end to end
|
||||
// with nothing else in the loop — no Pi, no OLED, no protocol frames.
|
||||
// The ESP32 is plugged into a computer via USB and the host runs
|
||||
// two tiny scripts:
|
||||
// GOAL: prove the I2S audio chain (INMP441 + MAX98357A) end to end.
|
||||
// The command stream lives on Serial2 (hardware UART2, pins RX=27
|
||||
// TX=13) which is wired to the Raspberry Pi's UART0 (/dev/serial0).
|
||||
// The USB Serial port is kept only for boot-time diagnostics — all
|
||||
// the real traffic goes over the UART to the Pi.
|
||||
//
|
||||
// scripts/esp-record.mjs <file.raw> <duration_ms>
|
||||
// scripts/esp-play.mjs <file.raw>
|
||||
// On the host side, the same two scripts we used with the USB link
|
||||
// work unchanged — just pass `ESP_PORT=/dev/serial0`:
|
||||
//
|
||||
// Protocol over USB Serial (921600 baud, line-based for commands,
|
||||
// raw bytes for audio):
|
||||
// ESP_PORT=/dev/serial0 pnpm esp:record out.wav 3000
|
||||
// ESP_PORT=/dev/serial0 pnpm esp:play out.wav
|
||||
//
|
||||
// Protocol (same as before, 921600 baud, line-based for commands,
|
||||
// raw bytes for audio payload):
|
||||
//
|
||||
// host → esp32
|
||||
// "PING\n" ping
|
||||
@ -37,6 +41,21 @@
|
||||
#include <driver/i2s.h>
|
||||
#include <string.h>
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// Comms config — UART2 to the Raspberry Pi
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
// Hardware UART2 remapped to pins that don't clash with anything
|
||||
// else on the devkit. TX = GPIO 13, RX = GPIO 27.
|
||||
static constexpr int HW_UART_RX_PIN = 27;
|
||||
static constexpr int HW_UART_TX_PIN = 13;
|
||||
static constexpr long HW_UART_BAUD = 921600;
|
||||
|
||||
// HW_COMM is the Stream that carries the command/audio protocol.
|
||||
// Changing this single #define lets us swap between USB (Serial)
|
||||
// and the Pi-facing UART (Serial2).
|
||||
#define HW_COMM Serial2
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// Audio config
|
||||
// ──────────────────────────────────────────────────────────
|
||||
@ -66,13 +85,13 @@ static char g_line[64];
|
||||
static size_t g_lineLen = 0;
|
||||
|
||||
static void sendLog(const char* msg) {
|
||||
Serial.print("LOG ");
|
||||
Serial.println(msg);
|
||||
HW_COMM.print("LOG ");
|
||||
HW_COMM.println(msg);
|
||||
}
|
||||
|
||||
static void sendErr(const char* msg) {
|
||||
Serial.print("ERR ");
|
||||
Serial.println(msg);
|
||||
HW_COMM.print("ERR ");
|
||||
HW_COMM.println(msg);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
@ -162,8 +181,8 @@ static void handleRec(uint32_t durationMs) {
|
||||
const uint32_t totalSamples = (SAMPLE_RATE * durationMs) / 1000;
|
||||
const uint32_t totalBytes = totalSamples * sizeof(int16_t);
|
||||
|
||||
Serial.print("BEGIN ");
|
||||
Serial.println(totalBytes);
|
||||
HW_COMM.print("BEGIN ");
|
||||
HW_COMM.println(totalBytes);
|
||||
|
||||
// Flush whatever old noise is in the mic DMA first.
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
@ -174,13 +193,13 @@ static void handleRec(uint32_t durationMs) {
|
||||
if (want > OUT_S16_SAMPLES) want = OUT_S16_SAMPLES;
|
||||
const size_t got = micReadMono(g_micMono, want);
|
||||
if (got == 0) continue;
|
||||
Serial.write(reinterpret_cast<const uint8_t*>(g_micMono),
|
||||
got * sizeof(int16_t));
|
||||
HW_COMM.write(reinterpret_cast<const uint8_t*>(g_micMono),
|
||||
got * sizeof(int16_t));
|
||||
sent += got;
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
Serial.println("END");
|
||||
HW_COMM.println();
|
||||
HW_COMM.println("END");
|
||||
}
|
||||
|
||||
static void handlePlay(uint32_t totalBytes) {
|
||||
@ -188,9 +207,9 @@ static void handlePlay(uint32_t totalBytes) {
|
||||
// with a pop.
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
|
||||
// Give Serial.readBytes a generous timeout so a jittery host
|
||||
// doesn't abort us mid-playback.
|
||||
Serial.setTimeout(2000);
|
||||
// Give readBytes a generous timeout so a jittery host doesn't
|
||||
// abort us mid-playback.
|
||||
HW_COMM.setTimeout(2000);
|
||||
|
||||
uint32_t remaining = totalBytes;
|
||||
while (remaining > 0) {
|
||||
@ -200,7 +219,7 @@ static void handlePlay(uint32_t totalBytes) {
|
||||
if (want & 1) want -= 1;
|
||||
if (want == 0) want = 2;
|
||||
|
||||
const size_t got = Serial.readBytes(g_spkInBuf, want);
|
||||
const size_t got = HW_COMM.readBytes(g_spkInBuf, want);
|
||||
if (got == 0) {
|
||||
sendErr("PLAY read timeout");
|
||||
return;
|
||||
@ -213,12 +232,12 @@ static void handlePlay(uint32_t totalBytes) {
|
||||
// Let the last frames actually reach the speaker, then clear.
|
||||
delay(50);
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
Serial.println("OK");
|
||||
HW_COMM.println("OK");
|
||||
}
|
||||
|
||||
static void handleLine(const char* line) {
|
||||
if (strcmp(line, "PING") == 0) {
|
||||
Serial.println("PONG");
|
||||
HW_COMM.println("PONG");
|
||||
return;
|
||||
}
|
||||
if (strncmp(line, "REC ", 4) == 0) {
|
||||
@ -244,25 +263,35 @@ static void handleLine(const char* line) {
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
void setup() {
|
||||
// Bump the UART RX buffer WAY above the 256-byte default so we
|
||||
// USB Serial is kept as a *boot-time logger only*. It gives
|
||||
// you something to look at via `pio device monitor` when the
|
||||
// board is plugged into a laptop, without interfering with
|
||||
// the Pi link on Serial2.
|
||||
Serial.begin(115200);
|
||||
Serial.println("[boot] USB logger up, real comms on Serial2");
|
||||
|
||||
// Bump the UART2 RX buffer WAY above the 256-byte default so we
|
||||
// can absorb a full PLAY payload (up to a few tens of KB) without
|
||||
// losing bytes if the host floods us.
|
||||
Serial.setRxBufferSize(16 * 1024);
|
||||
Serial.begin(921600);
|
||||
HW_COMM.setRxBufferSize(16 * 1024);
|
||||
HW_COMM.begin(HW_UART_BAUD, SERIAL_8N1, HW_UART_RX_PIN, HW_UART_TX_PIN);
|
||||
delay(50);
|
||||
|
||||
if (!audioBegin()) {
|
||||
sendErr("I2S init failed");
|
||||
Serial.println("[boot] I2S init FAILED");
|
||||
} else {
|
||||
sendLog("I2S ready");
|
||||
Serial.println("[boot] I2S ready");
|
||||
}
|
||||
|
||||
Serial.println("READY");
|
||||
HW_COMM.println("READY");
|
||||
Serial.println("[boot] READY sent on Serial2");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
while (Serial.available() > 0) {
|
||||
const int c = Serial.read();
|
||||
while (HW_COMM.available() > 0) {
|
||||
const int c = HW_COMM.read();
|
||||
if (c < 0) break;
|
||||
if (c == '\r') continue;
|
||||
if (c == '\n') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user