/** * app_charge_task.c — AI-4 充电管理任务(µC/OS-III) * * 集成充电状态机 + BQ25756 HAL + INT 中断故障保护 * * 分层: * soft_i2c.c ← GPIO 位操作 I2C * bq25756_hal.c ← BQ25756 寄存器读写(本文件仅调用 HAL 函数) * bq25756_ai4_init.c ← AI-4 初始化序列 * charge_sm.c ← 充电状态机 * app_charge_task.c ← RTOS 任务 + ISR + 诊断(本文件) * * 任务结构: * ChargeTask (prio 5): * 每 200ms: 读 BQ25756 ADC → 输入状态机 → 执行 BQ25756 寄存器操作 * 每 10s: 复位 BQ25756 看门狗 * 每轮检查: INT 中断标记的故障 → 立即停充 * * AdcTask (prio 4): * 采集 NTC ADC → 转换为温度值 → 全局变量供 ChargeTask 读取 * * INT 中断 (EXTI, 最高优先): * BQ25756 INT 引脚下降沿触发 → ISR 读 FAULT_STATUS → * 置全局故障标记 → ChargeTask 下一轮(最迟 200ms)检测到并停充 */ #include "board_config.h" #include "bq25756_regs.h" #include "bq25756_hal.h" #include "soft_i2c.h" #include "charge_sm.h" #include #include #include /*=========================================================================== * 全局变量 — 跨任务/ISR 共享 *===========================================================================*/ static int32_t g_vbat_mv = 0; /* 电池电压 mV */ static int32_t g_ibat_ma = 0; /* 充电电流 mA */ static int8_t g_temp_c = 25; /* NTC 温度 °C (AdcTask 写入) */ /*=========================================================================== * RTOS 延时适配 — bq25756_hal.c 的平台无关回调 *===========================================================================*/ static void rtos_delay_ms(uint32_t ms) { OS_ERR err; OSTimeDlyHMSM(0, 0, 0, ms, OS_OPT_TIME_DLY, &err); } /*=========================================================================== * NTC 温度计算(103AT 10kΩ NTC, β=3435K) *===========================================================================*/ static int8_t ntc_adc_to_temp(uint16_t adc_val) { if (adc_val >= 4090) return -40; /* 开路 — NTC 断线 */ if (adc_val <= 5) return 125; /* 短路 — NTC 焊连 */ float r_ntc = (float)(10000UL * adc_val) / (4095.0f - (float)adc_val); float temp_k = 1.0f / (1.0f/298.15f + logf(r_ntc/10000.0f)/3435.0f); float temp_c = temp_k - 273.15f; return (int8_t)(temp_c + 0.5f); } /*=========================================================================== * INT 中断初始化(GD32F303 平台相关) *===========================================================================*/ static void bq_int_init(void) { rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_AF); gpio_init(BQ_INT_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_2MHZ, BQ_INT_PIN); gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_1); exti_deinit(); exti_init(BQ_INT_EXTI_LINE, EXTI_INTERRUPT, EXTI_TRIG_FALLING); exti_interrupt_enable(BQ_INT_EXTI_LINE); nvic_irq_enable(BQ_INT_EXTI_IRQn, 0, 0); bq_int_mask_only_fault(); } /*=========================================================================== * INT 中断服务程序 *===========================================================================*/ void BQ_INT_EXTI_IRQHandler(void) { if (exti_interrupt_flag_get(BQ_INT_EXTI_LINE) != RESET) { uint8_t fault, f1, f2, ff; SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_FAULT_STATUS, &fault); g_bq_fault_status = fault; SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_FLAG_1, &f1); SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_FLAG_2, &f2); SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_FAULT_FLAG, &ff); g_bq_fault = 1; exti_interrupt_flag_clear(BQ_INT_EXTI_LINE); } } /*=========================================================================== * ChargeTask — 充电管理主循环(µC/OS-III 任务,优先级 5) *===========================================================================*/ static void ChargeTask(void *p_arg) { ChargeSM sm; ChargeSM_InitDefault(&sm); OS_ERR err; uint32_t wd_timer = 0; uint8_t pg_initialized = 0; uint8_t prev_pg = 0; while (1) { /* ——— PG 引脚监控 — 输入电源断线即时响应 ——— */ { uint8_t cur_pg = BQ_PG_READ(); if (!pg_initialized) { prev_pg = cur_pg; pg_initialized = 1; } else if (prev_pg && !cur_pg) { printf("[WARN] PG lost! Input power dropped. Stopping charge.\r\n"); bq_set_en_chg(0); BQ_CE_HIGH(); ChargeSM_ForceFault(&sm, "PG lost"); prev_pg = cur_pg; continue; } else if (!prev_pg && cur_pg) { printf("[OK] PG restored — input power returned\r\n"); } prev_pg = cur_pg; } /* ——— INT 中断故障检查 ——— */ if (g_bq_fault) { uint8_t fs = g_bq_fault_status; printf("[FAULT] INT: REG0x24=0x%02X | " "%s%s%s%s%s%s%s\r\n", fs, (fs & BQ25756_FAULT_VAC_UV) ? "VAC_UV " : "", (fs & BQ25756_FAULT_VAC_OV) ? "VAC_OV " : "", (fs & BQ25756_FAULT_IBAT_OCP) ? "IBAT_OCP " : "", (fs & BQ25756_FAULT_VBAT_OVP) ? "VBAT_OVP " : "", (fs & BQ25756_FAULT_TSHUT) ? "TSHUT " : "", (fs & BQ25756_FAULT_CHG_TMR) ? "CHG_TMR " : "", (fs & BQ25756_FAULT_DRV_OKZ) ? "DRV_SUP " : ""); bq_set_en_chg(0); BQ_CE_HIGH(); ChargeSM_ForceFault(&sm, "BQ25756 HW fault"); if (fs & BQ25756_FAULT_VBAT_OVP) { printf("[FAULT] BAT_OVP: enabling IBAT_LOAD to discharge output caps...\r\n"); /* Step 1: EN_IBAT_LOAD */ uint8_t ctrl = SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_CHARGER_CTRL); ctrl |= BQ25756_EN_IBAT_LOAD; SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_CHARGER_CTRL, ctrl); /* Step 2: Disable IBAT_ADC */ uint8_t adc_ch = SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_ADC_CHANNEL_CTRL); uint8_t adc_ch_saved = adc_ch; adc_ch |= 0x40; SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_ADC_CHANNEL_CTRL, adc_ch); /* Step 3: Wait 2s for discharge */ OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TIME_DLY, &err); /* Step 4: Restore IBAT_ADC */ SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_ADC_CHANNEL_CTRL, adc_ch_saved); /* Step 5: Clear IBAT_LOAD */ ctrl &= ~BQ25756_EN_IBAT_LOAD; SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_CHARGER_CTRL, ctrl); printf("[FAULT] BAT_OVP: IBAT_LOAD discharge complete\r\n"); g_bq_fault = 0; continue; } else if (fs & (BQ25756_FAULT_TSHUT | BQ25756_FAULT_DRV_OKZ)) { printf("[FAULT] Non-recoverable: initiating REG_RST...\r\n"); OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TIME_DLY, &err); uint8_t pwr = SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_POWER_PATH_REV); pwr |= BQ25756_REG_RST; SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_POWER_PATH_REV, pwr); OSTimeDlyHMSM(0, 0, 0, 10, OS_OPT_TIME_DLY, &err); int r = BQ25756_AI4_Init(); if (r != 0) { printf("[ERROR] REG_RST re-init failed step %d\r\n", r); } else { printf("[OK] REG_RST re-init complete\r\n"); bq_int_mask_only_fault(); SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_GATE_DRV_STRENGTH, 0x15); SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_GATE_DRV_DEAD_TIME, 0x05); } } else { OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TIME_DLY, &err); } g_bq_fault = 0; continue; } /* ——— CV_TMR_EXP 轮询检测 ——— */ { static uint8_t prev_cv_tmr = 0; uint8_t status3 = SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_CHARGER_STATUS_3); uint8_t cv_tmr = (status3 & 0x08) ? 1 : 0; if (!prev_cv_tmr && cv_tmr) { printf("[WARN] CV timer (4hr) expired at REG0x23=0x%02X\r\n", status3); ChargeSM_ForceFault(&sm, "CV timer expired"); } prev_cv_tmr = cv_tmr; } /* ——— DPM 状态监测 + CHARGE_STAT 交叉校验 ——— */ { static uint8_t dpm_diag_div = 0; if (++dpm_diag_div >= 10) { dpm_diag_div = 0; uint8_t status1 = SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_CHARGER_STATUS_1); uint8_t iac_dpm = (status1 & 0x40) ? 1 : 0; uint8_t vac_dpm = (status1 & 0x20) ? 1 : 0; uint8_t bq_state = (status1 & 0x07); static uint8_t prev_iac_dpm = 0, prev_vac_dpm = 0; if (iac_dpm && !prev_iac_dpm) printf("[DPM] IAC_DPM active\r\n"); if (vac_dpm && !prev_vac_dpm) printf("[DPM] VAC_DPM active\r\n"); if (!iac_dpm && prev_iac_dpm) printf("[DPM] IAC_DPM cleared\r\n"); if (!vac_dpm && prev_vac_dpm) printf("[DPM] VAC_DPM cleared\r\n"); prev_iac_dpm = iac_dpm; prev_vac_dpm = vac_dpm; static const char *bq_states[] = { "NOT_CHARGING","TRICKLE","PRECHG","CC","CV","?\5","TOPOFF","DONE" }; const char *mcustr = ChargeSM_StateStr(sm.state); const char *bqstr = (bq_state <= 7 && bq_state != 5) ? bq_states[bq_state] : "UNKNOWN"; uint8_t mismatch = 0; switch (sm.state) { case CHARGE_STATE_IDLE: if (bq_state != 0 && bq_state != 1) mismatch = 1; break; case CHARGE_STATE_PRECHARGE: if (bq_state != 1 && bq_state != 2) mismatch = 1; break; case CHARGE_STATE_CC: if (bq_state != 3) mismatch = 1; break; case CHARGE_STATE_CV: if (bq_state != 4 && bq_state != 7) mismatch = 1; break; case CHARGE_STATE_DONE: if (bq_state != 7 && bq_state != 0) mismatch = 1; break; case CHARGE_STATE_NO_BATTERY: if (bq_state != 0) mismatch = 1; break; } if (mismatch) { printf("[WARN] State mismatch: MCU=%s BQ=%s (0x%02X)\r\n", mcustr, bqstr, status1); } } } /* ——— 读取 ADC ——— */ g_vbat_mv = bq_read_vbat_mv(); g_ibat_ma = bq_read_ibat_ma(); /* ——— 输入 ChargeSM ——— */ ChargeSM_Decision d = ChargeSM_Run(&sm, g_vbat_mv, g_ibat_ma, g_temp_c); /* ——— STAT1/STAT2 硬件引脚读回(每 2s)——— */ { static uint8_t stat_div = 0; if (++stat_div >= 10) { stat_div = 0; uint8_t s1 = BQ_STAT1_READ(); uint8_t s2 = BQ_STAT2_READ(); const char *stat_str; if (!s1 && !s2) stat_str = "CHARGING"; else if (!s1 && s2) stat_str = "DONE"; else if ( s1 && !s2) stat_str = "FAULT"; else stat_str = "NO_INPUT"; static const char *prev_stat_str = ""; if (strcmp(stat_str, prev_stat_str)) { printf("[STAT] Pins: %s (S1=%d S2=%d) | MCU: %s\r\n", stat_str, s1, s2, ChargeSM_StateStr(sm.state)); prev_stat_str = stat_str; } if (s1 && !s2 && g_bq_fault == 0) { printf("[WARN] STAT pins report FAULT but MCU has no fault flag!\r\n"); } } } /* ——— ADC/MPPT/PFM/TS 诊断打印(每 10s)——— */ { static uint8_t diag_div = 0; if (++diag_div >= 50) { diag_div = 0; uint8_t mppt_stat = SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_CHARGER_STATUS_2) & 0x03; uint16_t vac_mpp = SoftI2C_ReadReg16(BQ25756_I2C_ADDR, BQ25756_REG_VAC_MPP_DETECTED); const char *mppt_s = (mppt_stat == 0) ? "OFF" : (mppt_stat == 1) ? "IDLE" : (mppt_stat == 2) ? "SWEEP" : "LOCK"; int32_t vin = bq_read_vin_mv(); int32_t iin = bq_read_iin_ma(); printf("[ADC] VIN=%ldmV IIN=%ldmA VBAT=%ldmV IBAT=%ldmA | MPPT=%s VMPP=%dmV\r\n", (long)vin, (long)iin, (long)g_vbat_mv, (long)g_ibat_ma, mppt_s, (int)vac_mpp); /* PFM/FSW_SYNC check */ bq_pfm_sync_check(); /* TS 交叉校验(每 30s) */ { static uint8_t ts_div = 0; if (++ts_div >= 3) { ts_div = 0; bq_ts_cross_check(g_temp_c); } } /* MPPT 智能重触发 */ { static int32_t last_vin = 0; if (mppt_stat == 3 && last_vin > 1000) { int32_t vin_diff = vin - last_vin; if (vin_diff < 0) vin_diff = -vin_diff; if (vin_diff > last_vin * 15 / 100) { printf("[MPPT] VAC step (%ld→%ldmV), forcing sweep...\r\n", (long)last_vin, (long)vin); bq_mppt_force_sweep(); } } last_vin = vin; } } } /* ——— 执行状态机决策 ——— */ switch (d.action) { case SM_ACTION_SET_PRECHG: bq_set_en_pfm(1); bq_write_precharge_current(d.target_current_ma); bq_set_en_chg(1); BQ_CE_LOW(); break; case SM_ACTION_SET_CC: bq_set_en_pfm(0); bq_set_en_chg(1); BQ_CE_LOW(); bq_soft_start(d.target_current_ma, rtos_delay_ms); break; case SM_ACTION_STOP: bq_set_en_chg(0); BQ_CE_HIGH(); break; case SM_ACTION_RESTART: bq_set_en_pfm(0); bq_write_charge_current(d.target_current_ma); bq_set_en_chg(1); BQ_CE_LOW(); break; case SM_ACTION_NONE: default: break; } /* PFM 模式落地 — 非预充状态强制 PWM */ if (sm.state != CHARGE_STATE_PRECHARGE) { bq_set_en_pfm(0); } /* ——— I2C 看门狗 + WD_EXP 检测 ——— */ wd_timer += CHARGE_TASK_PERIOD_MS; if (wd_timer >= WD_RESET_PERIOD_MS) { static uint8_t wd_exp_prev = 0; uint8_t flag1 = SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_FLAG_1); if (flag1 & 0x08) { wd_exp_prev++; if (wd_exp_prev >= 3) printf("[WARN] WD_EXP x%d — I2C may be unreliable!\r\n", wd_exp_prev); } else { wd_exp_prev = 0; } bq_reset_wd(); wd_timer = 0; } /* ——— 调试输出 ——— */ if (d.action != SM_ACTION_NONE) { printf("[CHG] %s | VBAT=%ldmV IBAT=%ldmA TEMP=%dC | %s\r\n", ChargeSM_StateStr(sm.state), (long)g_vbat_mv, (long)g_ibat_ma, (int)g_temp_c, d.reason); } OSTimeDlyHMSM(0, 0, 0, CHARGE_TASK_PERIOD_MS, OS_OPT_TIME_DLY, &err); } } /*=========================================================================== * AdcTask — NTC 温度采集任务(µC/OS-III 任务,优先级 4) *===========================================================================*/ static void ntc_adc_init(void) { rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_ADC0); gpio_init(NTC_ADC_PORT, GPIO_MODE_AIN, GPIO_OSPEED_2MHZ, NTC_ADC_PIN); adc_deinit(NTC_ADC_PERIPH); adc_channel_length_config(NTC_ADC_PERIPH, ADC_REGULAR_CHANNEL, 1); adc_regular_channel_config(NTC_ADC_PERIPH, 0, NTC_ADC_CHANNEL, ADC_SAMPLETIME_239POINT5); adc_external_trigger_source_config(NTC_ADC_PERIPH, ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE); adc_data_alignment_config(NTC_ADC_PERIPH, ADC_DATAALIGN_RIGHT); adc_resolution_config(NTC_ADC_PERIPH, ADC_RESOLUTION_12B); adc_enable(NTC_ADC_PERIPH); adc_calibration_enable(NTC_ADC_PERIPH); } static void AdcTask(void *p_arg) { OS_ERR err; ntc_adc_init(); while (1) { adc_software_trigger_enable(NTC_ADC_PERIPH, ADC_REGULAR_CHANNEL); while (!adc_flag_get(NTC_ADC_PERIPH, ADC_FLAG_EOC)); uint16_t adc_val = adc_regular_data_read(NTC_ADC_PERIPH); g_temp_c = ntc_adc_to_temp(adc_val); OSTimeDlyHMSM(0, 0, 0, 500, OS_OPT_TIME_DLY, &err); } } /*=========================================================================== * App_Charge_Init — 充电系统总初始化 *===========================================================================*/ void App_Charge_Init(void) { OS_ERR err; /* Step 1: 软件 I2C */ SoftI2C_Config i2c_cfg = { .scl_port = BQ_I2C_SCL_PORT, .scl_pin = BQ_I2C_SCL_PIN, .sda_port = BQ_I2C_SDA_PORT, .sda_pin = BQ_I2C_SDA_PIN, }; SoftI2C_Init(&i2c_cfg); /* Step 2: 控制引脚 */ rcu_periph_clock_enable(RCU_GPIOA); gpio_init(BQ_CE_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, BQ_CE_PIN); BQ_CE_HIGH(); gpio_init(BQ_PG_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_2MHZ, BQ_PG_PIN); gpio_init(BQ_STAT1_PORT, GPIO_MODE_IPU, GPIO_OSPEED_2MHZ, BQ_STAT1_PIN); gpio_init(BQ_STAT2_PORT, GPIO_MODE_IPU, GPIO_OSPEED_2MHZ, BQ_STAT2_PIN); /* Step 3: BQ25756 硬件初始化 */ int ret = BQ25756_AI4_Init(); if (ret != 0) { printf("[ERROR] BQ25756 init failed, step %d\r\n", ret); return; } printf("[OK] BQ25756 AI-4 init complete (v3, 14 steps)\r\n"); /* Step 4: INT 中断 */ bq_int_init(); printf("[OK] INT interrupt enabled (fault-only mode)\r\n"); /* Step 4.5: Gate Driver */ SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_GATE_DRV_STRENGTH, 0x15); SoftI2C_WriteReg(BQ25756_I2C_ADDR, BQ25756_REG_GATE_DRV_DEAD_TIME, 0x05); printf("[OK] Gate driver: Slower + 75ns deadtime\r\n"); /* Step 4.5.5: VFB_ADC POST 自检 */ { int vfb_ret = bq_vfb_post_check(19.34f, 100, rtos_delay_ms); if (vfb_ret == -2) printf("[ERROR] VFB_POST failed — charging DISABLED\r\n"); else if (vfb_ret == 0) printf("[OK] VFB_POST: FB divider verified\r\n"); } /* Step 4.6: MPPT 启动 */ bq_mppt_start(); printf("[OK] MPPT enabled (P&O=1s, FS=20min)\r\n"); /* Step 4.7: VAC 工作窗口诊断 */ { int32_t vin = bq_read_vin_mv(); uint8_t pin_cfg = SoftI2C_ReadReg(BQ25756_I2C_ADDR, BQ25756_REG_PIN_CTRL); printf("[VAC] Input: %ldmV | Pin_CTRL=0x%02X\r\n", (long)vin, pin_cfg); if (vin < 3000) printf("[VAC] WARNING: No input voltage (<3V)\r\n"); else if (vin > 0 && vin < 4200) printf("[VAC] WARNING: VAC=%ldmV below VVAC_OK min\r\n", (long)vin); } /* Step 5: 创建 RTOS 任务 */ OSTaskCreate((OS_TCB *)&ChargeTaskTCB, (CPU_CHAR *)"Charge", (OS_TASK_PTR )ChargeTask, (void *)0, (OS_PRIO )CHARGE_TASK_PRIO, (CPU_STK *)ChargeTaskStk, (CPU_STK_SIZE )CHARGE_TASK_STACK_SIZE / 10, (CPU_STK_SIZE )CHARGE_TASK_STACK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); OSTaskCreate((OS_TCB *)&AdcTaskTCB, (CPU_CHAR *)"ADC", (OS_TASK_PTR )AdcTask, (void *)0, (OS_PRIO )ADC_TASK_PRIO, (CPU_STK *)AdcTaskStk, (CPU_STK_SIZE )ADC_TASK_STACK_SIZE / 10, (CPU_STK_SIZE )ADC_TASK_STACK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); printf("[OK] Charge + ADC tasks created\r\n"); }