main
TylerK Update 20 file(s) fb8969c 1mo ago
/*
 * charge_control.h -- CC/CV battery charge state machine, P&O MPPT, and the
 * arbitration between them that produces the single VBATT reference voltage
 * the analog FB summing network is commanded to.
 *
 * Control architecture: the LM5175 stage has exactly ONE analog control
 * input available to firmware -- the FB summing node, which sets the
 * regulated OUTPUT (battery bus) voltage. There is no direct handle on
 * input current or input voltage. So every constraint the system needs to
 * enforce (charge current limit, battery voltage ceiling, MPPT power
 * tracking) is expressed as "what VBATT setpoint would this constraint
 * want", and the arbitrator commands the MOST RESTRICTIVE (lowest) of
 * those candidate setpoints -- asking for a lower output voltage is always
 * the "back off" direction, whichever constraint is currently binding.
 *
 * MPPT note: the board's ADC set covers VBATT, IIN (panel current) and
 * ICHG (charge current), but NOT VIN (panel voltage) directly -- true P*V
 * power tracking on the input side isn't available without adding a
 * sense divider on a future board rev. Since the battery bus voltage is
 * slow-moving relative to a single P&O perturbation-and-settle interval,
 * maximizing charge current (ICHG) into that near-constant bus is an
 * accepted, commonly-used proxy for maximizing extracted panel power, and
 * is what's implemented here. IIN is still used directly for the hard
 * panel-side overcurrent backoff (PANEL_IIN_MAX_A).
 */
#ifndef CHARGE_CONTROL_H
#define CHARGE_CONTROL_H

#include <stdint.h>

typedef enum {
    CHG_STATE_BULK = 0,     /* constant current, MPPT actively perturbing */
    CHG_STATE_ABSORPTION,   /* constant voltage at BATT_CV_ABSORPTION_V */
    CHG_STATE_FLOAT,        /* constant voltage at BATT_CV_FLOAT_V, maintenance */
    CHG_STATE_FAULT,        /* PGOOD lost or a sensor out of range -- output disabled */
} charge_state_t;

typedef struct {
    float vbatt;
    float iin;          /* filtered -- smooth, for the MPPT gradient */
    float ichg;         /* filtered -- smooth, for the MPPT gradient */
    /* Unfiltered copies of the same two currents. Protection limits run on
     * these: the exponential filter that makes the MPPT gradient readable
     * also delays it, and a limiter acting on a delayed signal permits a
     * real overshoot proportional to that delay. Optimise on the smooth
     * signal, protect on the fast one. */
    float iin_fast;
    float ichg_fast;
    uint8_t mppt_pgood;   /* 1 = LM5175 stage reports power-good */
    uint8_t bias_pgood;   /* 1 = housekeeping bias reports power-good */
} sensor_frame_t;

typedef struct {
    charge_state_t state;
    uint32_t state_entry_ms;

    /* MPPT (P&O) sub-controller state */
    float mppt_ref_v;        /* MPPT's own candidate VBATT setpoint */
    float mppt_step_v;       /* perturbation step size */
    int8_t mppt_dir;         /* +1 or -1, current perturbation direction */
    float mppt_last_ichg;
    uint32_t mppt_last_perturb_ms;

    uint8_t mppt_armed;      /* 1 once a baseline ICHG has been taken */
    float mppt_ichg_sum;     /* ICHG accumulated across the current window */
    uint16_t mppt_ichg_n;    /* samples in that accumulation */

    /* incremental current limiters, active in every charging state */
    float cc_ref_v;          /* charge-current limiter's candidate setpoint */
    float iin_ref_v;         /* panel-current limiter's candidate setpoint */

    /* absorption taper must be sustained, so it needs its own timer */
    uint32_t taper_start_ms;
    uint8_t taper_running;

    /* fault debounce counters */
    uint8_t fault_count;
    uint8_t ok_count;
    const char *fault_reason;

    /* the final arbitrated output, and which constraint won */
    float commanded_vbatt;
    const char *limiting_reason;
} charge_control_t;

void charge_control_init(charge_control_t *cc, uint32_t now_ms);

/* Run one control-loop iteration. Call at a steady rate (recommended
 * 20-50ms); the MPPT perturbation itself is internally rate-limited to a
 * slower cadence (see MPPT_PERTURB_INTERVAL_MS) so it doesn't chase the
 * RC filter's own settling time. Returns the commanded VBATT setpoint
 * (also available as cc->commanded_vbatt). */
float charge_control_update(charge_control_t *cc, const sensor_frame_t *s, uint32_t now_ms);

#endif /* CHARGE_CONTROL_H */