sl-firmware 4827d3cd55 feat(audio): I2S3 audio amplifier driver — Issue #143
Add I2S3/DMA audio output driver for MAX98357A/PCM5102A class-D amps:

- audio_init(): PLLI2S N=192/R=2 → 96 MHz → FS≈22058 Hz (<0.04% error),
  GPIO PC10/PA15/PB5 (AF6), PC5 mute, DMA1_Stream7_Ch0 circular,
  HAL_I2S_Transmit_DMA ping-pong, 441-sample half-buffers (20 ms each)
- Square-wave tone generator (ISR-safe, integer volume scaling 0-100)
- Tone sequencer: STARTUP/ARM/DISARM/FAULT/BEEP sequences via audio_tick()
- PCM FIFO (4096 samples, SPSC ring): receives Jetson audio via JLink
- JLink protocol: JLINK_CMD_AUDIO = 0x08, JLINK_MAX_PAYLOAD 64→252 bytes
  (supports 126 int16 samples/frame = 5.7 ms @22050 Hz)
- main.c: audio_init(), STARTUP tone on boot, ARM/FAULT tones, audio_tick()
- config.h: AUDIO_BCLK/LRCK/DOUT/MUTE pin defines + PLLI2S constants
- test_audio.py: 45 tests, all passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:31:34 -05:00

107 lines
3.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef AUDIO_H
#define AUDIO_H
#include <stdint.h>
#include <stdbool.h>
/*
* 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 (0100).
*/
/* 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 0100. 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 */