用ESP32-S2模块实现PID控制温度
2023寒假一起练 ESP32-S2 NST112 PID 温度控制 arduino AD转换 Lvgl 动态曲线
标签
嵌入式系统
Funpack活动
测试
显示
aramy
更新2023-03-27
1441

硬件介绍:2023寒假一起练平台(5)基于ESP32-S2模块的开发板。ESP32-S2是乐鑫2019年推出的一款新芯片。ESP32-S2-MINI-1采用PCB板载天线,模组配置了4MB SPI flash,32 位LX7 单核处理器,工作频率高达 240 MHz。43 个 GPIO 口,14 个电容式传感 IO,支持 SPI、I2C、I2S、UART、ADC/DAC 和 PWM 等各种标准外设,支持 LCD 接口(8-bit 并口 RGB、8080、6800 接口),支持 8-/16-bit DVP 图像传感器接口,最高时钟频率支持到 40 MHz ,支持全速 USB OTG。没有蓝牙、单核模块,GPIO够多,这是我非常喜欢的一款芯片。

任务选择:这次我选择了项目4 - 实现一个恒温自动控制系统。IO扩展板上有一处加温电阻,将加热区域用物体(纸巾等)包裹起来,通过电流给电阻加热,并通过温度传感器感知板上温度的变化,测温以及在LCD屏上的温度显示。要求:使用按键设定目标温度,并且通过程序控制加热功率,使得温度尽快尽量稳定的维持在目标温度。温度偏离设定温度±3°C彩灯变为红色。
Fs-0jkEKz_lKMhID1Wwsdo3za538
电路板的一角可以看见有个圆形的缺口。通过电路图可以知道,板子上这个位置有4个68欧的电阻并联,通过一个MOS管做开关,接入3.3v电源中。这样就形成了一个最大0.64瓦的加热单元。反面有一颗NST112温度芯片,通过IIC与主控相连,这样就可以形成一个闭环的加热系统啦。正好借此机会学习一下PID控制。编程语言就选用熟悉的Arduino,编程工具使用Vscode。在Vscode下使用platformio做Arduino的开发很是方便。

任务实现:有了任务目标、选好了编程语言和环境,接下来就是动手啦!
FovxT7PVnIYEbMYgBAy1d8dhJHLc
首先解决温度传感器。这里的温度传感器是NST112,是一款低功耗高精度数字温度传感器。适用于负温度系数和正温度系数热敏电阻的替换。NST112具有可兼容I2C和SMBus的接口,具有可编程警报和SMBus重置功能,在单路总线上最多可支持4个器件。且无需校准即可在 -20℃到85℃的范围内实现高达±0.5℃的精度。NST112温度传感器是高线性度的,不需要重新组合计算或查表以导出温度。NST112具有12bit的模数转换提供高达0.0625℃解析度。看介绍是一款很厉害的温度传感器,但是芯片很新,在网上没能找到现成的类库。只能读官方文档,仿照其它I2C温度计的类库,自己写了个简单的代码。这里有偷懒,读取温度的两个字节,直接将高位作为温度的整数位,低字节作为温度的小数位,单位使用摄氏度。侧试了一下能用。

#ifndef USER_NST112_H
#define USER_NST112_H
#include <Wire.h>

#define USER_SDA 4
#define USER_SCL 3
#define NTS112_ADDRESS 0X48

void init_nst112()
{
    Wire.begin(USER_SDA, USER_SCL);
}

float readTemperatureC()
{
    float temp;
    Wire.beginTransmission(NTS112_ADDRESS);
    Wire.write(0x00);
    Wire.endTransmission();
    Wire.requestFrom(NTS112_ADDRESS, (uint8_t)2);
    uint16_t t = Wire.read();
    temp = (float)t;
    t = Wire.read();
    temp += (float)t / 256.00;
    Wire.endTransmission();
    return temp;
}
#endif      //USER_NST112_H

然后是编码器的控制。编码器负责和用户交互,供用户设置需要的温度。查看电路图这次的编码器是接着一组电阻,然后通过一个管脚接入ESP32主控。意味着必须通过AD查询的方式来获得编码器的动作。运行分两个状态:设置状态、运行状态。编码器按下动作控制状态切换。设置状态时不加热,用户可以通过旋转编码器修改预设温度值。运行状态时系统控制MOS管加热,此时不响应编码器的旋转。顺时针增加目标温度,逆时针减小目标温度。
这里编码器的这种控制方式很新颖,但是我用轮询方式查询编码器的动作,也是有很多问题。在查询过程中添加了延时用来消抖,但是效果还不是太理想,旋转时很难做到精细控制。

#ifndef ENCODER_H
#define ENCODER_H
#include <Arduino.h>

#define ANALOG_PIN 1
#define TEMPMAXVAL 90               //最高温
#define TEMPMINVAL 35              //最低温
static float encoder_val = TEMPMINVAL;
static bool enabledeal=false;

void init_encoder()
{
    pinMode(ANALOG_PIN, INPUT);
}

//使用轮询方式检查编码器
uint8_t check_encoder()
{
    int analog_value = 0;
    analog_value = (analogRead(ANALOG_PIN) + 50) / 100;
    if (analog_value >= 21 && analog_value <= 23) //右侧按键
        return 1;
    if (analog_value >= 60 && analog_value <= 62) //编码器按键
        return 2;
    if (analog_value >= 70 && analog_value <= 72) //编码器旋转 顺时针
        return 3;
    if (analog_value >= 65 && analog_value <= 69) //编码器旋转 逆时针
        return 4;
    if (analog_value < 74 || analog_value > 75) //静态
    {
    }
    return 0;
}

void readEncoderValue()
{
    //检查3次编码器的返回值,如果一致则 修改
    uint8_t echo[3];
    for (uint8_t i = 0; i < 3; i++)
    {
        delay(4);
        echo[i] = check_encoder();
    }
    if (echo[0] > 0)
    {
        if (echo[0] == echo[1] && echo[2] == echo[1])
        {
            switch (echo[0])
            {
            case 2:
                enabledeal=!enabledeal;
                break;
            case 3:
                if(enabledeal){
                    encoder_val+=0.25;
                    encoder_val=encoder_val>TEMPMAXVAL?TEMPMAXVAL:encoder_val;                   //设置上限
                }
                break;
            case 4:
                if(enabledeal){
                    encoder_val-=0.25;
                    
                    encoder_val=encoder_val<TEMPMINVAL?TEMPMINVAL:encoder_val;                   //设置个下限 
                }
                break;
            }
        }
    }
}

#endif // ENCODER_H

电阻加热功率有限,所以设置加热的上限为90度,超过这个温度,靠电阻加热很困难了。降温只能靠空气降温,设置一个最低温度值,高于当前室温即可。

MOS管的通断能控制电阻是否加热,主控通过PWM波就可以控制MOS管的通断。通过控制高低电平的占比,可以控制输出功率的大小。这里就参考了PID自动控制的算法。PID算法很适合这种模糊控制的场景,通过输入目标温度、当前温度,就能结合历史误差获得一个合适的PWM值输出。这里参考网上的PID算法。不过自己对PID算法理解还是不够透彻,调参调的不是很好,感觉波动还是有些大。

#ifndef TEMPTUREPID_H_
#define TEMPTUREPID_H_
#include <Arduino.h>
#define PWMPIN  10              //PWM输出管脚
#define MAXOUTVAL 1023
typedef struct Pid
{
    float Sv; //用户设定值
    float Pv;   //实际值

    float Kp;
    float ki;
    float kd;

    float Ek;   //本次偏差
    float Ek_1; //上次偏差
    float SEk;  //历史偏差之和

    float Iout;
    float Pout;
    float Dout;

    float OUT;
    int times;
} PID;

extern PID pid;

void PID_Init(void);
void PID_Calc(void);

#endif
#include "tempturepid.h"

PID pid;

void PID_Init()
{
	pid.Kp = 210;
	pid.ki=0.125;
	pid.kd=0.08;
	ledcSetup(0, 5000, 10);    //通道0, 5KHz,10位解析度
    ledcAttachPin(PWMPIN, 0); //使用0通道
	ledcWrite(0, 0);                //初始为0
}

void PID_Calc() // pid计算
{
	float DelEk;
	float ti, ki;
	float td;
	float kd;
	float out;
	pid.Ek = pid.Sv - pid.Pv;	//得到当前的偏差值
	pid.Pout = pid.Kp * pid.Ek; //比例输出
	pid.SEk += pid.Ek;			//历史偏差总和
	DelEk = pid.Ek - pid.Ek_1;	//最近两次偏差之差
	pid.Iout = pid.ki * pid.SEk; 	//积分输出
	pid.Dout = pid.kd * DelEk; //微分输出

	out = pid.Pout + pid.Iout + pid.Dout;

	if (out > MAXOUTVAL)
	{
		pid.OUT = MAXOUTVAL;
	}
	else if (out <= 0)
	{
		pid.OUT = 0;
	}
	else
	{
		pid.OUT = out;
	}
	pid.Ek_1 = pid.Ek; //更新偏差
}

  最后在tft屏幕,绘制温度变化的波形图。屏幕驱动直接使用了TFT的库,波形显示使用了Lvgl的库,使用chart的控件,绘制实时的波形图,每半秒绘制一个点,一共绘制20个点,就是绘制10秒钟温度变化的曲线。
Fjoi4VDcny9LNvZwZyYBJn9lLVZ9

总结:感谢硬禾学堂提供这些学习的机会。在做项目过程中学习到许多知识,结交了很多大神。这里提个建议:这个扩展板上的MOS管控制端悬空,没有加下拉。这样导致板子上电后,如果不小心摸到MOS管的控制脚,就用可能导致MOS管导通,电阻就会以最大功率运行,自己不小心被烫到了。

 

附件下载
esp32s2_2023.zip.001
esp32s2_2023.zip.002
团队介绍
瞎折腾小能手
团队成员
aramy
单片机业余爱好者,瞎捣鼓小能手。
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号