Heater PID closed-loop on PB12 (HEAT_EN) with safety protections

This commit is contained in:
2026-10-10 10:04:48 +08:00
parent 6246a87c3c
commit 30540d884d
8 changed files with 276 additions and 40 deletions
+171
View File
@@ -0,0 +1,171 @@
/**
* @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;
}