Files
FilmForge/Src/heat.c
T

172 lines
5.1 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 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 */
float err = s_target - t;
s_integral += err;
{
/* 积分限幅(抗饱和):积分项贡献不超过满量程 */
float imax = 100.0f / HEATER_PID_KI;
if (s_integral > imax) s_integral = imax;
if (s_integral < -imax) s_integral = -imax;
}
float deriv = err - s_prev_err;
s_prev_err = err;
float u = HEATER_PID_KP * err + HEATER_PID_KI * s_integral + HEATER_PID_KD * deriv;
if (u < 0.0f) u = 0.0f; /* 降温靠自然散热,输出不为负 */
if (u > 100.0f) u = 100.0f;
/* 输出量化到 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;
}