找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2344

积分

0

好友

342

主题
发表于 2025-12-31 00:50:44 | 查看: 22| 回复: 0

系统框架

CW32L012温度控制系统架构图
图1:系统架构图,展示了CW32L012温度控制系统的核心功能模块连接。

  • 按键输入:通过按键控制光标,用于更改设定温度值以及P、I、D参数。
  • ADC采集:采集NTC热敏电阻的电压,将其转换为电阻值,再通过查表法获取当前温度。
  • PWM输出:将PID计算得到的结果通过PWM输出到执行器(如加热片),控制加热功率。
  • 串口调试:将实际温度、设定温度以及PID参数实时发送到上位机,便于通过波形观察系统动态。
  • 屏幕显示:在本地屏幕上显示所有关键参数,包括实际温度、设定值、PID参数、NTC电压以及PWM占空比。

实验准备及接线

1. 器材准备

  • 主控:CW32L012开发板
  • 辅助器材:若干杜邦线(公对母,母对母)、万用表(用于调试)
  • 供电:USB线或直接通过杜邦线为实验板和CW32L012开发板供电

2. 基础接线步骤

电源连接

  • 实验板 5V → CW32L012开发板 5V
  • 实验板 GND → CW32L012开发板 GND (共地至关重要,可有效避免信号干扰)

传感器接线

  • 实验板NTC1排针接口 → CW32L012开发板ADC转换IO口 PA4

PWM输出接线

  • 实验板PWM接口 → CW32L012开发板PWM输出口 PB9

串口接线

  • CW32L012开发板 PA9 (TX) → CH340或WCH-Link的RX引脚
  • CW32L012开发板 PA10 (RX) → CH340或WCH-Link的TX引脚
  • CW32L012开发板 3.3V → CH340或WCH-Link的3.3V引脚
  • CW32L012开发板 GND → CH340或WCH-Link的GND引脚

接线完成后,系统硬件连接如下图所示:

开发板与实验板连接示意图
图2:CW32L012开发板与温控实验板的整体连接实物图。

调试器连接细节图
图3:WCH-Link调试器与开发板的连接细节。

温度闭环控制实现

温度闭环控制流程图
图4:温度闭环PID控制流程图,清晰展示了从设定到反馈的完整控制环路。

目标温度设定

系统启动时初始化一个目标温度值,后续可通过按键实时调整。以下代码定义了温度控制参数数组及按键处理函数。

//温度控制系统的参数:实际温度,设定温度,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采集获取当前温度

通过ADC采集NTC热敏电阻的电压,将其转换为电阻值,再利用预存的分度表(电阻-温度对应表)查表得到当前温度值。查表算法采用了高效的二分查找法,并在未精确匹配时进行线性插值,以获取更高精度的浮点温度值。

/**************************
获取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;
}

滑动均值滤波处理

原始ADC采集的温度数据可能存在噪声和抖动。通过串口将原始数据绘制成波形,可以观察到明显的震荡现象。

滤波前的温度波动波形图
图5:滤波前,实际温度值围绕设定值剧烈波动的串口波形图。

滤波后的温度平滑波形图
图6:经过滑动均值滤波后,温度波形变得平滑稳定,控制效果显著改善。

对比滤波前后的波形图可以直观看到,滤波后的曲线明显平滑,这说明滑动均值滤波有效抑制了噪声。以下是滤波算法的C语言实现:

#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计算

这是控制系统的核心。算法根据设定温度与实际温度的偏差,结合P、I、D三个参数,计算出合适的控制量(加热功率)。

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输出

将PID计算得到的控制量转换为PWM的占空比,并输出到定时器的比较寄存器,从而驱动执行机构(加热片)工作。

void Set_pwm_ccr(uint32_t Angle)
{
        Angle=(Angle>ARR_Value)? ARR_Value:Angle;
        CW_GTIM1->CCR4 = Angle;
}
Set_pwm_ccr(pid_control());//输出

代码现象与运行结果

系统运行整体实物图
图7:系统整体运行实物图,展示了开发板、屏幕及温控模块的连接状态。

LCD屏幕显示参数详情
图8:LCD屏幕实时显示所有关键参数,包括实际温度、设定温度、PID参数、PWM占空比和NTC电压。

  1. 屏幕显示:屏幕各变量含义如下:

    • Temp PID (标题)
    • Real Temp:实际温度
    • Set Temp:设定温度
    • P (Kp):比例系数
    • I (Ki):积分系数
    • D (Kd):微分系数
    • PWM Duty:输出的PWM信号占空比
    • Vntc:采集到的NTC热敏电阻电压
  2. 按键操作

    • 按键1(左):对光标选中的参数进行“加”操作。
    • 按键2(中):对光标选中的参数进行“减”操作。
    • 按键3(右):切换光标,选择要调整的参数项。

    屏幕光标选择示意图
    图9:通过按键切换光标,可选择修改设定温度或任意一个PID参数。

  3. PWM输出指示:板上的一颗LED亮度直接反映了PWM输出的占空比大小。占空比越大,LED越亮;反之则越暗,提供了直观的状态反馈。

    PWM输出LED指示图
    图10:LED亮度随PWM占空比变化,用于直观指示加热功率。

  4. 串口波形监控:使用VOFA+上位机软件连接串口,CW32L012会将设定温度、实际温度、Kp、Ki、Kd等参数打包发送,并在软件中实时绘制波形,便于观察系统响应和调试PID参数。

    VOFA+上位机波形显示界面
    图11:在VOFA+软件中观察的温度及PID参数实时波形,是调试过程的重要工具。


本文涉及的完整工程代码及更深入的嵌入式系统开发讨论,欢迎访问云栈社区进行交流与下载。




上一篇:将工具页面数据转化为内容页:一个SEO与长尾关键词驱动的创作思路
下一篇:深度解析Docker底层原理:Namespace、Cgroups与UnionFS核心技术详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 11:55 , Processed in 0.219591 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表