#ifndef AUDIO_H #define AUDIO_H #include #include /* * audio.h — I2S audio output driver (Issue #143) * * Hardware: SPI3 repurposed as I2S3 master TX (blackbox flash not used * on balance bot). Supports MAX98357A (I2S class-D amp) and PCM5102A * (I2S DAC + external amp) — both use standard Philips I2S. * * Pin assignment (SPI3 / I2S3, defined in config.h): * PC10 I2S3_CK (BCLK) AF6 * PA15 I2S3_WS (LRCLK) AF6 * PB5 I2S3_SD (DIN) AF6 * PC5 AUDIO_MUTE (GPIO) active-high = enabled; low = muted/shutdown * * PLLI2S: N=192, R=2 → 96 MHz I2S clock → 22058 Hz (< 0.04% from 22050) * DMA1 Stream7 Channel0 (SPI3_TX), circular, double-buffer ping-pong. * * Mixer priority (highest to lowest): * 1. PCM audio chunks from Jetson (via JLINK_CMD_AUDIO, written to FIFO) * 2. Notification tones (queued by audio_play_tone) * 3. Silence * * Volume applies to all sources via integer sample scaling (0–100). */ /* Maximum int16_t samples per JLINK_CMD_AUDIO frame (252-byte payload / 2) */ #define AUDIO_CHUNK_MAX_SAMPLES 126u /* Pre-defined notification tones */ typedef enum { AUDIO_TONE_BEEP_SHORT = 0, /* 880 Hz, 100 ms — acknowledge / UI feedback */ AUDIO_TONE_BEEP_LONG = 1, /* 880 Hz, 500 ms — generic warning */ AUDIO_TONE_STARTUP = 2, /* C5→E5→G5 arpeggio (3 × 120 ms) */ AUDIO_TONE_ARM = 3, /* 880 Hz→1047 Hz two-beep ascending */ AUDIO_TONE_DISARM = 4, /* 880 Hz→659 Hz two-beep descending */ AUDIO_TONE_FAULT = 5, /* 200 Hz buzz, 500 ms — tilt/safety fault */ AUDIO_TONE_COUNT } AudioTone; /* * audio_init() * * Configure PLLI2S, GPIO, DMA1 Stream7, and SPI3/I2S3. * Pre-fills DMA buffer with silence, starts circular DMA TX, then * unmutes the amp. Call once before safety_init(). */ void audio_init(void); /* * audio_mute(mute) * * Drive AUDIO_MUTE_PIN: false = hardware-muted (SD/XSMT low), * true = active (amp enabled). Does NOT stop DMA; allows instant * un-mute without DMA restart clicks. */ void audio_mute(bool active); /* * audio_set_volume(vol) * * Software volume 0–100. Applied in ISR fill path via integer scaling. * 0 = silence, 100 = full scale (±16384 for square wave, passthrough for PCM). */ void audio_set_volume(uint8_t vol); /* * audio_play_tone(tone) * * Queue a pre-defined notification tone. The tone plays after any tones * already in the queue. Returns false if the tone queue is full (depth 4). * Tones are pre-empted by incoming PCM audio from the Jetson. */ bool audio_play_tone(AudioTone tone); /* * audio_write_pcm(samples, n) * * Write mono 16-bit 22050 Hz PCM samples into the Jetson PCM FIFO. * Called from jlink_process() dispatch on JLINK_CMD_AUDIO (main-loop context). * Returns the number of samples actually accepted (0 if FIFO is full). */ uint16_t audio_write_pcm(const int16_t *samples, uint16_t n); /* * audio_tick(now_ms) * * Advance the tone sequencer state machine. Must be called every 1 ms * from the main loop. Manages step transitions and gap timing; updates * the volatile active-tone parameters read by the ISR fill path. */ void audio_tick(uint32_t now_ms); /* * audio_is_playing() * * Returns true if the DMA is running (always true after audio_init() * unless the amp is hardware-muted or the I2S peripheral has an error). */ bool audio_is_playing(void); #endif /* AUDIO_H */