/** * 充电状态机 — AI-4 版本 * 7S Li-ion, 2A CC, 有 NTC, FB 默认分压 * * 与 LiPow battery.c 的差异: * - LiPow 的充电电流由 USB PD 协商动态决定,AI-4 固定 2A * - LiPow 有 per-cell 平衡逻辑(2S-4S),AI-4 7S 不需要 * - LiPow 用 RGB LED 指示状态,AI-4 可扩展 */ #include "charge_sm.h" #include #include /*=========================================================================== * AI-4 配置(7S / 2A / NTC / FB 默认分压) *===========================================================================*/ static const ChargeSM_Config AI4_DefaultCfg = { .full_voltage_mv = 29400, /* 7×4.2V */ .precharge_thresh_mv = 16170, /* 55% of 29.4V */ .recharge_hyst_mv = 2900, /* ~10% drop → restart */ .cc_current_ma = 2000, /* 2A 主充 */ .precharge_current_ma = 200, /* 200mA 预充 */ .term_current_ma = 200, /* 200mA → done (CC 的 10%)*/ .precharge_timeout_s = 1800, /* 30min */ .cc_cv_timeout_s = 28800, /* 8hr */ .cv_stable_time_s = 30, /* 30s below term → done */ .vbat_overvoltage_mv = 31000, .ibat_overcurrent_ma = 5000, /* 2.5× target */ /* NTC 已接入 */ .temp_max_c = 50, /* 高于 50°C 停充 */ .temp_min_c = 0, /* 低于 0°C 停充 */ .tick_ms = 200, }; /*=========================================================================== * 初始化 *===========================================================================*/ void ChargeSM_InitDefault(ChargeSM *sm) { ChargeSM_Init(sm, &AI4_DefaultCfg); } void ChargeSM_Init(ChargeSM *sm, const ChargeSM_Config *cfg) { memset(sm, 0, sizeof(*sm)); sm->cfg = *cfg; sm->state = CHARGE_STATE_IDLE; } /*=========================================================================== * 状态名 *===========================================================================*/ const char* ChargeSM_StateStr(ChargeState s) { switch (s) { case CHARGE_STATE_IDLE: return "IDLE"; case CHARGE_STATE_PRECHARGE: return "PRECHARGE"; case CHARGE_STATE_CC: return "CC"; case CHARGE_STATE_CV: return "CV"; case CHARGE_STATE_DONE: return "DONE"; case CHARGE_STATE_FAULT: return "FAULT"; default: return "?"; } } /*=========================================================================== * 故障检查 *===========================================================================*/ static int CheckFault(const ChargeSM *sm, int32_t vbat_mv, int32_t ibat_ma, int8_t temp_c, char *reason) { if (vbat_mv > 0 && (uint32_t)vbat_mv > sm->cfg.vbat_overvoltage_mv) { snprintf(reason, 32, "VBAT_OV %ldmV", (long)vbat_mv); return 1; } if (ibat_ma > 0 && (uint32_t)ibat_ma > sm->cfg.ibat_overcurrent_ma) { snprintf(reason, 32, "IBAT_OC %ldmA", (long)ibat_ma); return 1; } if (sm->cfg.temp_max_c > 0 && temp_c > sm->cfg.temp_max_c) { snprintf(reason, 32, "TEMP_HI %dC", temp_c); return 1; } if (sm->cfg.temp_min_c >= 0 && temp_c < sm->cfg.temp_min_c) { snprintf(reason, 32, "TEMP_LO %dC", temp_c); return 1; } return 0; } /*=========================================================================== * 状态切换(重置计时器) *===========================================================================*/ static void set_state(ChargeSM *sm, ChargeState ns) { sm->state = ns; sm->state_enter_ms = sm->elapsed_ms; sm->term_hold_ms = 0; sm->last_action = SM_ACTION_NONE; } /*=========================================================================== * ChargeSM_Run — 主循环 *===========================================================================*/ ChargeSM_Decision ChargeSM_Run(ChargeSM *sm, int32_t vbat_mv, int32_t ibat_ma, int8_t temp_c) { ChargeSM_Decision d = { .action = SM_ACTION_NONE, .target_current_ma = 0 }; d.reason[0] = '\0'; sm->elapsed_ms += sm->cfg.tick_ms; uint32_t state_time_ms = sm->elapsed_ms - sm->state_enter_ms; /* 故障检查最优先 */ char fr[32]; if (CheckFault(sm, vbat_mv, ibat_ma, temp_c, fr)) { if (sm->state != CHARGE_STATE_FAULT) { set_state(sm, CHARGE_STATE_FAULT); d.action = SM_ACTION_STOP; snprintf(d.reason, 32, "FAULT:%s", fr); } return d; } switch (sm->state) { case CHARGE_STATE_IDLE: if (vbat_mv <= 0) break; if ((uint32_t)vbat_mv < sm->cfg.precharge_thresh_mv) { set_state(sm, CHARGE_STATE_PRECHARGE); d.action = SM_ACTION_SET_PRECHG; d.target_current_ma = sm->cfg.precharge_current_ma; snprintf(d.reason, 32, "→PRECHG %ldmV", (long)vbat_mv); } else { set_state(sm, CHARGE_STATE_CC); d.action = SM_ACTION_SET_CC; d.target_current_ma = sm->cfg.cc_current_ma; snprintf(d.reason, 32, "→CC %ldmV", (long)vbat_mv); } break; case CHARGE_STATE_PRECHARGE: if (sm->cfg.precharge_timeout_s && state_time_ms > sm->cfg.precharge_timeout_s * 1000) { set_state(sm, CHARGE_STATE_FAULT); d.action = SM_ACTION_STOP; snprintf(d.reason, 32, "PRECHG_TO"); break; } if (vbat_mv > 0 && (uint32_t)vbat_mv >= sm->cfg.precharge_thresh_mv) { set_state(sm, CHARGE_STATE_CC); d.action = SM_ACTION_SET_CC; d.target_current_ma = sm->cfg.cc_current_ma; snprintf(d.reason, 32, "→CC %ldmV", (long)vbat_mv); } break; case CHARGE_STATE_CC: if (sm->cfg.cc_cv_timeout_s && state_time_ms > sm->cfg.cc_cv_timeout_s * 1000) { set_state(sm, CHARGE_STATE_FAULT); d.action = SM_ACTION_STOP; snprintf(d.reason, 32, "CC_CV_TO"); break; } if (vbat_mv > 0 && (uint32_t)vbat_mv >= sm->cfg.full_voltage_mv) { set_state(sm, CHARGE_STATE_CV); } break; case CHARGE_STATE_CV: if (sm->cfg.cc_cv_timeout_s && state_time_ms > sm->cfg.cc_cv_timeout_s * 1000) { set_state(sm, CHARGE_STATE_FAULT); d.action = SM_ACTION_STOP; snprintf(d.reason, 32, "CC_CV_TO"); break; } if (ibat_ma > 0 && (uint32_t)ibat_ma <= sm->cfg.term_current_ma) { sm->term_hold_ms += sm->cfg.tick_ms; } else { sm->term_hold_ms = 0; } if (sm->term_hold_ms >= sm->cfg.cv_stable_time_s * 1000) { set_state(sm, CHARGE_STATE_DONE); d.action = SM_ACTION_STOP; snprintf(d.reason, 32, "DONE %ldmA", (long)ibat_ma); } break; case CHARGE_STATE_DONE: if (vbat_mv > 0 && (uint32_t)vbat_mv <= sm->cfg.full_voltage_mv - sm->cfg.recharge_hyst_mv) { set_state(sm, CHARGE_STATE_CC); d.action = SM_ACTION_RESTART; d.target_current_ma = sm->cfg.cc_current_ma; snprintf(d.reason, 32, "RECHG %ldmV", (long)vbat_mv); } break; case CHARGE_STATE_FAULT: break; } return d; } void ChargeSM_ForceFault(ChargeSM *sm, const char *reason) { (void)reason; set_state(sm, CHARGE_STATE_FAULT); }