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
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
#include "adc_sense.h"
#include "board_config.h"
#include "main.h" /* CubeMX-generated: hadc1 handle, GPIO pin/port defines */
#include <math.h>
#define ADC_FILTER_ALPHA 0.25f
static float ema(float prev, float sample, float alpha) {
return prev + alpha * (sample - prev);
}
void adc_sense_init(adc_filter_state_t *filt) {
filt->vbatt_f = 0.0f;
filt->iin_f = 0.0f;
filt->ichg_f = 0.0f;
filt->initialized = 0;
}
/* Reads ADC1's 3-channel scan sequence (configured in CubeMX as
* PA0=rank1, PA1=rank2, PA2=rank3) and the two PGOOD GPIOs. */
void adc_sense_read(adc_filter_state_t *filt, sensor_frame_t *out) {
uint32_t raw_vbatt = 0, raw_iin = 0, raw_ichg = 0;
int ok = 1;
/* A conversion that does not complete must NOT be reported as a reading
* of zero. Zero volts on VBATT_SENSE is a perfectly plausible-looking
* number that happens to mean "battery empty, charge as hard as you
* can" -- the most dangerous possible interpretation of a dead sensor.
* Propagate NaN instead, which charge_control's plausibility check
* rejects, and the charger faults rather than acting on a fiction. */
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) raw_vbatt = HAL_ADC_GetValue(&hadc1); else ok = 0;
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) raw_iin = HAL_ADC_GetValue(&hadc1); else ok = 0;
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) raw_ichg = HAL_ADC_GetValue(&hadc1); else ok = 0;
HAL_ADC_Stop(&hadc1);
if (!ok) {
out->vbatt = out->iin = out->ichg = (float)NAN;
out->iin_fast = out->ichg_fast = (float)NAN;
out->mppt_pgood = 0;
out->bias_pgood = 0;
filt->initialized = 0; /* re-seed the filter when readings return */
return;
}
float vbatt = vbatt_from_adc(raw_vbatt);
float iin = iin_from_adc(raw_iin);
float ichg = ichg_from_adc(raw_ichg);
if (!filt->initialized) {
filt->vbatt_f = vbatt;
filt->iin_f = iin;
filt->ichg_f = ichg;
filt->initialized = 1;
} else {
filt->vbatt_f = ema(filt->vbatt_f, vbatt, ADC_FILTER_ALPHA);
filt->iin_f = ema(filt->iin_f, iin, ADC_FILTER_ALPHA);
filt->ichg_f = ema(filt->ichg_f, ichg, ADC_FILTER_ALPHA);
}
out->vbatt = filt->vbatt_f;
out->iin = filt->iin_f;
out->ichg = filt->ichg_f;
out->iin_fast = iin; /* unfiltered, for the protection limits */
out->ichg_fast = ichg;
out->mppt_pgood = (HAL_GPIO_ReadPin(MPPT_PGOOD_GPIO_Port, MPPT_PGOOD_Pin) == GPIO_PIN_SET) ? 1 : 0;
out->bias_pgood = (HAL_GPIO_ReadPin(BIAS_PGOOD_GPIO_Port, BIAS_PGOOD_Pin) == GPIO_PIN_SET) ? 1 : 0;
}