CW32L012的PID温度控制——实现思路及代码 一、系统框架

按键输入:通过按键来控制光标并更改设定温度值、Kp、Ki、Kd
ADC采集:采集NTC1的电压,转换成NTC1的电阻,再进行分度值查表,得到当前温度
PWM输出:将PID计算的结果通过PWM输出到执行器,控制板子加热
串口调试:将实际温度、设定温度值、Kp、Ki、Kd参数打印到上位机进行波形显示
屏幕显示:将实际温度、设定温度值、Kp、Ki、Kd、NTC1的电压、PWM当前的输出占空比参数显示到屏幕上
二、实验准备及接线
1.)先准备配套器材
主控:CW32L012开发板
辅助器材:杜邦线若干(公对母,母对母)、万用表(调试)
供电:USB 线或直接杜邦线供电(给实验板 + CW32L012开发板供电)
2.)基础接线步骤
电源连接
实验板5V→ CW32L012开发板 5V
实验板GND → CW32L012开发板 GND(共地很重要,避免信号干扰)
传感器接线
实验板NTC1排针接口 →CW32L012开发板ADC转换IO口PA4.
PWM输出接线
实验板PWM接口 →CW32L012开发板PWM输出口PB9
串口接线
CW32L012开发板PA9 →CH340或WCHlink的RX引脚
CW32L012开发板PA10 →CH340或WCHlink的TX引脚
CW32L012开发板3.3V →CH340或WCHlink的3.3V引脚
CW32L012开发板GND →CH340或WCHlink的GNDV引脚
接线完成图:



三、温度闭环控制实现

通过按键或初始化给定一个目标温度值
//温度控制系统的参数:实际温度,设定温度,P、I、D参数,PWM占空比
float Temp_Para[6] = {0,20,0,0,0,0};
void key_handle(uint8_t key)
{
switch(key)
{
case 1://按键1,对应参数++
if(para_Index==3) Temp_Para[para_Index]+=0.5;//如果是积分,每次变化0.5
else Temp_Para[para_Index]++;
if(Temp_Para[para_Index] > Temp_Para_Max[para_Index])
Temp_Para[para_Index] = Temp_Para_Min[para_Index];
break;
case 2://按键2,对应参数--
if(Temp_Para[para_Index] > Temp_Para_Min[para_Index])
{
if(para_Index==3) Temp_Para[para_Index]-=0.5;//如果是积分,每次变化0.5
else Temp_Para[para_Index]--;
}
else
Temp_Para[para_Index] = Temp_Para_Max[para_Index];
break;
case 3://按键3,选择要操作的参数
para_Index_old = para_Index;
para_Index++;
if(para_Index >= 5)
para_Index= 1;
break;
}
}
ADC采集获取当前温度
采集NTC1的电压,转换成NTC1的电阻,再进行分度值查表,得到当前温度
部分代码:
/**************************
获取NTC1上端点电压函数
返回值:电压(单位V)
**************************/
float get_ntc_v(void)
{
return adc_result*3.3/4095;
}
/**************************
获取NTC1电阻函数
传入:NTC1上端点电压值(单位V)
返回值:电阻(单位欧姆)
**************************/
float get_ntc_r(float Vadc)
{
return (Vadc*10000)/(5-Vadc);
}
//0摄氏度~100摄氏度时NTC的电阻值,共101个电阻数据
const uint16_t PID_NTC_Table[]={
32108, 30544, 29066, 27669, 26346, 25095, 23910, 22788, 21724, 20716,
19760, 18856, 17997, 17181, 16405, 15667, 14965, 14297, 13662, 13058,
12483, 11936, 11415, 10920, 10449, 10000, 9573, 9166, 8778, 8409,
8057, 7722, 7402, 7097, 6806, 6529, 6264, 6011, 5770, 5539,
5319, 5109, 4908, 4716, 4533, 4357, 4190, 4029, 3876, 3729,
3588, 3454, 3325, 3201, 3083, 2970, 2861, 2757, 2658, 2562,
2470, 2383, 2299, 2218, 2140, 2066, 1994, 1926, 1860, 1796,
1735, 1677, 1620, 1566, 1514, 1464, 1416, 1370, 1325, 1282,
1241, 1201, 1163, 1126, 1091, 1057, 1024, 992, 961, 932,
903, 876, 849, 824, 799, 775, 752, 730, 708, 687,
667
};
/**
* @brief 二分查找NTC电阻值对应的下标,无精确值时线性插值返回浮点下标
* @param target_res: 要查找的NTC电阻值(uint16_t)
* @retval 浮点型下标:
* - 精确匹配:返回整数下标(如25.0,对应25℃)
* - 插值匹配:返回小数下标(如25.5,对应25.5℃)
* - 超出范围:返回0.0(电阻 >最大值)或100.0(电阻< 最小值)
*/
float NTC_FindIndex(uint16_t target_res)
{
// 边界1:目标电阻 > 数组最大值(0℃对应电阻)→ 返回0.0
if (target_res > PID_NTC_Table[0])
{
return 0.0f;
}
// 边界2:目标电阻 < 数组最小值(100℃对应电阻)→ 返回100.0
if (target_res < PID_NTC_Table[100 - 1])
{
return 100.0f;
}
// 二分查找初始化
int32_t low = 0; // 左边界下标
int32_t high = 100 - 1; // 右边界下标
int32_t mid = 0;
// 二分查找核心循环(适配单调递减数组)
while (low <= high)
{
mid = low + (high - low)/2; // 计算中间下标(避免溢出可写:low + (high - low)/2)
if (PID_NTC_Table[mid] == target_res)
{
// 精确匹配,返回浮点型下标
return (float)mid;
}
else if (target_res < PID_NTC_Table[mid])
{
// 目标电阻更小 → 对应温度更高 → 向右(大下标)查找
low = mid + 1;
}
else
{
// 目标电阻更大 → 对应温度更低 → 向左(小下标)查找
high = mid - 1;
}
}
// 未找到精确值,执行线性插值(此时high < low 且 high + 1 = low)
// 插值公式(单调递减适配):
// 下标 = high + (目标值 - 高下标电阻) / (低下标电阻 - 高下标电阻)
float res_high = (float)PID_NTC_Table[high]; // high下标对应的电阻(更大)
float res_low = (float)PID_NTC_Table[low]; // low下标对应的电阻(更小)
float index = (float)high + (target_res - res_high) / (res_low - res_high);
return index;
}
对温度进行滑动均值滤波
将数据打印到串口显示波形,可以看到数据呈现明显的震荡,根据这个波形现象,我们采取合适的滤波,也就是滑动均值滤波:


可以直观看到滤波前后,波形明显平滑了不少,说明滤波效果好。
代码:
#define TEMP_FILTER_WINDOW_SIZE 5 // 滑动窗口大小(建议3~10,越大越平滑,实时性稍降)
// ******************************************************
/**
* @brief 温度滑动平均滤波函数
* @param rawTemp 输入的原始浮点温度值(如传感器采集的温度)
* @return 滤波后的浮点温度值
* @note 函数内部通过静态变量维护滤波窗口,无需外部初始化/销毁,调用即使用
*/
float tempMovingAverageFilter(float rawTemp)
{
// 静态变量:仅第一次调用初始化,后续调用保留值(维护滤波窗口状态)
static float tempBuffer[TEMP_FILTER_WINDOW_SIZE] = {0.0f}; // 温度缓冲区
static int bufferIndex = 0; // 下一个写入的索引(循环覆盖)
static int dataCount = 0; // 已存入的有效数据个数
// 1. 将新温度值写入缓冲区(循环覆盖最旧数据)
tempBuffer[bufferIndex] = rawTemp;
// 2. 更新索引(循环:0→1→...→窗口大小-1→0)
bufferIndex = (bufferIndex + 1) % TEMP_FILTER_WINDOW_SIZE;
// 3. 更新有效数据数(窗口未满时累加,满后保持窗口大小)
if (dataCount < TEMP_FILTER_WINDOW_SIZE)
{
dataCount++;
}
// 4. 计算窗口内所有数据的平均值(滤波核心)
float sum = 0.0f;
for (int i = 0; i < dataCount; i++)
{
sum += tempBuffer[i];
}
return sum / (float)dataCount;
}
PID计算
对采集到的温度和设定的温度进行运算,得出适合的加热功率并输出
代码:
typedef struct
{
int16_t target; //目标值
int16_t actual; //实际值
float out; //输出值
float err; //偏差值
float err_last; //上一个偏差值
float integral; //积分值
float Kp;
float Ki;
float Kd;
}pid;
void set_pid_para(void)//更新pid参数
{
temper_pid.actual=Temp_Para[0];
temper_pid.target=Temp_Para[1];
temper_pid.Kp=Temp_Para[2];
temper_pid.Ki=Temp_Para[3];
temper_pid.Kd=Temp_Para[4];
}
uint16_t pid_control(void)
{
set_pid_para();//更新pid参数
temper_pid.err=temper_pid.target-temper_pid.actual;//误差
if(temper_pid.err<=0) return 0;//设定温度低于等于实际温度,加热关闭
else if(temper_pid.err >5) return ARR_Value;//实际温度与设定温度相差大于5度,加热输出最大功率
else
{
temper_pid.integral+=temper_pid.err;//积分
temper_pid.integral=(temper_pid.integral >10)? 10:temper_pid.integral;//积分限幅
temper_pid.integral=(temper_pid.integral< -10)? -10:temper_pid.integral;
temper_pid.out=temper_pid.Kp*temper_pid.err+temper_pid.Ki*temper_pid.integral+temper_pid.Kd*(temper_pid.err-temper_pid.err_last);
temper_pid.err_last=temper_pid.err;//更新上一次误差
temper_pid.out=(temper_pid.out >ARR_Value)? ARR_Value:temper_pid.out;//输出限幅
return temper_pid.out;
}
}
PWM输出
将控制算法结果作用到PWM,控制板子进行加热
void Set_pwm__ccr(uint32_t Angle)
{
Angle=(Angle >ARR_Value)? ARR_Value:Angle;
CW_GTIM1- >CCR4 = Angle;
}
Set_pwm__ccr(pid_control());//输出
四、代码现象


屏幕:与屏幕对应的变量含义:
Temp PID
实际温度(Real Temp)
设定温度(Set Temp)
P(Kp)
I(Ki)
D(Kd)
输出的PWM占空比(PWM Duty)
采集到的NTC的电压(Vntc)
按键:
按键1(左):对光标所选中的参数进行加操作
按键2(中):对光标所选中的参数进行减操作
按键3(右):切换光标选择的内容

PWM输出灯:LED亮的程度反应了输出PWM的占空比大小,占空比越大,灯越亮,反之则越暗

串口:使用VOFA+软件连接串口,CW32会将设定、实际温度,Kp,Ki,Kd几个参数打印到上位机,显示波形

全部0条评论
快来发表一下你的评论吧 !