前馈+PI调参记录
最近搞了一个测PL的加热样品台计划使用前馈PI的方法来进行温度控制。首先使用恒定功率加热获得稳态温度用于前馈参数的确定。不过由于我加热棒功率选小了整个样品台的热容又比较大升温到稳态非常慢。因此我先加热到差不多快稳态的温度然后使用公式来拟合这里可以直接使用origin软件的曲线拟合工具。前馈参数的确定cubeMonitor的使用温度曲线数据获取我这里用的是stm32cubeMonitor打开以后自带一个work flow首先双击上面的my variables然后点击加号选择之前在cubeIDE编译后生成的elf文件然后再variable list里面同时选择上想要监测的变量值 我这里吧加热功率跟温度都选了温度数据不需要获取太频繁选择1Hz在work flow的my probe out这里设置同样 在my probe in这里同样设置自己的stlink然后设置下面的my variables因为我这里是记录两个数据因此选择multiple columns。设置完成后点击右上角的deploy然后点击dashboard就可以打开记录界面结果是自动保存在C盘 user 用户名 logs里面。以csv格式存储。恒功率拟合结果PWM占空比Y0稳态20%80.765640%118.931160%150.452980%176.3988100%196.6839用二阶多项式拟合将拟合得到的系数用于前馈部分的计算代码在下面。初始Kp Ki系数的估计方法Kp的估计维持目标温度所需的功率随温度的变化率为其中a和b为也就是之前用于拟合温度与功率的a和b一般取Kp在0.2~0.5倍S大小Ki的估计Ki与热时间常数有关也就是之前用于拟合稳态温度使用的单指数公式中的tau一般来说对于响应比较慢的系统积分时间Ti等于tau~2tau范围。加热温控程序heater_control.c#include heater_control.h #include math.h #include stddef.h #define HEATER_CONTROL_EPSILON (1.0e-6f) static float HeaterControl_Clamp(float value, float minimum, float maximum) { if (isnan(value)) { return minimum; } if (value maximum) { return maximum; } if (value minimum) { return minimum; } return value; } static float HeaterControl_ModelTemperature( const HeaterControl_t *controller, float duty) { return controller-config.a * duty controller-config.b * duty * duty controller-config.c; } static float HeaterControl_Absolute(float value) { return (value 0.0f) ? value : -value; } static float HeaterControl_FindClosestSafeDuty( const HeaterControl_t *controller, float target_temperature) { float duty_min controller-config.output_min; float duty_max controller-config.output_max; float best_duty duty_min; float best_error 0.0f; uint8_t candidate_found 0U; float candidates[3]; uint8_t candidate_count 0U; uint8_t index; /* 只在模型斜率为正的区域以及抛物线顶点中选择安全工作点。 */ if ((controller-config.a 2.0f * controller-config.b * duty_min) HEATER_CONTROL_EPSILON) { candidates[candidate_count] duty_min; } if ((controller-config.a 2.0f * controller-config.b * duty_max) HEATER_CONTROL_EPSILON) { candidates[candidate_count] duty_max; } if (HeaterControl_Absolute(controller-config.b) HEATER_CONTROL_EPSILON) { float vertex -controller-config.a / (2.0f * controller-config.b); if ((vertex duty_min) (vertex duty_max)) { candidates[candidate_count] vertex; } } for (index 0U; index candidate_count; index) { float error HeaterControl_Absolute( target_temperature - HeaterControl_ModelTemperature(controller, candidates[index])); if ((candidate_found 0U) || (error best_error)) { best_error error; best_duty candidates[index]; candidate_found 1U; } } /* 整个输出区间都处于负斜率区时退回最小输出。 */ return (candidate_found ! 0U) ? best_duty : duty_min; } void HeaterControl_Init( HeaterControl_t *controller, const HeaterControl_Config_t *config) { if ((controller NULL) || (config NULL)) { return; } controller-config *config; if (!isfinite(controller-config.a)) { controller-config.a 0.0f; } if (!isfinite(controller-config.b)) { controller-config.b 0.0f; } if (!isfinite(controller-config.c)) { controller-config.c 0.0f; } if (!isfinite(controller-config.output_min)) { controller-config.output_min 0.0f; } if (!isfinite(controller-config.output_max)) { controller-config.output_max 100.0f; } if (controller-config.output_min controller-config.output_max) { float temporary controller-config.output_min; controller-config.output_min controller-config.output_max; controller-config.output_max temporary; } if (controller-config.integral_min controller-config.integral_max) { float temporary controller-config.integral_min; controller-config.integral_min controller-config.integral_max; controller-config.integral_max temporary; } if (!isfinite(controller-config.integral_min)) { controller-config.integral_min -100.0f; } if (!isfinite(controller-config.integral_max)) { controller-config.integral_max 100.0f; } if (controller-config.integral_min controller-config.integral_max) { float temporary controller-config.integral_min; controller-config.integral_min controller-config.integral_max; controller-config.integral_max temporary; } if ((!isfinite(controller-config.sample_time_s)) || (controller-config.sample_time_s 0.0f)) { controller-config.sample_time_s 0.2f; } if ((!isfinite(controller-config.kp_factor)) || (controller-config.kp_factor 0.0f)) { controller-config.kp_factor 0.0f; } if ((!isfinite(controller-config.ki_tau_factor)) || (controller-config.ki_tau_factor 0.0f)) { controller-config.ki_tau_factor 0.0f; } if ((!isfinite(controller-config.tau_s)) || (controller-config.tau_s 0.0f)) { controller-config.tau_s 0.0f; } HeaterControl_Reset(controller); } void HeaterControl_Reset(HeaterControl_t *controller) { if (controller NULL) { return; } controller-integral 0.0f; controller-error 0.0f; controller-model_slope 0.0f; controller-sensitivity 0.0f; controller-dynamic_kp 0.0f; controller-dynamic_ki 0.0f; controller-feedforward 0.0f; controller-p_term 0.0f; controller-i_term 0.0f; controller-output 0.0f; } float HeaterControl_CalculateFeedforward( const HeaterControl_t *controller, float target_temperature) { float duty_min; float duty_max; float a; float b; float c; if (controller NULL) { return 0.0f; } if (!isfinite(target_temperature)) { return 0.0f; } if ((!isfinite(controller-config.a)) || (!isfinite(controller-config.b)) || (!isfinite(controller-config.c)) || (!isfinite(controller-config.output_min)) || (!isfinite(controller-config.output_max)) || (controller-config.output_min controller-config.output_max)) { return 0.0f; } duty_min controller-config.output_min; duty_max controller-config.output_max; a controller-config.a; b controller-config.b; c controller-config.c; /* 解方程bP^2 aP (c - target) 0 */ if (HeaterControl_Absolute(b) HEATER_CONTROL_EPSILON) { /* b接近0时退化为线性模型T aP c */ if (a HEATER_CONTROL_EPSILON) { return duty_min; } return HeaterControl_Clamp( (target_temperature - c) / a, duty_min, duty_max); } else { float discriminant; float square_root; float root_1; float root_2; uint8_t root_1_valid; uint8_t root_2_valid; uint8_t root_1_increasing; uint8_t root_2_increasing; discriminant a * a - 4.0f * b * (c - target_temperature); if (discriminant 0.0f) { return HeaterControl_FindClosestSafeDuty( controller, target_temperature); } square_root sqrtf(discriminant); root_1 (-a square_root) / (2.0f * b); root_2 (-a - square_root) / (2.0f * b); root_1_valid ((root_1 duty_min) (root_1 duty_max)) ? 1U : 0U; root_2_valid ((root_2 duty_min) (root_2 duty_max)) ? 1U : 0U; /* 模型斜率dT/dP a 2bP正常加热曲线应取正斜率根。 */ root_1_increasing ((a 2.0f * b * root_1) 0.0f) ? 1U : 0U; root_2_increasing ((a 2.0f * b * root_2) 0.0f) ? 1U : 0U; if ((root_1_valid ! 0U) (root_1_increasing ! 0U)) { return root_1; } if ((root_2_valid ! 0U) (root_2_increasing ! 0U)) { return root_2; } /* 不使用负斜率根在正斜率边界和顶点中选择最接近的值。 */ return HeaterControl_FindClosestSafeDuty( controller, target_temperature); } } float HeaterControl_Update( HeaterControl_t *controller, float target_temperature, float measured_temperature) { float proposed_integral; float unsaturated_output; if (controller NULL) { return 0.0f; } if ((!isfinite(target_temperature)) || (!isfinite(measured_temperature)) || (!isfinite(controller-config.a)) || (!isfinite(controller-config.b)) || (!isfinite(controller-config.c)) || (!isfinite(controller-config.output_min)) || (!isfinite(controller-config.output_max)) || (!isfinite(controller-config.integral_min)) || (!isfinite(controller-config.integral_max)) || (!isfinite(controller-config.sample_time_s)) || (!isfinite(controller-config.kp_factor)) || (!isfinite(controller-config.ki_tau_factor)) || (!isfinite(controller-config.tau_s)) || (controller-config.output_min controller-config.output_max) || (controller-config.integral_min controller-config.integral_max) || (controller-config.sample_time_s 0.0f) || (controller-config.kp_factor 0.0f) || (controller-config.ki_tau_factor 0.0f) || (controller-config.tau_s 0.0f)) { HeaterControl_Reset(controller); return 0.0f; } /* 1. 计算误差 */ controller-error target_temperature - measured_temperature; if (!isfinite(controller-error)) { HeaterControl_Reset(controller); return 0.0f; } /* 2. 计算目标温度对应的前馈占空比 */ controller-feedforward HeaterControl_CalculateFeedforward( controller, target_temperature); if (!isfinite(controller-feedforward)) { HeaterControl_Reset(controller); return 0.0f; } /* 3. 根据前馈工作点计算动态Kp、Ki */ /* * 当前目标温度所对应的稳态工作点为 feedforward。 * * 温度模型 * T aP bP^2 c * * 模型斜率 * dT/dP a 2bP * * 灵敏度 * S dP/dT 1 / (dT/dP) */ controller-model_slope controller-config.a 2.0f * controller-config.b * controller-feedforward; if (!isfinite(controller-model_slope)) { HeaterControl_Reset(controller); return 0.0f; } /* * 只允许在正斜率区域计算动态PI参数。 * 正常加热系统中占空比增加时稳态温度应升高。 */ if (controller-model_slope HEATER_CONTROL_EPSILON) { controller-sensitivity 1.0f / controller-model_slope; controller-dynamic_kp controller-config.kp_factor * controller-sensitivity; if ((controller-config.ki_tau_factor HEATER_CONTROL_EPSILON) (controller-config.tau_s HEATER_CONTROL_EPSILON)) { controller-dynamic_ki controller-dynamic_kp / (controller-config.ki_tau_factor * controller-config.tau_s); } else { /* 参数无效时暂时关闭积分 */ controller-dynamic_ki 0.0f; } } else { /* * 模型斜率为零或负数说明拟合曲线在该工作点异常。 * 为防止除零或增益反向关闭PI修正。 */ controller-sensitivity 0.0f; controller-dynamic_kp 0.0f; controller-dynamic_ki 0.0f; } /* 动态比例项 */ controller-p_term controller-dynamic_kp * controller-error; /* 动态积分项 */ proposed_integral controller-integral controller-dynamic_ki * controller-error * controller-config.sample_time_s; if (!isfinite(proposed_integral)) { HeaterControl_Reset(controller); return 0.0f; } proposed_integral HeaterControl_Clamp( proposed_integral, controller-config.integral_min, controller-config.integral_max); unsaturated_output controller-feedforward controller-p_term proposed_integral; if (!isfinite(unsaturated_output)) { HeaterControl_Reset(controller); return 0.0f; } /* 条件积分抗饱和。 */ if (!((unsaturated_output controller-config.output_max controller-error 0.0f) || (unsaturated_output controller-config.output_min controller-error 0.0f))) { controller-integral proposed_integral; } controller-i_term controller-integral; unsaturated_output controller-feedforward controller-p_term controller-i_term; controller-output HeaterControl_Clamp( unsaturated_output, controller-config.output_min, controller-config.output_max); return controller-output; } void HeaterControl_SetDynamicPI( HeaterControl_t *controller, float kp_factor, float ki_tau_factor, float tau_s) { if (controller NULL) { return; } if ((!isfinite(kp_factor)) || (kp_factor 0.0f)) { kp_factor 0.0f; } if ((!isfinite(ki_tau_factor)) || (ki_tau_factor 0.0f)) { ki_tau_factor 0.0f; } if ((!isfinite(tau_s)) || (tau_s 0.0f)) { tau_s 0.0f; } controller-config.kp_factor kp_factor; controller-config.ki_tau_factor ki_tau_factor; controller-config.tau_s tau_s; } void HeaterControl_SetFeedforwardCoefficients( HeaterControl_t *controller, float a, float b, float c) { if (controller NULL) { return; } if ((!isfinite(a)) || (!isfinite(b)) || (!isfinite(c))) { return; } controller-config.a a; controller-config.b b; controller-config.c c; }heater_control.h#ifndef HEATER_CONTROL_H #define HEATER_CONTROL_H #ifdef __cplusplus extern C { #endif #include stdint.h /* * 前馈模型 * * T a * P b * P^2 c * * T稳态温度单位 °C * P加热占空比范围通常为 0~100 * * 控制器会根据目标温度反解前馈占空比 Pff * 然后叠加 PI 修正 * * Pout Pff Kp * error integral */ typedef struct { /* 前馈拟合系数T aP bP^2 c */ float a; float b; float c; /* * 动态PI参数 * * kp_factor * Kp kp_factor * S * 推荐初始范围0.2 ~ 0.5 * * ki_tau_factor * Ki Kp / (ki_tau_factor * tau_s) * 推荐初始范围1.0 ~ 2.0 * * tau_s * 热系统时间常数单位秒 */ float kp_factor; float ki_tau_factor; float tau_s; /* 每次调用 HeaterControl_Update() 的时间间隔单位秒 */ float sample_time_s; /* 最终输出限幅通常设置为0和100 */ float output_min; float output_max; /* 积分项限幅 */ float integral_min; float integral_max; } HeaterControl_Config_t; typedef struct { HeaterControl_Config_t config; float integral; /* 以下变量便于调试和OLED显示 */ float error; /* 当前工作点计算出的动态参数便于调试 */ float model_slope; float sensitivity; float dynamic_kp; float dynamic_ki; float feedforward; float p_term; float i_term; float output; } HeaterControl_t; void HeaterControl_Init( HeaterControl_t *controller, const HeaterControl_Config_t *config); void HeaterControl_Reset(HeaterControl_t *controller); float HeaterControl_CalculateFeedforward( const HeaterControl_t *controller, float target_temperature); float HeaterControl_Update( HeaterControl_t *controller, float target_temperature, float measured_temperature); void HeaterControl_SetDynamicPI( HeaterControl_t *controller, float kp_factor, float ki_tau_factor, float tau_s); void HeaterControl_SetFeedforwardCoefficients( HeaterControl_t *controller, float a, float b, float c); #ifdef __cplusplus } #endif #endif /* HEATER_CONTROL_H */

相关新闻

C++并发编程实战:避免多锁死锁的工程化解决方案

C++并发编程实战:避免多锁死锁的工程化解决方案

1. 项目概述:从一道经典面试题说起最近在帮团队面试C后端开发的同学,发现一个挺有意思的现象:很多候选人能把互斥锁(mutex)、条件变量(condition variable)这些概念背得滚瓜烂熟,甚至…

2026/7/15 3:36:47阅读更多 →
STM32F407学习记录(五)Cortex-M3/M4权威指南:第一章

STM32F407学习记录(五)Cortex-M3/M4权威指南:第一章

前言:本文章相当于学习笔记,摘录总结。推荐自行观看相关书籍(权威指南)。1.1什么是ARM Coretex-M处理器1.1.1Cortex-M3和Cortex-M4处理器介绍Cortex-M3/M4以及特点1.1.2Cortex-M处理器家族Cortex-M3/M4是基于Armv7-M架构。Cortex-…

2026/7/15 3:36:47阅读更多 →
3. 进程状态

3. 进程状态

目录 一、教材中经典的进程状态模型 二、调度 && 运行 && 阻塞 && 挂起 2.1. 画图 例子 2.2. 阻塞与挂起的区别 2.3. 上述例子的流程图 三、Linux 内核链表 的底层工作原理和内存布局图 3.1. 结构定义 3.2. 如何通过链表节点找到宿主结…

2026/7/15 3:36:47阅读更多 →
影刀RPA 影刀流程的单元测试方法:不靠试跑靠验证

影刀RPA 影刀流程的单元测试方法:不靠试跑靠验证

影刀RPA 影刀流程的单元测试方法:不靠试跑靠验证 作者:林焱 大多数人验证流程的方法是:跑一遍,看看结果对不对。这在小流程中没问题,流程一大、分支一多,靠"试跑"验证效率极低。更专业的做法是单…

2026/7/15 5:21:57阅读更多 →
影刀RPA 影刀中的SQL查询大全:从简单查询到多表联查

影刀RPA 影刀中的SQL查询大全:从简单查询到多表联查

影刀RPA 影刀中的SQL查询大全:从简单查询到多表联查 作者:林焱 影刀内置了SQLite指令,但很多新手只会最简单的SELECT *。这篇把RPA场景中最常用的SQL语句整理出来——不是数据库教科书,而是你在写影刀流程时打开就能抄的速查表。…

2026/7/15 5:21:57阅读更多 →
DS90UB662-Q1多路视频流合并:CSI-2转发模式详解与工程实践

DS90UB662-Q1多路视频流合并:CSI-2转发模式详解与工程实践

1. 项目概述:多路视频流合并的挑战与DS90UB662-Q1的解决方案在嵌入式视觉系统,尤其是汽车电子领域,我们常常面临一个核心挑战:如何将来自多个图像传感器(比如环视系统的四个鱼眼摄像头)的视频流&#xff0c…

2026/7/15 5:21:57阅读更多 →
Spring IOC 控制反转

Spring IOC 控制反转

一句话 IOC 是把对象的创建和依赖管理权从代码里转移到了 Spring 容器,开发者不再手动 new 对象和 set 依赖,而是由容器统一管理并注入。 1. 什么是 IOC 传统方式:UserService service new UserService();service.setUserDao(new UserDao()…

2026/7/15 5:21:57阅读更多 →
影刀RPA 异常处理的分层设计:子流程级、步骤级、操作级的三层防线

影刀RPA 异常处理的分层设计:子流程级、步骤级、操作级的三层防线

影刀RPA 异常处理的分层设计:子流程级、步骤级、操作级的三层防线 作者:林焱 异常处理是RPA流程稳定性的最后一道防线。没有异常处理的流程,一个报错就全盘崩溃;异常处理太粗的流程,错误信息被吞掉,出问题…

2026/7/15 5:21:57阅读更多 →
Windows Home Server 2011安装指南:兼容性、驱动与家庭服务器部署

Windows Home Server 2011安装指南:兼容性、驱动与家庭服务器部署

如果你在整理旧设备时翻出一台还能开机的老电脑,或者想用最低成本搭建一个家庭文件备份和远程访问环境,可能会想到十年前那个专为家庭设计的服务器系统——Windows Home Server 2011。虽然它早已在2016年停止支持,但在某些特定场景下&#xf…

2026/7/15 5:16:57阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/14 4:56:14阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/14 2:55:05阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/14 6:17:41阅读更多 →
AI框架决定企业AI能走多远

AI框架决定企业AI能走多远

企业AI建设的第一性原理 企业搞AI,最关键的决定是什么?不是选哪家大模型,不是先做哪个场景,不是招多少AI人才——而是选哪个AI开发框架。 为什么?因为框架决定了企业AI能力的"天花板"。选对了框架&#xff0…

2026/7/15 0:01:30阅读更多 →
Java企业为什么需要AI框架

Java企业为什么需要AI框架

Java企业在AI时代的尴尬处境 Java是全球企业级应用开发的主流语言——全球超过一半的企业系统跑在Java上。但在AI浪潮面前,很多Java企业感到尴尬:大模型的接口是各种语言的,AI开发社区以其他语言为主流,似乎Java在AI时代"掉队…

2026/7/15 0:01:30阅读更多 →
CC3230x嵌入式开发实战:SD主机、定时器与低功耗模式深度解析

CC3230x嵌入式开发实战:SD主机、定时器与低功耗模式深度解析

1. 项目概述:为什么需要关注CC3230x的SD主机、定时器与低功耗?在物联网和嵌入式设备开发领域,我们常常面临一个核心矛盾:设备需要具备强大的连接能力、可靠的数据存储和实时控制功能,同时又必须严格控制功耗以延长电池…

2026/7/15 0:01:30阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/14 15:07:30阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/14 4:45:36阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/14 2:42:17阅读更多 →