B001-V3 works with it now
This commit is contained in:
@@ -34,194 +34,47 @@
|
||||
#include "storage.h"
|
||||
#include "rtc.h"
|
||||
|
||||
#define TAG "POWER"
|
||||
|
||||
// === GPIO Pin Definitions ===
|
||||
#define PIN_V_ISENS1 ADC_CHANNEL_6 // GPIO34
|
||||
#define PIN_V_ISENS2 ADC_CHANNEL_3 // GPIO39 / VN
|
||||
#define PIN_V_ISENS1 ADC_CHANNEL_0 // GPIO36 / VP
|
||||
#define PIN_V_ISENS2 ADC_CHANNEL_6 // GPIO34
|
||||
#define PIN_V_ISENS3 ADC_CHANNEL_7 // GPIO35
|
||||
#define PIN_V_BATTERY ADC_CHANNEL_0 // GPIO36 / VP
|
||||
#define PIN_V_BATTERY ADC_CHANNEL_3 // GPIO39 / VN
|
||||
#define PIN_V_SENS_BAT PIN_V_BATTERY
|
||||
|
||||
#define PIN_CHG_DISABLE GPIO_NUM_26
|
||||
#define PIN_CHG_BULK GPIO_NUM_27
|
||||
|
||||
#define AUTOZERO_THRESH 2000.0f // mA
|
||||
// update time
|
||||
#define UPDATE_MS 20
|
||||
#define UPDATE_S 0.02f
|
||||
|
||||
|
||||
typedef enum {
|
||||
CHG_T_LOWBAT = 0,
|
||||
CHG_T_BULK = 1,
|
||||
CHG_T_STEADY = 2,
|
||||
} charge_timer_t;
|
||||
|
||||
#define N_CHG_TIMERS 3
|
||||
RTC_DATA_ATTR charge_state_t current_charge_state = CHG_STATE_BULK;
|
||||
RTC_DATA_ATTR int64_t charge_timers[N_CHG_TIMERS] = {-1};
|
||||
|
||||
int64_t now;
|
||||
|
||||
charge_state_t get_charging_state() { return current_charge_state; }
|
||||
|
||||
void setTimerN(charge_timer_t i, int64_t sec) {
|
||||
// set the timer for <sec> in the future if it's currently less than now
|
||||
if (charge_timers[i] < now) {
|
||||
charge_timers[i] = now + sec;
|
||||
ESP_LOGI("BAT", "Set timer[%d] +%lld", i, (long long)sec);
|
||||
}
|
||||
}
|
||||
|
||||
void resetTimerN(charge_timer_t i) {
|
||||
charge_timers[i] = -1;
|
||||
}
|
||||
void resetBatTimers() {
|
||||
for (uint8_t i=0; i<N_CHG_TIMERS; i++)
|
||||
resetTimerN(i);
|
||||
}
|
||||
|
||||
bool getTimerN(charge_timer_t i) {
|
||||
if (charge_timers[i] < 0) return false;
|
||||
return system_rtc_get_raw_time() > charge_timers[i];
|
||||
}
|
||||
|
||||
#define BULK_CHARGE_S 20 //2*60*60
|
||||
#define FLOAT_STEADY_S 10 //30*60
|
||||
#define LOW_DETECT_S 10 //5*60
|
||||
|
||||
#define STEADY_MV 13000
|
||||
#define LOW_MV 12800
|
||||
|
||||
void run_charge_fsm() {
|
||||
now = system_rtc_get_raw_time();
|
||||
|
||||
//ESP_LOGI("BAT", "FSM STATE %d", current_charge_state);
|
||||
|
||||
if (rtc_is_set()) {
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
// turn off bulk charging and go to float when time is up
|
||||
if (getTimerN(CHG_T_BULK)) {
|
||||
ESP_LOGI("BAT", "BULK -> FLOAT");
|
||||
current_charge_state = CHG_STATE_FLOAT;
|
||||
}
|
||||
break;
|
||||
|
||||
case CHG_STATE_FLOAT:
|
||||
if (getTimerN(CHG_T_STEADY)) {
|
||||
ESP_LOGI("BAT", "FLOAT -> OFF");
|
||||
current_charge_state = CHG_STATE_OFF;
|
||||
}
|
||||
if (get_battery_mV() > STEADY_MV) {
|
||||
setTimerN(CHG_T_STEADY, FLOAT_STEADY_S);
|
||||
} else {
|
||||
resetTimerN(CHG_T_STEADY);
|
||||
}
|
||||
// NO break; !! float should also kick into bulk with same triggers
|
||||
case CHG_STATE_OFF:
|
||||
|
||||
// after 5 minutes of low-ish battery go into bulk charge
|
||||
if (getTimerN(CHG_T_LOWBAT)) {
|
||||
ESP_LOGI("BAT", " -> BULK");
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
setTimerN(CHG_T_BULK, BULK_CHARGE_S);
|
||||
}
|
||||
if (get_battery_mV() < LOW_MV) {
|
||||
setTimerN(CHG_T_LOWBAT, LOW_DETECT_S);
|
||||
} else {
|
||||
resetTimerN(CHG_T_LOWBAT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
//ESP_LOGI("BAT", " -> BULK");
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
}
|
||||
|
||||
//rtc_gpio_hold_dis(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_dis(PIN_CHG_DISABLE);
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
gpio_set_level(PIN_CHG_BULK, 1);
|
||||
gpio_set_level(PIN_CHG_DISABLE, 0);
|
||||
//ESP_LOGI("BAT", "BULK");
|
||||
break;
|
||||
case CHG_STATE_FLOAT:
|
||||
gpio_set_level(PIN_CHG_BULK, 0);
|
||||
gpio_set_level(PIN_CHG_DISABLE, 0);
|
||||
//ESP_LOGI("BAT", "FLOAT");
|
||||
break;
|
||||
case CHG_STATE_OFF:
|
||||
gpio_set_level(PIN_CHG_BULK, 0);
|
||||
gpio_set_level(PIN_CHG_DISABLE, 1);
|
||||
//ESP_LOGI("BAT", "OFF");
|
||||
break;
|
||||
}
|
||||
//rtc_gpio_hold_en(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
}
|
||||
int64_t now; // us
|
||||
|
||||
typedef struct {
|
||||
bool enabled; // Auto-zero active for this channel
|
||||
float threshold_ma; // Max current to consider "zero" (mA)
|
||||
float learned_offset_mv; // Accumulated zero offset (mV)
|
||||
bool initialized; // First valid zero established
|
||||
} autozero_t;
|
||||
static autozero_t autozero[N_BRIDGES] = {0};
|
||||
int64_t az_enable_time; // Timestamp to enable autozeroing at (negative to disable)
|
||||
float az_offset; // Accumulated zero offset
|
||||
bool az_initialized; // First valid zero established
|
||||
|
||||
// === E-Fuse (Software Breaker) Configuration ===
|
||||
static const char* currentLimits_A[N_BRIDGES] = {
|
||||
[BRIDGE_DRIVE] = "efuse_drive_A", //40000,
|
||||
[BRIDGE_AUX] = "efuse_aux_A", // 5000,
|
||||
[BRIDGE_JACK] = "efuse_jack_A" // 10000
|
||||
};
|
||||
static const float i2t_thresholds[N_BRIDGES] = { // A^2*s (tunable per bridge if needed)
|
||||
[BRIDGE_DRIVE] = 6.0f,
|
||||
[BRIDGE_AUX] = 6.0f,
|
||||
[BRIDGE_JACK] = 6.0f
|
||||
};
|
||||
static const float i_instant[N_BRIDGES] = { // Instant trip multiplier of I_rated
|
||||
[BRIDGE_DRIVE] = 15.0f,
|
||||
[BRIDGE_AUX] = 15.0f,
|
||||
[BRIDGE_JACK] = 15.0f
|
||||
};
|
||||
static const float cool_rate[N_BRIDGES] = { // Cooling constant (1/s)
|
||||
[BRIDGE_DRIVE] = 0.008f,
|
||||
[BRIDGE_AUX] = 0.008f,
|
||||
[BRIDGE_JACK] = 0.008f
|
||||
};
|
||||
static const int32_t cooldown_ms[N_BRIDGES] = { // Auto-reset delay after trip
|
||||
[BRIDGE_DRIVE] = 5000,
|
||||
[BRIDGE_AUX] = 5000,
|
||||
[BRIDGE_JACK] = 5000
|
||||
};
|
||||
|
||||
static float efuse_heat[N_BRIDGES] = {0};
|
||||
static uint64_t efuse_trip_time[N_BRIDGES] = {0}; // Timestamp when tripped
|
||||
static bool efuse_tripped[N_BRIDGES] = {false};
|
||||
bool ema_init;
|
||||
float ema_current;
|
||||
|
||||
float current; // with all the corrections applied
|
||||
|
||||
float heat;
|
||||
bool tripped;
|
||||
int64_t trip_time;
|
||||
} isens_channel_t;
|
||||
static isens_channel_t isens[N_BRIDGES] = {0};
|
||||
|
||||
// === ADC Handles ===
|
||||
static adc_oneshot_unit_handle_t adc1_handle = NULL;
|
||||
static adc_cali_handle_t adc_cali_handle = NULL;
|
||||
|
||||
// === EMA Filter State ===
|
||||
#define EMA_ALPHA_CURRENT 0.5f
|
||||
#define EMA_ALPHA_BATTERY 0.05f
|
||||
|
||||
static float ema_current[N_BRIDGES] = {0};
|
||||
static bool ema_init[N_BRIDGES] = {false};
|
||||
|
||||
static float ema_battery = 0.0f;
|
||||
static bool ema_battery_init = false;
|
||||
|
||||
// === Shared Volatile Outputs ===
|
||||
volatile int32_t bridgeCurrents_mA[N_BRIDGES] = {0};
|
||||
volatile int32_t batteryVoltage_mV = 0;
|
||||
|
||||
// === ADC Initialization ===
|
||||
static esp_err_t adc_init(void) {
|
||||
if (adc1_handle != NULL) {
|
||||
return ESP_OK; // Already initialized
|
||||
}
|
||||
|
||||
// ADC1 oneshot mode
|
||||
esp_err_t adc_init() {
|
||||
// ADC1 oneshot mode
|
||||
adc_oneshot_unit_init_cfg_t init_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
@@ -246,51 +99,62 @@ static esp_err_t adc_init(void) {
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_cfg, &adc_cali_handle));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
float get_raw_battery_voltage(void) {
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, PIN_V_SENS_BAT, &adc_raw)
|
||||
!= ESP_OK) { return NAN; }
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv)
|
||||
!= ESP_OK) { return NAN; }
|
||||
|
||||
// Voltage divider: 150kohm to 1Mohm -> gain = 1.15 -> scale = 1150/150
|
||||
return voltage_mv * 0.00766666666; // same as / 1000.0 * 1150.0 / 150.0;
|
||||
}
|
||||
|
||||
esp_err_t process_battery_voltage(void)
|
||||
{
|
||||
float raw = get_raw_battery_voltage();
|
||||
|
||||
if (!ema_battery_init) {
|
||||
ema_battery = (float)raw;
|
||||
ema_battery_init = true;
|
||||
} else {
|
||||
float alpha = get_param(PARAM_ADC_ALPHA_BATTERY).f32;
|
||||
if (isnan(raw)) {
|
||||
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
||||
} else {
|
||||
if (isnan(ema_battery) || isnan(alpha))
|
||||
ema_battery = raw;
|
||||
else
|
||||
ema_battery = alpha * (float)raw + (1.0f - alpha) * ema_battery;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void autozero_enable(bridge_t bridge, bool enable) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].enabled = enable;
|
||||
if (!enable) {
|
||||
autozero[bridge].learned_offset_mv = 0.0f;
|
||||
autozero[bridge].initialized = false;
|
||||
}
|
||||
void set_autozero(bridge_t bridge) {
|
||||
// enable autozeroing for this bridge 1 second from now
|
||||
isens[bridge].az_enable_time = now+1000000;
|
||||
//ESP_LOGI(TAG, "KILLING BRIDGE %d; %lld -> %lld", bridge, (long long int) now, (long long int) isens[bridge].az_enable_time);
|
||||
}
|
||||
|
||||
void autozero_set_threshold(bridge_t bridge, float threshold_ma) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].threshold_ma = fmaxf(0.0f, threshold_ma);
|
||||
}
|
||||
|
||||
float autozero_get_offset_mv(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return 0.0f;
|
||||
return autozero[bridge].learned_offset_mv;
|
||||
}
|
||||
|
||||
void autozero_reset(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].learned_offset_mv = 0.0f;
|
||||
autozero[bridge].initialized = false;
|
||||
}
|
||||
|
||||
void autozero_reset_all(void) {
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
autozero_reset((bridge_t)i);
|
||||
}
|
||||
}
|
||||
|
||||
// === Raw Current Reading (mA) ===
|
||||
static int32_t read_bridge_current_raw(bridge_t bridge) {
|
||||
int adc_raw = 0;
|
||||
esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
isens_channel_t *channel = &isens[bridge];
|
||||
|
||||
adc_channel_t pin;
|
||||
switch(bridge) {
|
||||
case BRIDGE_DRIVE: pin = PIN_V_ISENS3; break;
|
||||
case BRIDGE_AUX: pin = PIN_V_ISENS1; break;
|
||||
case BRIDGE_DRIVE: pin = PIN_V_ISENS1; break;
|
||||
case BRIDGE_JACK: pin = PIN_V_ISENS2; break;
|
||||
default: return -42069;
|
||||
case BRIDGE_AUX: pin = PIN_V_ISENS3; break;
|
||||
default: return -42069; // lol
|
||||
}
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, pin, &adc_raw) != ESP_OK) {
|
||||
@@ -299,205 +163,171 @@ static int32_t read_bridge_current_raw(bridge_t bridge) {
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float current_sense_mv = (float)voltage_mv;
|
||||
autozero_t *az = &autozero[bridge];
|
||||
|
||||
// === AUTO-ZERO LEARNING PHASE ===
|
||||
if (az->enabled && get_bridge_state(bridge)==0) {
|
||||
float raw_current_ma = 0.0f;
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
// ACS37042KLHBLT-030B3 is 30A capable and 44 mV/A
|
||||
raw_current_ma = (current_sense_mv - 1650.0f) * 1000.0f / 44.0f;
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
// ACS37220LEZATR-100B3 is 100A capable and 13.2 mV/A
|
||||
raw_current_ma = (current_sense_mv - 1650.0f) * 1000.0f / 13.20f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fabsf(raw_current_ma) <= az->threshold_ma) {
|
||||
// Valid zero sample
|
||||
if (!az->initialized) {
|
||||
az->learned_offset_mv = current_sense_mv - 1650.0f;
|
||||
az->initialized = true;
|
||||
} else {
|
||||
// EMA on offset (slow adaptation)
|
||||
float alpha = 0.1f;
|
||||
az->learned_offset_mv = alpha * (current_sense_mv - 1650.0f) +
|
||||
(1.0f - alpha) * az->learned_offset_mv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === APPLY AUTO-ZERO OFFSET ===
|
||||
float corrected_mv = current_sense_mv - az->learned_offset_mv;
|
||||
|
||||
int32_t offset_mv = (int32_t)(corrected_mv - 1650.0f);
|
||||
int32_t current_ma = 0;
|
||||
|
||||
|
||||
float raw_a = NAN;
|
||||
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
current_ma = offset_mv * 1000 / 44; // 44 mV/A
|
||||
// ACS37042KLHBLT-030B3 is 30A capable and 44 mV/A
|
||||
raw_a = (voltage_mv - 1650.0f) / 44.0f;
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
current_ma = offset_mv * 10000 / 132; // 13.2 mV/A
|
||||
// ACS37220LEZATR-100B3 is 100A capable and 13.2 mV/A
|
||||
raw_a = (voltage_mv - 1650.0f) / 13.2f;
|
||||
break;
|
||||
}
|
||||
|
||||
return current_ma;
|
||||
}
|
||||
|
||||
// === Raw Battery Voltage Reading (mV) ===
|
||||
static int32_t read_battery_voltage_raw(void)
|
||||
{
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, PIN_V_SENS_BAT, &adc_raw) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Voltage divider: 150kΩ to 1MΩ → gain = 1.15 → scale = 1150/150
|
||||
return (int32_t)voltage_mv * 1150 / 150;
|
||||
}
|
||||
|
||||
// === EMA Filter Update ===
|
||||
static void apply_ema(float *state, bool *init, float alpha, int32_t raw, volatile int32_t *out)
|
||||
{
|
||||
if (!*init) {
|
||||
*state = (float)raw;
|
||||
*init = true;
|
||||
|
||||
if (!channel->ema_init) {
|
||||
channel->ema_current = (float)raw_a;
|
||||
channel->ema_init = true;
|
||||
} else {
|
||||
*state = alpha * (float)raw + (1.0f - alpha) * *state;
|
||||
}
|
||||
*out = (int32_t)(*state + 0.5f);
|
||||
}
|
||||
|
||||
// === Public Accessors ===
|
||||
int32_t get_bridge_mA(uint8_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return -1;
|
||||
return (int32_t)bridgeCurrents_mA[bridge];
|
||||
}
|
||||
|
||||
int32_t get_battery_mV(void)
|
||||
{
|
||||
return (int32_t)batteryVoltage_mV;
|
||||
}
|
||||
|
||||
// === E-Fuse: Trip Logic (called every cycle) ===
|
||||
static void efuse_update(uint8_t bridge, float I, float dt, uint64_t now)
|
||||
{
|
||||
float I_rated = (float)get_param_i8(currentLimits_A[bridge]);
|
||||
float I_norm = I / I_rated;
|
||||
|
||||
// Instant trip on extreme overcurrent
|
||||
if (I_norm >= i_instant[bridge]) {
|
||||
efuse_tripped[bridge] = true;
|
||||
efuse_trip_time[bridge] = now;
|
||||
return;
|
||||
}
|
||||
|
||||
// Cooling when below threshold
|
||||
if (I_norm < 1.1f) {
|
||||
efuse_heat[bridge] -= efuse_heat[bridge] * cool_rate[bridge] * dt;
|
||||
efuse_heat[bridge] = fmaxf(0.0f, efuse_heat[bridge]);
|
||||
efuse_tripped[bridge] = false; // Auto-clear if cooled
|
||||
return;
|
||||
}
|
||||
|
||||
// Accumulate heat (I²t)
|
||||
efuse_heat[bridge] += (I_norm * I_norm) * dt;
|
||||
|
||||
if (efuse_heat[bridge] >= i2t_thresholds[bridge]) {
|
||||
efuse_tripped[bridge] = true;
|
||||
efuse_trip_time[bridge] = now;
|
||||
}
|
||||
}
|
||||
|
||||
// === E-Fuse: Auto-Reset After Cooldown ===
|
||||
static void efuse_cooldown_check(uint64_t now)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
if (efuse_tripped[i] &&
|
||||
(now - efuse_trip_time[i]) >= (cooldown_ms[i] * 1000ULL)) {
|
||||
efuse_heat[i] = 0.0f;
|
||||
efuse_tripped[i] = false;
|
||||
float alpha = get_param(PARAM_ADC_ALPHA_ISENS).f32;
|
||||
if (isnan(raw_a)) {
|
||||
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
||||
channel->ema_current = NAN;
|
||||
} else {
|
||||
if (isnan(ema_battery) || isnan(alpha))
|
||||
channel->ema_current = raw_a;
|
||||
else
|
||||
channel->ema_current = alpha * raw_a + (1.0f - alpha) * channel->ema_current;
|
||||
}
|
||||
}
|
||||
|
||||
// === AUTO-ZERO LEARNING PHASE ===
|
||||
if (now > channel->az_enable_time) {
|
||||
//ESP_LOGI(TAG, "AZING %d", bridge);
|
||||
float db = get_param(PARAM_ADC_DB_IAZ).f32;
|
||||
if (isnan(db) || fabsf(channel->ema_current) <= db) {
|
||||
// Valid zero sample
|
||||
if (!channel->az_initialized) {
|
||||
channel->az_offset = channel->ema_current;
|
||||
channel->az_initialized = true;
|
||||
} else {
|
||||
float alpha = get_param(PARAM_ADC_ALPHA_IAZ).f32;
|
||||
if (isnan(raw_a)) {
|
||||
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
||||
} else {
|
||||
if (isnan(ema_battery) || isnan(alpha))
|
||||
channel->az_offset = channel->ema_current;
|
||||
else
|
||||
channel->az_offset = alpha * channel->ema_current +
|
||||
(1.0f - alpha) * channel->az_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the offset
|
||||
channel->current = channel->ema_current - channel->az_offset;
|
||||
|
||||
|
||||
// PARAMETERS FOR E-FUSING ALGORITHM
|
||||
// PARAM_EFUSE_KINST : ratio of nominal current that should cause an immediate shutdown
|
||||
// PARAM_EFUSE_TCOOL : cooldown timer from trip (in microseconds)
|
||||
// PARAM_EFUSE_TAUCOOL : speed of cooldown for heating (units are 1/s; bigger = faster cooldown)
|
||||
|
||||
// Monitor E-fusing
|
||||
float I_nominal = NAN;
|
||||
switch(bridge) {
|
||||
case BRIDGE_DRIVE:
|
||||
I_nominal = get_param(PARAM_EFUSE_INOM_1).f32;
|
||||
break;
|
||||
case BRIDGE_JACK:
|
||||
I_nominal = get_param(PARAM_EFUSE_INOM_2).f32;
|
||||
break;
|
||||
case BRIDGE_AUX:
|
||||
I_nominal = get_param(PARAM_EFUSE_INOM_3).f32;
|
||||
break;
|
||||
}
|
||||
|
||||
// Normalize the current as a fraction of rated current
|
||||
float I_norm = fabsf(channel->current / I_nominal);
|
||||
|
||||
// Instant trip on extreme overcurrent
|
||||
if (I_norm >= get_param(PARAM_EFUSE_KINST).f32) {
|
||||
channel->tripped = true;
|
||||
channel->trip_time = now;
|
||||
ESP_LOGI(TAG, "FUSE TRIP: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
|
||||
return ESP_OK; // no more processing, if we're over, we're over
|
||||
}
|
||||
|
||||
// Accumulate heat
|
||||
channel->heat += (I_norm * I_norm) * UPDATE_S;
|
||||
|
||||
// Only do cooling when below threshold
|
||||
if (I_norm < 1.0f) {
|
||||
// if we are hot we radiate more heat
|
||||
// (I^2/I^2*t) * (1/t) * t = I^2/I^2*t
|
||||
channel->heat -= channel->heat * get_param(PARAM_EFUSE_TAUCOOL).f32 * UPDATE_S;
|
||||
channel->heat = fmaxf(0.0f, channel->heat); // keep it from going negative
|
||||
// channel.tripped = false; // Auto-clear if cooled (WTF why this is insane)
|
||||
}
|
||||
|
||||
// If built-up heat exceeds the time limit, trip
|
||||
// Recall units of heat are (current_actual^2/current_nominal^2)*time
|
||||
// Ergo, heat is measured in seconds
|
||||
if (channel->heat > get_param(PARAM_EFUSE_HEAT_THRESH).f32) {
|
||||
channel->tripped = true;
|
||||
channel->trip_time = now;
|
||||
|
||||
// If we're not overheated
|
||||
// And enough time has passed
|
||||
// Go ahead and reset the e-fuse
|
||||
} else if (channel->tripped &&
|
||||
(now - channel->trip_time) > get_param(PARAM_EFUSE_TCOOL).i64) {
|
||||
channel->tripped = false;
|
||||
// channel.heat = 0.0f // I think we should wait for the e-fuse to catch up
|
||||
}
|
||||
|
||||
if (bridge == BRIDGE_DRIVE)
|
||||
ESP_LOGI(TAG, "FUSE: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
// === Public Accessors ===
|
||||
float get_bridge_A(bridge_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return NAN;
|
||||
return isens[bridge].current;
|
||||
}
|
||||
|
||||
float get_battery_V(void)
|
||||
{
|
||||
if (ema_battery_init)
|
||||
return ema_battery;
|
||||
return get_raw_battery_voltage();
|
||||
}
|
||||
|
||||
// === Public E-Fuse Controls ===
|
||||
void efuse_reset_all(void)
|
||||
/*void efuse_reset_all(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
efuse_heat[i] = 0.0f;
|
||||
efuse_tripped[i] = false;
|
||||
isens[i].heat = 0.0f;
|
||||
isens[i].tripped = false;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
bool efuse_is_tripped(uint8_t bridge)
|
||||
bool efuse_is_tripped(bridge_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return false;
|
||||
return efuse_tripped[bridge];
|
||||
return isens[bridge].tripped;
|
||||
}
|
||||
|
||||
|
||||
// === Power Management Task ===
|
||||
void power_mgmt_task(void *param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
/*gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << PIN_CHG_DISABLE) | (1ULL << PIN_CHG_BULK),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io_conf);*/
|
||||
|
||||
/*// Enable RTC GPIO domain (required for hold)
|
||||
rtc_gpio_init(PIN_CHG_DISABLE);
|
||||
rtc_gpio_init(PIN_CHG_BULK);
|
||||
|
||||
// Set as output
|
||||
rtc_gpio_set_direction(PIN_CHG_DISABLE, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
rtc_gpio_set_direction(PIN_CHG_BULK, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
|
||||
// Optional: set initial level (will be held)
|
||||
//rtc_gpio_set_level(PIN_CHG_DISABLE, 1); // e.g., start disabled
|
||||
//rtc_gpio_set_level(PIN_CHG_BULK, 0);
|
||||
|
||||
// **Critical: Enable hold function**
|
||||
rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
rtc_gpio_hold_en(PIN_CHG_BULK);*/
|
||||
|
||||
ESP_ERROR_CHECK(adc_init());
|
||||
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(20);
|
||||
|
||||
// Optional: Enable auto-zero with default threshold
|
||||
autozero_enable(BRIDGE_DRIVE, true);
|
||||
autozero_enable(BRIDGE_AUX, true);
|
||||
autozero_enable(BRIDGE_JACK, true);
|
||||
|
||||
autozero_set_threshold(BRIDGE_DRIVE, AUTOZERO_THRESH);
|
||||
autozero_set_threshold(BRIDGE_AUX, AUTOZERO_THRESH);
|
||||
autozero_set_threshold(BRIDGE_JACK, AUTOZERO_THRESH);
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(UPDATE_MS);
|
||||
|
||||
//uint64_t last_wake_time = esp_timer_get_time();
|
||||
//const uint64_t period = 5000; // 100 us => 10kHz
|
||||
while (1) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
uint64_t now_us = esp_timer_get_time();
|
||||
now = esp_timer_get_time(); // us
|
||||
|
||||
/*if (now - last_wake_time < period) {
|
||||
uint32_t delay_us = (period - (now - last_wake_time)) / 1000;
|
||||
@@ -508,43 +338,20 @@ void power_mgmt_task(void *param) {
|
||||
|
||||
// Sample currents
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
int32_t raw_ma = read_bridge_current_raw((bridge_t)i);
|
||||
apply_ema(&ema_current[i], &ema_init[i], EMA_ALPHA_CURRENT,
|
||||
raw_ma, &bridgeCurrents_mA[i]);
|
||||
|
||||
// Reset spike timer if under limit
|
||||
/*if (bridgeCurrents_mA[i] < currentLimits_mA[i]) {
|
||||
currentSpikeSafeTimes[i] = now + CURRENT_SPIKE_TIME_US;
|
||||
}*/
|
||||
|
||||
// === E-FUSE UPDATE ===
|
||||
float I = (float)bridgeCurrents_mA[i] / 1000.0f;
|
||||
float dt = 0.020f; // 20 ms task period
|
||||
efuse_update(i, I, dt, now_us);
|
||||
process_bridge_current(i);
|
||||
}
|
||||
|
||||
/*ESP_LOGI("PWR", "[ %6ld | %6ld | %6ld mA ] { %6ld mV }",
|
||||
(long)bridgeCurrents_mA[BRIDGE_DRIVE],
|
||||
(long)bridgeCurrents_mA[BRIDGE_JACK],
|
||||
(long)bridgeCurrents_mA[BRIDGE_AUX],
|
||||
(long)batteryVoltage_mV);*/
|
||||
|
||||
// Sample battery
|
||||
int32_t raw_bat = read_battery_voltage_raw();
|
||||
apply_ema(&ema_battery, &ema_battery_init, EMA_ALPHA_BATTERY,
|
||||
raw_bat, &batteryVoltage_mV);
|
||||
|
||||
|
||||
//run_charge_fsm();
|
||||
efuse_cooldown_check(now_us);
|
||||
process_battery_voltage();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
|
||||
void start_power() {
|
||||
esp_err_t power_init() {
|
||||
xTaskCreate(power_mgmt_task, "PWR", 4096, NULL, 5, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void shutdown_power() {
|
||||
|
||||
esp_err_t power_stop() {
|
||||
return ESP_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user