board
Solar MPPT Power System
Public Unreviewedby TylerK
A 30 W solar battery charger with regulated 5 V and 3.3 V rails: an STM32G031 running perturb & observe MPPT and a CC/CV charge state machine, steering a TI LM5175 4-switch buck-boost through a single analog control input. Four-layer KiCad board, schematic and PCB generated from Python, with an electrothermal simulation run against the published board file.
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
/*
* 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 */