【本文发布于https://blog.csdn.net/Stack_/article/details/130532003,未经允许不得转载,转载须注明出处】
每篇PID文章都长篇大论数学原理,就是不教你怎么代码实现。理论是需要和实践相结合的,只看不做是学不会的。
此代码在STC8H上运行,摒弃了浮点运算,若是在32位单片机上,可根据需要改为浮点以提高精度
此代码能在我的加热器上将温度误差控制在0.5℃以内,如果需要达到更高的精度需要花更多时间调参数。
1、根据热敏电阻厂家提供的 阻值-温度 对照表计算出各温度下对应的理论AD值,并制表
#ifndef __NTC_LIST_H__
#define __NTC_LIST_H__
//[email protected]
//12位ADC,10K电阻分压,10K电阻在VCC端,5V电压
code unsigned int ntc_ad_list[] =
{
/*0℃ - 9℃*/
3102, 3065, 3027, 2989, 2950, 2910, 2870, 2829, 2788, 2746,
/*10℃ - 19℃*/
2704, 2661, 2619, 2585, 2541, 2497, 2452, 2407, 2362, 2317,
/*20℃ - 29℃*/
2272, 2227, 2182, 2137, 2092, 2048, 2003, 1959, 1915, 1871,
/*30℃ - 39℃*/
1828, 1785, 1743, 1701, 1660, 1619, 1579, 1539, 1500, 1461,
/*40℃ - 49℃*/
1423, 1386, 1349, 1313, 1278, 1244, 1210, 1176, 1144, 1112,
/*50℃ - 59℃*/
1081, 1051, 1021, 992, 964, 936, 912, 886, 861, 836,
/*60℃ - 69℃*/
812, 789, 767, 745, 723, 703, 683, 663, 644, 625,
/*70℃ - 79℃*/
607, 590, 573, 557, 541, 525, 510, 496, 481, 468,
/*80℃ - 89℃*/
454, 441, 429, 417, 405, 394, 382, 372, 361, 351,
/*90℃ - 99℃*/
341, 332, 323, 314, 305, 297, 288, 281, 273, 265
};
#endif
2、根据AD值查表并插值计算出温度值
#ifndef __NTC_H__
#define __NTC_H__
#include "public.h"
extern uint8_t xdata ntc_exist;
extern uint16_t xdata ntc_temp;
extern uint8_t xdata ntc_shortcut;
void ntc_proc(void);
#endif
/**
******************************************************************************
* @copyright
* @file
* @author [email protected]
* @version
* @date
* @brief
******************************************************************************
* @attention files encoding : GB2312
* 文件编码 :GB2312
******************************************************************************
*/
#include "ntc.h"
#include "ntc_list.h"
uint8_t xdata ntc_exist = 0;
uint16_t xdata ntc_temp; //一位小数
uint8_t xdata ntc_shortcut = 0;
/**
* @brief
* @note
* @param
* @retval
* @author PWH //[email protected]
* @date 2023/2
*/
void ntc_proc(void)
{
uint16_t xdata i, j;
ntc_exist = 1;
ntc_shortcut = 0;
if (ad_val > 3800) //ntc电阻不存在
{
ntc_exist = 0;
}
if (ad_val < 20) //ntc电阻短路
{
ntc_shortcut = 1;
}
if (!ntc_exist || ntc_shortcut)
{
ntc_temp = 0;
return;
}
if (ad_val >= ntc_ad_list[0])
{
ntc_temp = 0;
}
else if (ad_val <= ntc_ad_list[sizeof(ntc_ad_list) / 2 - 1])
{
ntc_temp = 990; //99度
}
else
{
for (i = 0; i < sizeof(ntc_ad_list) / 2 - 1; i++) //查表
{
if (ad_val == ntc_ad_list[i])
{
ntc_temp = i * 10;
break;
}
else if (ad_val == ntc_ad_list[i + 1])
{
ntc_temp = (i + 1) * 10;
break;
}
else if (ad_val < ntc_ad_list[i] && ad_val > ntc_ad_list[i + 1]) //落在中间,将两点之间视为直线,进行插值
{
/*将 i 和 i+1 两点间视为直线,分为10段对应小数0-9*/
for (j = 1; j < 10; j++)
{
ntc_temp = i * 10 + j;
if (ad_val >= ntc_ad_list[i] - (ntc_ad_list[i] - ntc_ad_list[i + 1]) * j / 10)
{
return;
}
}
break;
}
}
}
}
需要根据实际的加热器对参数进行修改,例如 P、I、D系数,积分限幅值、温度补偿值、开启PID控温的温度区间,在升温速度和超调之间取得一个平衡。
#ifndef __PTC_H__
#define __PTC_H__
#include "public.h"
typedef struct {
int16_t pid_set_temp; //设置温度
int16_t pid_now_temp; //现在温度
int16_t pid_err_last; //上次误差
int16_t pid_err; //误差
int16_t pid_err_sum; //历史误差和
int16_t pid_result;
} PID_Para_t;
extern xdata PID_Para_t PID_Para;
extern uint8_t timer_ptc;
void ptc_proc(void);
void pid_init(void);
#endif
/**
******************************************************************************
* @copyright
* @file
* @author //[email protected]
* @version
* @date
* @brief
******************************************************************************
* @attention files encoding : GB2312
* 文件编码 :GB2312
******************************************************************************
*/
#include "ptc.h"
#define TEMPERATURE_COMPENSATION 1 //1:开启温度补偿
#define Kp 100 //比例常数 100倍值 100即1
#define Ki 18 //积分常数 100倍值 18即0.18
#define Kd 10 //微分常数 100倍值 90即0.9
#define I_MAX 2500
#define I_MIN 0
xdata PID_Para_t PID_Para;
/**
* @brief
* @note
* @param
* @retval
* @author PWH //[email protected]
* @date 2023/2
*/
void pid_init(void)
{
PID_Para.pid_set_temp = 30;
PID_Para.pid_err_last = 0;
PID_Para.pid_err_sum = 0;
}
/**
* @brief 位置式PID
* @note
* @param
* @retval
* @author PWH //[email protected]
* @date 2023/2
*/
void pid(void)
{
int16_t xdata P, D;
PID_Para.pid_err = PID_Para.pid_set_temp - PID_Para.pid_now_temp; //当前误差
P = PID_Para.pid_err * Kp; //比例量 P
PID_Para.pid_err_sum += PID_Para.pid_err * Ki; //积分量 I
if (PID_Para.pid_err_sum > I_MAX) PID_Para.pid_err_sum = I_MAX; //积分限幅
else if (PID_Para.pid_err_sum < I_MIN) PID_Para.pid_err_sum = I_MIN;
D = (PID_Para.pid_err - PID_Para.pid_err_last) * Kd; //微分量 D
PID_Para.pid_err_last = PID_Para.pid_err;
#if (1)
PID_Para.pid_result = (P + PID_Para.pid_err_sum + D) / 100;
#else
PID_Para.pid_result = (P + D) / 100;
#endif
if (PID_Para.pid_result > 100) PID_Para.pid_result = 100; //占空比 0-100
else if (PID_Para.pid_result < 0) PID_Para.pid_result = 0;
}
uint8_t timer_ptc = 0; //定时器中断10ms递增
/**
* @brief
* @note
* @param
* @retval
* @author PWH //[email protected]
* @date 2023/2
*/
void ptc_proc(void)
{
uint16_t xdata temperature_now;
uint16_t xdata target;
static uint8_t xdata timer = 0;
#if (TEMPERATURE_COMPENSATION == 1)
uint8_t xdata compensation; //补偿温度,陶瓷表面温度比热敏电阻处高,加温目标需要比设置的低
#endif
if (!timer_ptc) return; //每10ms执行一次
timer_ptc = 0;
if (ntc_exist)
{
#if (TEMPERATURE_COMPENSATION == 1)
switch (app_para.target) //app_para.target 目标温度无小数
{
case 30: compensation = 0; break; //一位小数 eg. 33=3.3℃
case 33: compensation = 0; break;
case 37: compensation = 0; break;
case 40: compensation = 0; break;
case 43: compensation = 5; break;
case 47: compensation = 5; break;
case 50: compensation = 12; break;
case 53: compensation = 15; break;
case 57: compensation = 11; break;
case 60: compensation = 16; break;
case 65: compensation = 21; break;
default: break;
}
#endif
temperature_now = ntc_temp;
#if (TEMPERATURE_COMPENSATION == 1)
target = app_para.target * 10 - compensation;
#else
target = app_para.target * 10;
#endif
if (temperature_now < target - 5) //达到目标温度-0.5℃前全速加热
{
Pin_Heater_Ctrl = HEATER_ON; //
}
else if (temperature_now > target + 1 * 10) //超出设置值1℃时全关
{
Pin_Heater_Ctrl = HEATER_OFF;
}
else //PID控制
{ //设定加热周期为1000ms,1000分之PID_Para.pid_result毫秒开加热器
if (timer_ptc >= PID_Para.pid_result)
{
Pin_Heater_Ctrl = HEATER_OFF;
}
else
{
Pin_Heater_Ctrl = HEATER_ON;
}
if (++timer > 100) //100 * 10ms = 1000ms周期
{
timer = 0;
PID_Para.pid_set_temp = target;
PID_Para.pid_now_temp = temperature_now;
pid();
}
}
}
else
{
Pin_Heater_Ctrl = HEATER_OFF;
}
}