/* * battery_adc.h — DMA-based battery voltage/current ADC driver (Issue #533) * * Hardware: * ADC3 channel IN11 (PC1) — Vbat through 10kΩ/1kΩ divider (11:1 ratio) * ADC3 channel IN13 (PC3) — Ibat via shunt amplifier (ADC_IBAT_SCALE=115) * DMA2 Stream0 Channel2 — ADC3 → memory circular (8-word buffer) * USART1 (jlink) — telemetry to Jetson via JLINK_TLM_BATTERY (0x82) * * HOW IT WORKS: * 1. ADC3 runs in continuous scan mode, alternating IN11 (Vbat) and IN13 (Ibat) * at APB2/8 clock (≈ 13.5 MHz ADC clock on STM32F7 @ 216 MHz). * 480-cycle sampling per channel → ~35 µs per scan pair, ~28 kHz scan rate. * * 2. DMA2_Stream0 (circular) fills an 8-word buffer: 4 Vbat samples followed * by 4 Ibat samples per DMA half-complete cycle. Interleaved layout: * [vbat0, ibat0, vbat1, ibat1, vbat2, ibat2, vbat3, ibat3] * * 3. battery_adc_tick() (call from main loop, 10–100 Hz) averages the 4 Vbat * and 4 Ibat raw values (4× hardware oversampling), then feeds a 1st-order * IIR low-pass filter: * filtered += (raw - filtered) >> BATTERY_ADC_LPF_SHIFT * With LPF_SHIFT=3 (α = 1/8) and 100 Hz tick rate, cutoff ≈ 4 Hz. * * 4. Calibration scales and offsets the filtered output: * vbat_mv = filtered_raw * (VBAT_AREF_MV * VBAT_SCALE_NUM) / 4096 * + cal.vbat_offset_mv * ibat_ma = filtered_raw * ADC_IBAT_SCALE_MA_PER_COUNT / 1000 * + cal.ibat_offset_ma * User calibration adjusts cal.vbat_offset_mv to null out divider tolerance. * * 5. battery_adc_publish() sends JLINK_TLM_BATTERY (0x82) to Jetson at 1 Hz. * * 6. battery_adc_check_pm() monitors for low voltage. If Vbat drops below * BATTERY_ADC_LOW_MV for BATTERY_ADC_LOW_HOLD_MS, calls * power_mgmt_notify_battery(vbat_mv) which requests sleep (Issue #467). * * Interrupt safety: * s_dma_buf is written by DMA hardware; battery_adc_tick() reads it with a * brief __disable_irq() snapshot to prevent torn reads of the 16-bit words. * All other state is private to the main-loop call path. */ #ifndef BATTERY_ADC_H #define BATTERY_ADC_H #include #include /* ---- Low-pass filter ---- */ /* IIR shift: α = 1/8 → cutoff ≈ 4 Hz at 100 Hz tick rate */ #define BATTERY_ADC_LPF_SHIFT 3u /* ---- Low-voltage thresholds (mV) ---- */ /* 3S LiPo: 9.0 V cell floor ×3 = 9900 mV full, 9000 mV absolute minimum */ #define BATTERY_ADC_LOW_MV 10200u /* ≈ 15% SoC — warn / throttle */ #define BATTERY_ADC_CRITICAL_MV 9600u /* ≈ 5% SoC — request sleep (#467) */ #define BATTERY_ADC_LOW_HOLD_MS 5000u /* must stay below this long to act */ /* 4S LiPo equivalents (auto-detected when Vbat ≥ 13 V at boot) */ #define BATTERY_ADC_LOW_MV_4S 13600u #define BATTERY_ADC_CRITICAL_MV_4S 12800u /* ---- Telemetry rate ---- */ #define BATTERY_ADC_PUBLISH_HZ 1u /* JLINK_TLM_BATTERY TX rate */ /* ---- Calibration struct ---- */ typedef struct { int16_t vbat_offset_mv; /* additive offset after scale (mV, ±500 clamp) */ int16_t ibat_offset_ma; /* additive offset for current (mA, ±200 clamp) */ uint16_t vbat_scale_num; /* divider numerator override; 0 = use VBAT_SCALE_NUM */ uint16_t vbat_scale_den; /* divider denominator override; 0 = use 1 */ } battery_adc_cal_t; /* ---- API ---- */ /* * battery_adc_init() — configure ADC3 continuous-scan + DMA2_Stream0. * Must be called after __HAL_RCC_ADC3_CLK_ENABLE / GPIO clock enables. * Call once during system init, before battery_adc_tick(). */ void battery_adc_init(void); /* * battery_adc_tick(now_ms) — average DMA buffer, apply IIR LPF, update state. * Call from main loop at 10–100 Hz. Non-blocking (<5 µs). */ void battery_adc_tick(uint32_t now_ms); /* * battery_adc_get_voltage_mv() — calibrated, LPF-filtered Vbat in mV. * Returns 0 if ADC not initialised. */ uint32_t battery_adc_get_voltage_mv(void); /* * battery_adc_get_current_ma() — calibrated, LPF-filtered Ibat in mA. * Positive = discharging (load current). Returns 0 if not initialised. */ int32_t battery_adc_get_current_ma(void); /* * battery_adc_get_raw_voltage_mv() — unfiltered last-tick average (mV). * Useful for calibration; use filtered version for control logic. */ uint32_t battery_adc_get_raw_voltage_mv(void); /* * battery_adc_calibrate(cal) — store calibration constants. * Applies immediately to subsequent battery_adc_tick() calls. * Pass NULL to reset to defaults (0 offset, default scale). */ void battery_adc_calibrate(const battery_adc_cal_t *cal); /* * battery_adc_get_calibration(out_cal) — read back current calibration. */ void battery_adc_get_calibration(battery_adc_cal_t *out_cal); /* * battery_adc_publish(now_ms) — send JLINK_TLM_BATTERY (0x82) frame. * Rate-limited to BATTERY_ADC_PUBLISH_HZ; safe to call every main loop tick. */ void battery_adc_publish(uint32_t now_ms); /* * battery_adc_check_pm(now_ms) — evaluate low-voltage thresholds. * Calls power_mgmt_notify_battery() on sustained critical voltage. * Call from main loop after battery_adc_tick(). */ void battery_adc_check_pm(uint32_t now_ms); /* * battery_adc_is_low() — true if Vbat below BATTERY_ADC_LOW_MV (warn level). */ bool battery_adc_is_low(void); /* * battery_adc_is_critical() — true if Vbat below BATTERY_ADC_CRITICAL_MV. */ bool battery_adc_is_critical(void); #endif /* BATTERY_ADC_H */