硬件介绍: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彩灯变为红色。
电路板的一角可以看见有个圆形的缺口。通过电路图可以知道,板子上这个位置有4个68欧的电阻并联,通过一个MOS管做开关,接入3.3v电源中。这样就形成了一个最大0.64瓦的加热单元。反面有一颗NST112温度芯片,通过IIC与主控相连,这样就可以形成一个闭环的加热系统啦。正好借此机会学习一下PID控制。编程语言就选用熟悉的Arduino,编程工具使用Vscode。在Vscode下使用platformio做Arduino的开发很是方便。
任务实现:有了任务目标、选好了编程语言和环境,接下来就是动手啦!
首先解决温度传感器。这里的温度传感器是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秒钟温度变化的曲线。
总结:感谢硬禾学堂提供这些学习的机会。在做项目过程中学习到许多知识,结交了很多大神。这里提个建议:这个扩展板上的MOS管控制端悬空,没有加下拉。这样导致板子上电后,如果不小心摸到MOS管的控制脚,就用可能导致MOS管导通,电阻就会以最大功率运行,自己不小心被烫到了。