main
TylerK Replace discrete-analog board with MCU/IC redesign: STM32G031 P&O MPPT + CC/CV firmware, LM5175 buck-boost, INA180 curr… e48ba30 1mo ago
/*
 * board_config.h -- pin mapping, sensor scaling and analog reference math
 * for the MPPT power system board (STM32G031F8Px), derived directly from
 * the schematic (mppt-mcu-src/generator-scripts/build.py). Every constant
 * here traces back to a real resistor/shunt/gain value on the board -- if a
 * resistor value changes on a future board rev, this is the one file that
 * needs updating.
 *
 * Pin map (STM32G031F8Px, TSSOP-20):
 *   PA0  - ADC1_IN0  : VBATT_SENSE   (battery voltage divider)
 *   PA1  - ADC1_IN1  : IIN_SENSE     (INA180 output, panel/input current)
 *   PA2  - ADC1_IN2  : ICHG_SENSE    (INA180 output, battery charge current)
 *   PA4  - TIM14_CH1 : MCU_MPPT_REF PWM (RC-filtered into LM5175 FB summing node)
 *   PA6  - GPIO out  : LED1 (green, charge status) via R23
 *   PA7  - GPIO out  : LED2 (red, fault status) via R24
 *   PA8/PB0/PB1/PB2  : MPPT_PGOOD (digital in, LM5175 stage power-good) -- pick PA8
 *   PB3/PB4/PB5/PB6  : BIAS_PGOOD (digital in, housekeeping bias power-good) -- pick PB3
 *   PA9  - USART1_TX : UART_TX (telemetry, 115200 8N1)
 *   PA10 - USART1_RX : UART_RX
 *   PA13 - SWDIO, PA14 - SWCLK : SWD programming header J5
 */
#ifndef BOARD_CONFIG_H
#define BOARD_CONFIG_H

#include <stdint.h>

/* ---------------------------------------------------------------------
 * ADC scaling
 *
 * VBATT_SENSE: R19=46.4k (top, to VBATT), R20=10k (bottom, to GND).
 *   V_adc = VBATT * R20/(R19+R20) = VBATT * 0.17730
 *   VBATT = V_adc / 0.17730 = V_adc * 5.6403
 *
 * IIN_SENSE: RS1=0.01ohm shunt, U5=INA180A1 (fixed gain 20 V/V).
 *   V_adc = Iin * RS1 * 20 = Iin * 0.20
 *   Iin = V_adc / 0.20 = V_adc * 5.0
 *
 * ICHG_SENSE: RS2=0.02ohm shunt, U6=INA180A1 (fixed gain 20 V/V).
 *   V_adc = Ichg * RS2 * 20 = Ichg * 0.40
 *   Ichg = V_adc / 0.40 = V_adc * 2.5
 * ------------------------------------------------------------------- */
#define ADC_VREF_V              3.3f
#define ADC_FULL_SCALE          4095.0f   /* 12-bit ADC */

#define VBATT_DIVIDER_RATIO     (10.0f / (46.4f + 10.0f))   /* R20/(R19+R20) */
#define IIN_SHUNT_OHM           0.01f
#define IIN_GAIN_V_PER_V        20.0f
#define ICHG_SHUNT_OHM          0.02f
#define ICHG_GAIN_V_PER_V       20.0f

static inline float adc_counts_to_volts(uint32_t counts) {
    return ((float)counts / ADC_FULL_SCALE) * ADC_VREF_V;
}

static inline float vbatt_from_adc(uint32_t counts) {
    return adc_counts_to_volts(counts) / VBATT_DIVIDER_RATIO;
}

static inline float iin_from_adc(uint32_t counts) {
    return adc_counts_to_volts(counts) / (IIN_SHUNT_OHM * IIN_GAIN_V_PER_V);
}

static inline float ichg_from_adc(uint32_t counts) {
    return adc_counts_to_volts(counts) / (ICHG_SHUNT_OHM * ICHG_GAIN_V_PER_V);
}

/* ---------------------------------------------------------------------
 * LM5175 FB summing-node transfer function
 *
 * Three resistors meet at U4.FB: R13 (from VBATT), R14 (to GND), R15 (from
 * MCU_MPPT_REF, the RC-filtered PWM output on PA4). The LM5175 regulates
 * VBATT until FB sits at its internal reference VFB (1.0V typical per the
 * LM5175 datasheet). Since the FB pin draws ~0 input current, KCL at the
 * node gives:
 *
 *   VBATT = VFB*(1 + R13/R14 + R13/R15) - (R13/R15) * V_MCU_REF
 *
 * R13=120k, R14=10k, R15=68k:
 *   ceiling (V_MCU_REF=0V)   : VBATT = 1.0*(1+12+1.7647)      = 14.76V
 *   floor   (V_MCU_REF=3.3V) : VBATT = 14.76 - 1.7647*3.3     =  8.94V
 *
 * This brackets the 14.2V CV target near the top of the range (reached at
 * V_MCU_REF ~= 0.32V, ~9.6% PWM duty) with room below for CC/MPPT throttle.
 * NOTE: earlier board values (R13=46.4k, R15=10k) gave only a 10.28V
 * ceiling -- physically unable to ever reach 14.2V -- corrected during
 * firmware bring-up; see build.py's comment at the R13 placement.
 * ------------------------------------------------------------------- */
#define LM5175_VFB              1.0f      /* LM5175 FB reference, datasheet typical */
#define FB_R13_OHM              120000.0f
#define FB_R14_OHM              10000.0f
#define FB_R15_OHM              68000.0f

#define FB_CEILING_V   (LM5175_VFB * (1.0f + FB_R13_OHM/FB_R14_OHM + FB_R13_OHM/FB_R15_OHM))
#define FB_SLOPE_V_PER_V        (FB_R13_OHM / FB_R15_OHM)   /* dVBATT/dV_MCU_REF, negative slope magnitude */
#define FB_FLOOR_V     (FB_CEILING_V - FB_SLOPE_V_PER_V * ADC_VREF_V)

/* Convert a desired VBATT setpoint into the V_MCU_REF the PWM+RC filter must
 * produce, clamped to what the network can actually reach. */
static inline float vbatt_setpoint_to_mcu_ref(float vbatt_setpoint) {
    if (vbatt_setpoint > FB_CEILING_V) vbatt_setpoint = FB_CEILING_V;
    if (vbatt_setpoint < FB_FLOOR_V)   vbatt_setpoint = FB_FLOOR_V;
    return (FB_CEILING_V - vbatt_setpoint) / FB_SLOPE_V_PER_V;
}

/* PWM duty (0.0-1.0) that makes the RC filter's DC average equal V_MCU_REF,
 * assuming the PA4 GPIO swings rail-to-rail 0..VDD. */
static inline float mcu_ref_to_pwm_duty(float mcu_ref_v) {
    float duty = mcu_ref_v / ADC_VREF_V;
    if (duty < 0.0f) duty = 0.0f;
    if (duty > 1.0f) duty = 1.0f;
    return duty;
}

/* ---------------------------------------------------------------------
 * Battery charge profile -- 12V SLA/AGM, matches the old discrete-analog
 * board's setpoints for a direct comparison.
 * ------------------------------------------------------------------- */
#define BATT_CV_ABSORPTION_V     14.2f
#define BATT_CV_FLOAT_V          13.6f
#define BATT_CC_BULK_LIMIT_A      2.0f    /* charge current ceiling, bulk phase */
#define BATT_ABSORPTION_TAPER_A   0.10f   /* current below which we call it "done" */
#define BATT_ABSORPTION_MIN_MS  600000UL  /* minimum 10 min in absorption before float */

/* Panel/input-side safety ceiling -- RS1/Q2-Q5 sized for ~5A continuous
 * (see 01-architecture-analysis.md Section 6); this is the hard backoff
 * limit, not the MPPT operating point. */
#define PANEL_IIN_MAX_A           5.0f

#endif /* BOARD_CONFIG_H */