/** * @file app_mppt.c * @brief MPPT P&O 扰动观察法实现 — FSBB 四开关 Buck-Boost * * 算法: * 1. 每隔 PNO_PERIOD 读一次 Vpv/Ipv 并算功率 * 2. 比较本次功率与上次: 涨了→同方向, 跌了→反向 * 3. 扰动 vin_set 步长 PNO_STEP * 4. 限幅在 VIN_MIN ~ VIN_MAX 之间 * 5. 定期全扫描 (RESTART) — 从 VIN_MIN 扫到 VIN_MAX * 6. 全扫描记录最大功率点, 重置 P&O 到该点 * * 调用链: * ChargeTask (200ms) → Mppt_Scheduler() → 更新 vin_set * TIM7 ISR (50us) → PID 计算 → PWM 占空比 (在 charge.c 中) */ #include "app_mppt.h" #include /*=========================================================================== * 静态变量 *===========================================================================*/ static MpptState g_mppt; /*=========================================================================== * 内部桩函数 — 实际实现应在 bsp_charge.c *===========================================================================*/ /** * 外部声明 — 这些函数在 bsp_charge.c 中实现: * int32_t BspCharge_GetVpv(void); // 返回面板电压 (mV) * int32_t BspCharge_GetIpv(void); // 返回面板电流 (mA) * void BspCharge_SetVinRef(int32_t mv); // 设置输入电压目标值 */ extern int32_t BspCharge_GetVpv(void); extern int32_t BspCharge_GetIpv(void); extern void BspCharge_SetVinRef(int32_t mv); /*=========================================================================== * 初始化 *===========================================================================*/ int Mppt_Init(void) { memset(&g_mppt, 0, sizeof(g_mppt)); /* 初始电压目标 — 取中点, 让 P&O 往两边探索 */ g_mppt.vin_ref_mv = (MPPT_VIN_MIN_MV + MPPT_VIN_MAX_MV) / 2; g_mppt.pno_dir = 1; g_mppt.ppv_max_mw = 0; g_mppt.mode = MPPT_MODE_DISABLED; return 0; } /*=========================================================================== * 接口函数 *===========================================================================*/ int32_t Mppt_GetVinRef(void) { return g_mppt.vin_ref_mv; } void Mppt_SetEnable(bool enable) { if (enable && g_mppt.mode == MPPT_MODE_DISABLED) { g_mppt.mode = MPPT_MODE_TRACKING; g_mppt.pno_timer_ms = 0; g_mppt.restart_timer_ms = 0; g_mppt.pno_dir = 1; g_mppt.ppv_last_mw = 0; } else if (!enable) { g_mppt.mode = MPPT_MODE_DISABLED; } } void Mppt_GetStatus(MpptState *out) { if (out) *out = g_mppt; } /*=========================================================================== * 内部函数 *===========================================================================*/ /** * @brief 读取面板功率 * * Vpv 和 Ipv 通过多次采样取平均以降低噪声。 * 功率 = Vpv × Ipv / 1000 (单位 mW) */ static int32_t read_panel_power(void) { int32_t vpv_sum = 0; int32_t ipv_sum = 0; int i; /* 8 次采样取平均 — 硬件 ADC 需在 bsp_charge.c 有时间间隔 */ for (i = 0; i < 8; i++) { vpv_sum += BspCharge_GetVpv(); ipv_sum += BspCharge_GetIpv(); } int32_t vpv = vpv_sum / 8; int32_t ipv = ipv_sum / 8; return (int32_t)(((int64_t)vpv * ipv) / 1000); } /** * @brief P&O 扰动 — 核心算法 * * 对比当前功率与上次功率: * - P_now > P_last: 同方向继续 (pno_dir 不变) * - P_now < P_last: 反方向 (pno_dir 取反) * * 扰动对象: vin_ref_mv (输入电压目标值) */ static void pno_perturb(void) { int32_t ppv_now = g_mppt.ppv_mw; if (g_mppt.ppv_last_mw == 0) { /* 第一次扰动 — 直接往正方向探 */ g_mppt.pno_dir = 1; } else if (ppv_now > g_mppt.ppv_last_mw) { /* 功率涨了 — 同方向 */ /* pno_dir 不变 */ } else { /* 功率跌了 — 反方向 */ g_mppt.pno_dir = -g_mppt.pno_dir; } g_mppt.vin_ref_mv += g_mppt.pno_dir * MPPT_PNO_STEP_MV; g_mppt.ppv_last_mw = ppv_now; g_mppt.pno_count++; /* 限幅 */ if (g_mppt.vin_ref_mv > MPPT_VIN_MAX_MV) g_mppt.vin_ref_mv = MPPT_VIN_MAX_MV; if (g_mppt.vin_ref_mv < MPPT_VIN_MIN_MV) g_mppt.vin_ref_mv = MPPT_VIN_MIN_MV; /* 更新全局最大功率记录 */ if (ppv_now > g_mppt.ppv_max_mw) { g_mppt.ppv_max_mw = ppv_now; g_mppt.vin_max_mw = g_mppt.vin_ref_mv; } } /** * @brief 全面板电压扫描 — 从头扫到尾, 找全局最大功率点 * * 流程: * 1. 从 VIN_MIN 开始 * 2. 每步增加 SWEEP_STEP * 3. 设 vin_set → 等待 SWEEP_SETTLE → 读功率 * 4. 记录最大功率的电压点 * 5. 完成后把 vin_set 设在全局最优点 */ static void sweep_start(void) { g_mppt.mode = MPPT_MODE_SWEEP; g_mppt.sweep_vin_cur = MPPT_VIN_MIN_MV; g_mppt.sweep_vin_start = MPPT_VIN_MIN_MV; g_mppt.sweep_vin_end = MPPT_VIN_MAX_MV; g_mppt.sweep_ppv_max = 0; g_mppt.sweep_vin_best = MPPT_VIN_MIN_MV; g_mppt.restart_count++; } static bool sweep_step(void) { /* 设电压点并等待 */ BspCharge_SetVinRef(g_mppt.sweep_vin_cur); /* 稳定后读功率 */ int32_t ppv = read_panel_power(); /* 记录全局最优 */ if (ppv > g_mppt.sweep_ppv_max) { g_mppt.sweep_ppv_max = ppv; g_mppt.sweep_vin_best = g_mppt.sweep_vin_cur; } /* 下一个电压点 */ g_mppt.sweep_vin_cur += MPPT_SWEEP_STEP_MV; if (g_mppt.sweep_vin_cur > g_mppt.sweep_vin_end) { /* 扫描完成 — 停在全局最优点 */ g_mppt.vin_ref_mv = g_mppt.sweep_vin_best; BspCharge_SetVinRef(g_mppt.vin_ref_mv); g_mppt.ppv_max_mw = g_mppt.sweep_ppv_max; g_mppt.vin_max_mw = g_mppt.sweep_vin_best; g_mppt.ppv_last_mw = 0; /* 重置 P&O 历史 */ g_mppt.mode = MPPT_MODE_TRACKING; return true; } return false; } /*=========================================================================== * Mppt_Scheduler — 主调度函数 *===========================================================================*/ void Mppt_Scheduler(void) { int32_t ppv_now; if (g_mppt.mode == MPPT_MODE_DISABLED) return; /* -- 全扫描模式 -- */ if (g_mppt.mode == MPPT_MODE_SWEEP) { sweep_step(); return; } /* -- 读取当前功率 -- */ ppv_now = read_panel_power(); g_mppt.vpv_mv = BspCharge_GetVpv(); g_mppt.ipv_ma = BspCharge_GetIpv(); g_mppt.ppv_mw = ppv_now; /* -- 功率暴跌检测 (Restart) -- */ if (g_mppt.ppv_max_mw > 0) { int32_t threshold = (int32_t)((int64_t)g_mppt.ppv_max_mw * MPPT_POWER_LOST_RATIO / 100); if (ppv_now < threshold) { /* 功率暴跌 — 可能有云遮挡或阴影, 重新全扫描 */ sweep_start(); return; } } /* -- 定期全扫描 -- */ g_mppt.restart_timer_ms += MPPT_PNO_PERIOD_MS; if (g_mppt.restart_timer_ms >= MPPT_RESTART_PERIOD_MS) { g_mppt.restart_timer_ms = 0; sweep_start(); return; } /* -- P&O 扰动间隔控制 -- */ g_mppt.pno_timer_ms += MPPT_PNO_PERIOD_MS; if (g_mppt.pno_timer_ms < MPPT_PNO_PERIOD_MS) return; g_mppt.pno_timer_ms = 0; /* -- P&O 扰动 -- */ pno_perturb(); /* -- 稳定后输出到 PID -- */ BspCharge_SetVinRef(g_mppt.vin_ref_mv); }