Files
FilmForge/Src/heat.c
T

201 lines
6.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 float s_ramp_target = 0.0f; /* 内部斜坡目标(以 HEATER_RAMP_CPS 滑向 s_target) */
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;
}
/* 目标斜坡:内部设定值以受限速率滑向 s_target。
满功率爬升段 ramp 超前于实际温升(不受影响);接近目标后 ramp 先到站停住,
误差随实际温度逼近而平滑收缩 -> 功率缓退,抑制热传导滞后造成的过冲。 */
float diff = s_target - s_ramp_target;
float step = HEATER_RAMP_CPS * (HEATER_PID_PERIOD_MS / 1000.0f);
if (diff > step) {
s_ramp_target += step;
} else if (diff < -step) {
s_ramp_target -= step;
} else {
s_ramp_target = s_target;
}
/* 位置式 PID(带抗积分饱和):输出饱和时积分仅沿退饱和方向更新,
防止升温段积分顶满后长时间维持 100% 输出(此前实测目标 50℃ 冲到 59℃) */
float err = s_ramp_target - t;
float u_noi = HEATER_PID_KP * err
+ HEATER_PID_KD * (err - s_prev_err); /* 比例 + 微分 */
float integ_new = s_integral + err;
/* 超温区间(实际温度高于内部目标):加热器只能加热、不能制冷,
输出必须为 0(此前积分正残量会算出小占空在超调段续灌加热);
积分按负误差同步回退,为温度回落后的重新加热保留正确初始态 */
if (err < 0.0f) {
s_integral = integ_new;
s_prev_err = err;
s_duty10 = 0;
} else {
/* 误差为正(未达/低于内部目标):正常 PID + 抗积分饱和 */
float u = u_noi + HEATER_PID_KI * integ_new;
if (u > 100.0f) {
u = 100.0f;
/* 超上限:仅误差为负才吸收——err>=0 分支不会发生,保持积分不动 */
} else {
s_integral = integ_new;
}
/* 输出量化到 10% 档位(四舍五入) */
s_duty10 = (uint8_t)((u + (float)HEATER_PWM_STEP_PCT / 2.0f) / (float)HEATER_PWM_STEP_PCT);
}
s_prev_err = err;
}
/******************************************************************************/
/* 公共接口 */
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) {
/* 从当前实际温度起坡,避免内部目标从 0 跳变造成功率冲击 */
s_ramp_target = ntc_get_temperature();
if (s_ramp_target < 0.0f) {
s_ramp_target = 0.0f; /* 传感器故障标记值非法,退化为 0 */
}
} else {
s_ramp_target = 0.0f;
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;
}