/** * @file heat.c * @brief 加热控制:慢 PWM 驱动(PB12)+ PID 控温闭环 + 安全保护。 * @details 硬件:PB12=HEAT_EN,高 = 加热开(2N7002K 电平转换 → VBE2412 PMOS → 24V)。 * 开关频率约束(<=10Hz)由"1s 载波周期 + 10% 占空步进"保证——每档脉宽 * 恒为 100ms 的整数倍。PID 以 500ms 周期计算(NTC 后台采样 100ms), * 输出量化到 0~10 档占空。 * @note 安全保护:上电默认关闭;NTC 传感器故障(OPEN/SHRT)强制关断; * 过温(>= HEATER_OVERTEMP_C)滞回保护;未使能强制关断。 */ #include "heat.h" #include "ntc.h" #include "gd32f4xx_rcu.h" #include "gd32f4xx_gpio.h" #include "board_config.h" /******************************************************************************/ /* 内部状态 */ static bool s_enable = false; /* 加热使能(M3) */ static float s_target = 0.0f; /* 目标温度(M4) */ static uint8_t s_duty10 = 0; /* 量化占空档位 0~10(x10%) */ static bool s_overtemp = false; /* 过温锁存(滞回恢复) */ static float s_integral = 0.0f; /* PID 积分项 */ static float s_prev_err = 0.0f; /* PID 上次误差 */ static uint16_t s_pwm_phase_ms = 0; /* PWM 载波相位 0..PERIOD-1 */ static uint16_t s_pid_divider = 0; /* PID 周期分频(10ms 圈) */ /******************************************************************************/ /* 底层输出 */ static void heat_out(bool on); static void heat_out(bool on) { if (on) { gpio_bit_set(HEATER_EN_PORT, HEATER_EN_PIN); } else { gpio_bit_reset(HEATER_EN_PORT, HEATER_EN_PIN); } } void heat_init(void) { /* PB12 推挽输出低:上电默认加热关 */ rcu_periph_clock_enable(HEATER_EN_RCU); gpio_mode_set(HEATER_EN_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, HEATER_EN_PIN); gpio_output_options_set(HEATER_EN_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, HEATER_EN_PIN); heat_out(false); } /******************************************************************************/ /* PID */ /** * @brief 复位 PID 积分与历史误差(使能切换/传感器故障时调用,防积分残留冲击)。 */ static void pid_reset(void) { s_integral = 0.0f; s_prev_err = 0.0f; } /** * @brief PID 计算(500ms 周期):误差 -> 占空档位 s_duty10(0~10)。 */ static void pid_compute(void) { float t = ntc_get_temperature(); /* 传感器故障(开路/短路):安全第一,强制关断并复位 PID */ if (t <= -273.0f) { s_duty10 = 0; pid_reset(); return; } /* 未使能:强制关断 */ if (!s_enable) { s_duty10 = 0; pid_reset(); return; } /* 过温滞回保护:达到阈值锁存关断,降回阈值-滞回量后恢复 PID */ if (t >= HEATER_OVERTEMP_C) { s_overtemp = true; } else if (t <= (HEATER_OVERTEMP_C - HEATER_OVERTEMP_HYST)) { s_overtemp = false; } if (s_overtemp) { s_duty10 = 0; pid_reset(); return; } /* 位置式 PID(带抗积分饱和):输出饱和时积分仅沿退饱和方向更新, 防止升温段积分顶满后长时间维持 100% 输出(此前实测目标 50℃ 冲到 59℃) */ float err = s_target - t; float u_noi = HEATER_PID_KP * err + HEATER_PID_KD * (err - s_prev_err); /* 比例 + 微分 */ float integ_new = s_integral + err; float u = u_noi + HEATER_PID_KI * integ_new; if (u > 100.0f) { u = 100.0f; if (err < 0.0f) s_integral = integ_new; /* 超上限:仅误差为负(降温方向)才吸收 */ } else if (u < 0.0f) { u = 0.0f; if (err > 0.0f) s_integral = integ_new; /* 超下限:仅误差为正(升温方向)才吸收 */ } else { s_integral = integ_new; /* 未饱和:正常积分 */ } s_prev_err = err; /* 输出量化到 10% 档位(四舍五入) */ s_duty10 = (uint8_t)((u + (float)HEATER_PWM_STEP_PCT / 2.0f) / (float)HEATER_PWM_STEP_PCT); } /******************************************************************************/ /* 公共接口 */ void heat_task(void) { uint8_t prev_duty = s_duty10; /* PWM 载波相位推进(10ms tick) */ s_pwm_phase_ms += 10U; if (s_pwm_phase_ms >= HEATER_PWM_PERIOD_MS) { s_pwm_phase_ms = 0U; } /* PID 周期分频 */ s_pid_divider += 10U; if (s_pid_divider >= HEATER_PID_PERIOD_MS) { s_pid_divider = 0U; pid_compute(); if (s_duty10 != prev_duty) { s_pwm_phase_ms = 0U; /* 占空变更:从新周期起点开始,响应即时 */ } } /* 按占空档位输出:0 档常关、10 档常开、其余按相位斩波 */ uint16_t on_ms = (uint16_t)s_duty10 * (HEATER_PWM_PERIOD_MS / 10U); if (s_duty10 == 0U) { heat_out(false); } else if (s_duty10 >= 10U) { heat_out(true); } else { heat_out(s_pwm_phase_ms < on_ms); } } void heat_set_enable(bool on) { if (s_enable == on) return; /* 幂等 */ s_enable = on; if (!on) { s_duty10 = 0; pid_reset(); heat_out(false); } } bool heat_get_enable(void) { return s_enable; } void heat_set_target(float temp_c) { s_target = temp_c; } float heat_get_target(void) { return s_target; } uint8_t heat_get_duty(void) { return s_duty10; }