saltylab-firmware/include/battery_coulomb.h
sl-mechanical c96ed54af2 feat: Implement Issue #325 - Battery coulomb counter (STM32 firmware)
ADC-based coulomb integration for SaltyBot battery state-of-charge (SOC) tracking:

**Core Features**:
- INA219 I2C current sensor integration (10mΩ shunt, ±3.2A range)
- Coulomb integration at 1kHz tick rate (Q = ∫I dt)
- SOC calculation: 100% × (1 - Discharged_Q / Capacity_Q)
- Runtime state tracking: current, coulombs, SOC, tick count, charging flag
- Cycle tracking with depth-of-discharge (DOD) calculation
- Flash-persistent calibration (offset/scale) with magic checksum (0xCAFEBABE)

**API Functions**:
- coulomb_init() - Initialize INA219, load calibration
- coulomb_tick() - 1kHz integration (current → coulombs → SOC)
- coulomb_set_soc() / coulomb_mark_full_charge() - Manual calibration points
- coulomb_mark_fully_discharged() - Cycle tracking
- coulomb_save/load_calibration() - Flash persistence
- coulomb_factory_reset() - Clear all state and calibration

**Test Coverage** (19 test suites, 49 assertions):
- Initialization and state reset
- coulomb_tick() integration accuracy (zero current, constant discharge)
- SOC calculation accuracy and clamping (0–100%)
- Charging vs. discharging detection
- Flash read/write with magic validation
- Calibration offset/scale application
- Cycle counting and DOD tracking
- Long-duration integration (10s at 0.5A = 5 coulombs)
- Boundary conditions and rollover handling
- Capacity variants (2200–3000 mAh)

**Data Structures**:
- coulomb_state_t: Runtime state (volatile)
- coulomb_calibration_t: Flash storage (persistent)
- coulomb_cycle_t: Cycle history with DOD tracking

**Integration Details**:
- Battery capacity: 2200 mAh (611 coulombs) — configurable per variant
- Flash address: 0x0800D000 (1 sector, STM32F4)
- Sampling: 1kHz systick, 1ms per integration window
- Discharge threshold: 50mA (filtering noise)

All tests pass (49/49 assertions); ready for hardware integration.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 00:49:52 -05:00

215 lines
7.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// =============================================================================
// 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 <stdint.h>
#include <stdbool.h>
// =============================================================================
// 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 (0100%)
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 (0100%)
* @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.0100.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