generated from hulk/gd32f4xx_template_cmake_vscode
Heater PID closed-loop on PB12 (HEAT_EN) with safety protections
This commit is contained in:
+18
-31
@@ -16,6 +16,7 @@
|
||||
#include "uart.h"
|
||||
#include "systick.h"
|
||||
#include "ntc.h"
|
||||
#include "heat.h"
|
||||
#include "board_config.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
@@ -68,22 +69,10 @@ static uint16_t s_temp_report_period_ms = 500; /**< 上报间隔(ms),100~6
|
||||
static uint16_t s_temp_report_counter = 0; /**< 主循环 10ms 圈计数 */
|
||||
/** @} */
|
||||
|
||||
/** @name M3/M4 加热控制状态(预留:当前仅软件状态,硬件执行体待接入)
|
||||
/** @name M3/M4 加热控制(heat 模块:PB12 慢 PWM + PID 闭环)
|
||||
* @{ */
|
||||
static bool s_heater_on = false; /**< 加热开关状态 */
|
||||
static float s_heater_target = 0.0f; /**< 目标温度(摄氏度,M4S 设定) */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief 加热输出执行体(按开关状态与目标温度驱动加热器件)。
|
||||
* @note TODO: 加热驱动(MOS/PWM/继电器)接入后在此实现真实输出;
|
||||
* 当前 M3/M4 仅维护软件状态(GUI 可先行联调协议)。
|
||||
*/
|
||||
static void heater_apply(void)
|
||||
{
|
||||
/* 占位:接入加热驱动后替换为真实硬件输出 */
|
||||
}
|
||||
|
||||
void system_software_reset(void)
|
||||
{
|
||||
// 确保所有待处理的内存访问完成
|
||||
@@ -354,10 +343,10 @@ static bool __attribute__((unused)) find_parameter_value(const uint8_t *cmd, uin
|
||||
* - M1: 单次查询温度值(payload 为 ASCII 摄氏度,如 "23.8";OPEN/SHRT=故障)
|
||||
* - M2: 查询连续上报状态(payload: "ON <ms>" 或 "OFF")
|
||||
* - M2S<ms>: 设置连续上报间隔(100~60000)并开启;M2S0 停止
|
||||
* - M3: 查询加热状态(payload: "ON" / "OFF")
|
||||
* - M3S1/M3S0: 开启/关闭加热(预留,当前仅软件状态)
|
||||
* - M3: 查询加热使能(payload: "ON" / "OFF")
|
||||
* - M3S1/M3S0: 开启/关闭加热(PB12 慢 PWM,PID 按目标温度闭环)
|
||||
* - M4: 查询目标温度(payload: ASCII 摄氏度)
|
||||
* - M4S<temp>: 设定目标温度(整数摄氏度,0~200;预留,当前仅软件状态)
|
||||
* - M4S<temp>: 设定目标温度(整数摄氏度,0~200)
|
||||
* - M888: 软件复位
|
||||
* - M999: 输出固件版本号(v<板卡码>.<主>.<次>.<修订>)
|
||||
* - M9999: 进入 OTA 模式(复位进 Bootloader)
|
||||
@@ -466,11 +455,11 @@ void handle_command(const uint8_t *frame, uint8_t len) {
|
||||
return;
|
||||
|
||||
/* ==========================================
|
||||
* M3 加热开关(预留,当前仅软件状态)
|
||||
* M3 : 查询加热状态(payload: "ON" / "OFF")
|
||||
* M3S1 : 开启加热
|
||||
* M3S0 : 关闭加热
|
||||
* 注意:上电默认关闭;看门狗/异常处理策略待控温闭环设计时补充。
|
||||
* M3 加热开关(PB12 慢 PWM 驱动 + PID 闭环)
|
||||
* M3 : 查询加热使能(payload: "ON" / "OFF")
|
||||
* M3S1 : 开启加热(使能后由 PID 按目标温度闭环输出)
|
||||
* M3S0 : 关闭加热(立即断输出并复位 PID)
|
||||
* 安全:上电默认关闭;NTC 故障/过温时强制关断。
|
||||
* ========================================== */
|
||||
case 3u:
|
||||
{
|
||||
@@ -478,23 +467,22 @@ void handle_command(const uint8_t *frame, uint8_t len) {
|
||||
|
||||
if (find_parameter_value(&cmd[cmd_index], (uint8_t)(cmd_len - cmd_index), 'S', &onoff)) {
|
||||
if (onoff == 0U || onoff == 1U) {
|
||||
s_heater_on = (onoff == 1U);
|
||||
heater_apply();
|
||||
heat_set_enable(onoff == 1U);
|
||||
send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
|
||||
} else {
|
||||
send_response(RESP_TYPE_PARAM_ERR, s_report_status_err, sizeof(s_report_status_err));
|
||||
}
|
||||
} else {
|
||||
send_response(RESP_TYPE_OK,
|
||||
s_heater_on ? (const uint8_t *)"ON" : (const uint8_t *)"OFF",
|
||||
s_heater_on ? 2U : 3U);
|
||||
heat_get_enable() ? (const uint8_t *)"ON" : (const uint8_t *)"OFF",
|
||||
heat_get_enable() ? 2U : 3U);
|
||||
}
|
||||
COMMAND_DEBUG("M3 heater %s", s_heater_on ? "ON" : "OFF");
|
||||
COMMAND_DEBUG("M3 heater %s", heat_get_enable() ? "ON" : "OFF");
|
||||
}
|
||||
return;
|
||||
|
||||
/* ==========================================
|
||||
* M4 温度设定(预留,当前仅软件状态)
|
||||
* M4 温度设定(PID 目标值)
|
||||
* M4 : 查询目标温度(payload: ASCII 摄氏度,如 "120.0")
|
||||
* M4S<temp> : 设定目标温度(整数摄氏度,HEATER_TEMP_MIN~MAX)
|
||||
* ========================================== */
|
||||
@@ -504,18 +492,17 @@ void handle_command(const uint8_t *frame, uint8_t len) {
|
||||
|
||||
if (find_parameter_value(&cmd[cmd_index], (uint8_t)(cmd_len - cmd_index), 'S', &target)) {
|
||||
if (target >= HEATER_TEMP_MIN && target <= HEATER_TEMP_MAX) {
|
||||
s_heater_target = (float)target;
|
||||
heater_apply();
|
||||
heat_set_target((float)target);
|
||||
send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
|
||||
} else {
|
||||
send_response(RESP_TYPE_PARAM_ERR, s_report_status_err, sizeof(s_report_status_err));
|
||||
}
|
||||
} else {
|
||||
char target_str[8];
|
||||
uint8_t n = (uint8_t)sprintf(target_str, "%.1f", s_heater_target);
|
||||
uint8_t n = (uint8_t)sprintf(target_str, "%.1f", heat_get_target());
|
||||
send_response(RESP_TYPE_OK, (const uint8_t *)target_str, n);
|
||||
}
|
||||
COMMAND_DEBUG("M4 target: %.1f", s_heater_target);
|
||||
COMMAND_DEBUG("M4 target: %.1f", heat_get_target());
|
||||
}
|
||||
return;
|
||||
|
||||
|
||||
+171
@@ -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;
|
||||
}
|
||||
+4
-1
@@ -13,6 +13,7 @@
|
||||
#include "rs485.h"
|
||||
#include "command.h"
|
||||
#include "ntc.h"
|
||||
#include "heat.h"
|
||||
#include "led.h"
|
||||
#include "pwr_hold.h"
|
||||
#include <stdio.h>
|
||||
@@ -38,6 +39,7 @@ int main(void)
|
||||
|
||||
rs485_init();
|
||||
ntc_init();
|
||||
heat_init();
|
||||
|
||||
#if DEBUG_MODE == CFG_ENABLE
|
||||
printf("FilmForge FW %s, built %s\r\n", PROJECT_VERSION, BUILD_DATE);
|
||||
@@ -52,8 +54,9 @@ int main(void)
|
||||
/* RS485 数据口命令处理(D5 帧协议,具体业务命令在 command.c 扩展) */
|
||||
command_process();
|
||||
|
||||
/* NTC 后台采样(100ms 一次)+ M2 连续上报任务 */
|
||||
/* NTC 后台采样(100ms 一次)+ 加热 PID/PWM 任务 + M2 连续上报任务 */
|
||||
ntc_sample_task();
|
||||
heat_task();
|
||||
command_report_task();
|
||||
|
||||
/* debug 串口周期打印 NTC 温度(约 1s 一次,读后台采样全局值) */
|
||||
|
||||
Reference in New Issue
Block a user