sl-firmware 1e76cb9fe3 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:27 -05:00

134 lines
5.0 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 JLINK_H
#define JLINK_H
#include <stdint.h>
#include <stdbool.h>
/*
* JLink — Jetson serial binary protocol over USART1 (PB6=TX, PB7=RX).
*
* Issue #120: replaces jetson_cmd ASCII-over-USB-CDC with a dedicated
* hardware UART at 921600 baud using DMA circular RX and IDLE interrupt.
*
* Frame format (both directions):
* [STX=0x02][LEN][CMD][PAYLOAD...][CRC16_hi][CRC16_lo][ETX=0x03]
*
* STX : frame start sentinel (0x02)
* LEN : count of CMD + PAYLOAD bytes (1 + payload_len)
* CMD : command/telemetry type byte
* PAYLOAD: 0..N bytes depending on CMD
* CRC16 : CRC16-XModem over CMD+PAYLOAD (poly 0x1021, init 0), big-endian
* ETX : frame end sentinel (0x03)
*
* Jetson → STM32 commands:
* 0x01 HEARTBEAT — no payload; refreshes heartbeat timer
* 0x02 DRIVE — int16 speed (-1000..+1000), int16 steer (-1000..+1000)
* 0x03 ARM — no payload; request arm (same interlock as CDC 'A')
* 0x04 DISARM — no payload; disarm immediately
* 0x05 PID_SET — float kp, float ki, float kd (12 bytes, IEEE-754 LE)
* 0x06 DFU_ENTER — no payload; request OTA DFU reboot (denied while armed)
* 0x07 ESTOP — no payload; engage emergency stop
*
* STM32 → Jetson telemetry:
* 0x80 STATUS — jlink_tlm_status_t (20 bytes), sent at JLINK_TLM_HZ
*
* Priority: CRSF RC always takes precedence. Jetson steer/speed only applied
* when mode_manager_active() == MODE_AUTONOMOUS (CH6 high). In RC_MANUAL and
* RC_ASSISTED modes the Jetson speed offset and steer are injected via
* mode_manager_set_auto_cmd() and blended per the existing blend ramp.
*
* Heartbeat: if no valid frame arrives within JLINK_HB_TIMEOUT_MS (1000ms),
* jlink_is_active() returns false and the main loop clears the auto command.
*/
/* ---- Frame constants ---- */
#define JLINK_STX 0x02u
#define JLINK_ETX 0x03u
/* ---- Command IDs (Jetson → STM32) ---- */
#define JLINK_CMD_HEARTBEAT 0x01u
#define JLINK_CMD_DRIVE 0x02u
#define JLINK_CMD_ARM 0x03u
#define JLINK_CMD_DISARM 0x04u
#define JLINK_CMD_PID_SET 0x05u
#define JLINK_CMD_DFU_ENTER 0x06u
#define JLINK_CMD_ESTOP 0x07u
#define JLINK_CMD_AUDIO 0x08u /* PCM audio chunk: int16 samples, up to 126 */
/* ---- Telemetry IDs (STM32 → Jetson) ---- */
#define JLINK_TLM_STATUS 0x80u
/* ---- Telemetry STATUS payload (20 bytes, packed) ---- */
typedef struct __attribute__((packed)) {
int16_t pitch_x10; /* pitch degrees ×10 */
int16_t roll_x10; /* roll degrees ×10 */
int16_t yaw_x10; /* yaw degrees ×10 (gyro-integrated) */
int16_t motor_cmd; /* ESC output -1000..+1000 */
uint16_t vbat_mv; /* battery millivolts */
int8_t rssi_dbm; /* CRSF RSSI (dBm, negative) */
uint8_t link_quality; /* CRSF LQ 0-100 */
uint8_t balance_state; /* 0=DISARMED, 1=ARMED, 2=TILT_FAULT */
uint8_t rc_armed; /* crsf_state.armed (1=armed) */
uint8_t mode; /* robot_mode_t: 0=RC_MANUAL,1=ASSISTED,2=AUTONOMOUS */
uint8_t estop; /* EstopSource value */
uint8_t soc_pct; /* state-of-charge 0-100, 255=unknown */
uint8_t fw_major;
uint8_t fw_minor;
uint8_t fw_patch;
} jlink_tlm_status_t; /* 20 bytes */
/* ---- Volatile state (read from main loop) ---- */
typedef struct {
/* Drive command — updated on JLINK_CMD_DRIVE */
volatile int16_t speed; /* -1000..+1000 */
volatile int16_t steer; /* -1000..+1000 */
/* Heartbeat timer — updated on any valid frame */
volatile uint32_t last_rx_ms; /* HAL_GetTick() of last valid frame; 0=none */
/* One-shot request flags — set by parser, cleared by main loop */
volatile uint8_t arm_req;
volatile uint8_t disarm_req;
volatile uint8_t estop_req;
/* PID update — set by parser, cleared by main loop */
volatile uint8_t pid_updated;
volatile float pid_kp;
volatile float pid_ki;
volatile float pid_kd;
/* DFU reboot request — set by parser, cleared by main loop */
volatile uint8_t dfu_req;
} JLinkState;
extern volatile JLinkState jlink_state;
/* ---- API ---- */
/*
* jlink_init() — configure USART1 (PB6=TX, PB7=RX) at 921600 baud with
* DMA2_Stream2_Channel4 circular RX (128-byte buffer) and IDLE interrupt.
* Call once before safety_init().
*/
void jlink_init(void);
/*
* jlink_is_active(now_ms) — returns true if a valid frame arrived within
* JLINK_HB_TIMEOUT_MS. Returns false if no frame ever received.
*/
bool jlink_is_active(uint32_t now_ms);
/*
* jlink_send_telemetry(status) — build and transmit a JLINK_TLM_STATUS frame
* over USART1 TX (blocking, ~0.2ms at 921600). Call at JLINK_TLM_HZ.
*/
void jlink_send_telemetry(const jlink_tlm_status_t *status);
/*
* jlink_process() — drain DMA circular buffer and parse frames.
* Call from main loop every iteration (not ISR). Lightweight: O(bytes_pending).
*/
void jlink_process(void);
#endif /* JLINK_H */