// ============================================================================= // SaltyBot — Battery Coulomb Counter (Issue #325) // Agent: sl-mechanical | 2026-03-03 // // ADC-BASED COULOMB COUNTING for LiPo battery state-of-charge (SOC) tracking. // Integrates battery current over time via I²C INA219 current sensor on STM32. // Maintains persistent cycle count and flash-stored calibration coefficients. // // OVERVIEW // • Current sensing: INA219 I2C (±3.2A, 10mΩ shunt) // • Integration period: 1 kHz tick → 1 coulomb per 3.6 seconds // • Coulomb budget: Li-Po capacity (mAh) → coulombs (C = mAh / 3.6) // • Cycle tracking: ΔQ / Capacity → depth-of-discharge (DOD) per cycle // • Flash storage: Calibration offsets, cycle count (wear tracking) // // THEORY // Q = ∫ I dt (coulombs) // SOC = 1 - (Discharged_Q / Total_Capacity_Q) // Cycle_Count = sum(DOD) where DOD = ΔQ / Capacity // // ============================================================================= #ifndef BATTERY_COULOMB_H #define BATTERY_COULOMB_H #include #include // ============================================================================= // BATTERY PARAMETERS // ============================================================================= // LiPo battery capacity (mAh) — adjust per robot variant #define BATTERY_CAPACITY_MAH 2200 // mAh (typical 2S-3S pack) #define BATTERY_CAPACITY_Q (BATTERY_CAPACITY_MAH / 3.6f) // coulombs // INA219 Configuration #define INA219_I2C_ADDR 0x40 // A0=GND, A1=GND #define INA219_SHUNT_OHM 0.01f // 10 mΩ shunt #define INA219_MAX_CURRENT_A 3.2f // ±3.2 A range // ADC & Sampling #define COULOMB_SAMPLE_RATE_HZ 1000 // 1 kHz tick from systick #define COULOMB_INTEGRATION_S 1.0f // Integration window (seconds) // Flash Storage (STM32 sector for calibration) #define COULOMB_FLASH_ADDR 0x0800D000 // Last sector (STM32F4) #define COULOMB_FLASH_SIZE 4096 // 1 sector // ============================================================================= // DATA STRUCTURES // ============================================================================= /** * @struct coulomb_state_t * @brief Runtime coulomb counter state (volatile) */ typedef struct { float current_a; // Current ADC reading (amperes) float coulombs_total; // Total coulombs flowed (integration sum) float coulombs_discharged; // Coulombs removed from battery (negative = charging) float soc_percent; // State-of-charge (0–100%) uint32_t tick_count; // Samples integrated since boot bool charging; // True if current < 0 (charging) uint16_t cycle_count; // Battery cycle count (full discharge = 1 cycle) } coulomb_state_t; /** * @struct coulomb_calibration_t * @brief Flash-stored calibration data (persistent across reboot) */ typedef struct { uint32_t magic; // Checksum/magic 0xCAFEBABE float current_offset_a; // Current measurement offset (zero-point cal) float current_scale; // Current scale factor (gain calibration) uint32_t total_cycles; // Cumulative cycle count (wear tracking) uint32_t timestamp; // Last calibration time (unix seconds) uint16_t reserved[6]; // Padding for future use } coulomb_calibration_t; /** * @struct coulomb_cycle_t * @brief Single cycle (charge/discharge) tracking */ typedef struct { float coulombs_capacity; // Capacity used in this cycle (Q) float depth_of_discharge; // DOD = coulombs_used / total_capacity uint32_t duration_s; // Cycle duration (seconds) float avg_current_a; // Average current during cycle } coulomb_cycle_t; // ============================================================================= // FUNCTION PROTOTYPES // ============================================================================= /** * @brief Initialize coulomb counter system * @param capacity_mah Battery capacity in mAh * @return 0 on success, -1 on INA219 init failure * * Initializes I2C, INA219 current sensor, loads flash calibration, * resets runtime counters. */ int coulomb_init(uint16_t capacity_mah); /** * @brief Update coulomb counter (call from 1 kHz systick) * * Reads INA219 current, integrates coulombs, updates SOC. * Non-blocking; should be called every systick period. */ void coulomb_tick(void); /** * @brief Get current system state * @return Pointer to coulomb_state_t (read-only) */ const coulomb_state_t* coulomb_get_state(void); /** * @brief Get current SOC percentage (0–100%) * @return SOC as float (0.0 = empty, 100.0 = full) */ float coulomb_get_soc(void); /** * @brief Get instantaneous current (amperes, signed) * @return Current in amps (negative = charging) */ float coulomb_get_current_a(void); /** * @brief Get total coulombs integrated since boot * @return Coulombs (Q) */ float coulomb_get_coulombs_total(void); /** * @brief Get total coulombs discharged from battery * @return Coulombs (Q) */ float coulomb_get_coulombs_discharged(void); /** * @brief Get current cycle count * @return Cycle count (uint16_t) */ uint16_t coulomb_get_cycle_count(void); /** * @brief Manually set SOC (e.g., after full charge detection) * @param soc_percent Desired SOC (0.0–100.0) * * Recalibrates coulombs_discharged to match target SOC. * Typically called when voltage is known to be full (5 amp + CV taper complete). */ void coulomb_set_soc(float soc_percent); /** * @brief Mark battery as fully charged (SOC = 100%) * * Resets coulombs_discharged to 0, begins new cycle tracking. */ void coulomb_mark_full_charge(void); /** * @brief Mark battery as fully discharged (SOC = 0%) * * Completes cycle count increment, logs cycle statistics to flash. */ void coulomb_mark_fully_discharged(void); /** * @brief Calibrate current sensor offset * * Samples INA219 at zero current (battery disconnected or in equilibrium) * and stores offset in RAM (can be flushed to flash via coulomb_save_calibration). */ void coulomb_calibrate_offset(void); /** * @brief Save runtime calibration to flash * @return 0 on success, -1 on flash error * * Persists current offset, scale, and cycle count to flash. * Should be called periodically or on shutdown. */ int coulomb_save_calibration(void); /** * @brief Load calibration from flash (called during init) * @return 0 on success, -1 if no valid calibration found * * Restores current offset, scale, and cycle count from flash. */ int coulomb_load_calibration(void); /** * @brief Reset all counters and calibration (factory reset) * @return 0 on success * * Clears flash storage and resets runtime state. Use with caution. */ int coulomb_factory_reset(void); /** * @brief Get detailed cycle information * @param cycle_idx Cycle index (0 = current, 1 = previous, etc.) * @param out_cycle Pointer to coulomb_cycle_t output struct * @return 0 on success, -1 if cycle_idx out of range */ int coulomb_get_cycle_info(uint8_t cycle_idx, coulomb_cycle_t* out_cycle); #endif // BATTERY_COULOMB_H