项目描述
完成的项目要求:
项目4 - 实现一个恒温自动控制系统
- IO扩展板上有一处加温电阻,将加热区域用物体(纸巾等)包裹起来,通过电流给电阻加热,并通过温度传感器感知板上温度的变化,测温以及在LCD屏上的温度显示。
要求:使用按键设定目标温度,并且通过程序控制加热功率,使得温度尽快尽量稳定的维持在目标温度。温度偏离设定温度±3°C彩灯变为红色。
(注意,加热电阻满占空比开启后温度较高)
根据项目的要求,实现一个恒温自动控制系统。通过电流给IO扩展板上加温电阻加热,并通过温度传感器感知板上温度的变化,并在LCD屏上显示温度及曲线。用旋转编码器设定目标温度,并且通过程序控制加热功率。温度偏离设定温度±3°C彩灯变为红色。
开发环境:Arduino 1.8.19
编程语言:C
本次实验的实验框图如上所示,以ESP32为核心,通过各种通讯方式与外设相互通信。其中温度传感器和旋转编码器为输入,LCD、加温电阻和LED为输出。
本次设计的软件流程图如上所述,首先对各种外设进行初始化。通过定时器进行任务调度,包括读取温度,读取输入,根据输入执行相应的温度控制,将波形和数值通过LCD显示,并控制加温电阻和LED。
硬件介绍
ESP32-S2 是一款高度集成、高性价比、低功耗、主打安全的单核 Wi-Fi SoC,具备强大的功能和丰富的 IO 接口。使用乐鑫ESP-IF开发环境,我们可以通过USB对其编程,作为带wifi的MCU单独使用,也可以烧录AT固件,作为WiFi透传模块与RP2040游戏机套件结合使用。
ESP32-S2 WiFi模块是物联网、可穿戴电子设备和智能家居等应用场景的理想选择,另搭配输入控制、输出显示以及传感器感知和控制的套件,使其功能更加完善。
配套的ESP32 S2 开发板除了ESP32wifi模组之外还集成了USB TYPE -C接口,两个按键,一个电源指示灯,一个用户LED灯,2排10pin的排针,将重要IO引出。使用USB供电或通过排针3.3V供电。
本扩展板包含如下功能:
- 按键、旋转编码器输入 - 以模拟信号的方式
- 双电位计控制输入 - 以数字信号的方式
- RGB三色LED显示
- 1.44寸128*128 LCD,SPI总线访问
- MMA7660三轴姿态传感器
- 电阻加热
- 温度传感器
- 与ESP32-S2核心模块的接口
实现的功能
PWM控加温电阻
加温电阻的原理为,通过V_HEAT控制mos管的开关,从而控制加温电阻的电流的通断。当mos管导通时,电阻由于电流导通发热,温度升高;当模式管关断时,电阻上没有流经电流,电阻的温度受热辐射的影响,温度降低,向环境温度靠近。
V_HEAT的预留接口在J2 MSP430_IF上,需要用杜邦线将其与J6 STEP_IF(NP)的CA_PWD管脚相连接。
当当前温度低于设定温度时,PWM占空比根据温度差设定,加温电阻升温;当当前温度高于环境温度时,PWM占空比为0,相当于接地状态。
int cal_val;
if(meai<target){
cal_val = (target-meai)*100;
if(cal_val>255)
cal_val = 255;
analogWrite(ca_pwd, cal_val);
// digitalWrite(ca_pwd, HIGH);
}
else{
analogWrite(ca_pwd, 0);
digitalWrite(ca_pwd, LOW);
}
温度的控制最好通过PID算法进行控制,但是由于环境比较理想,要求的精度也不是很高,这里只用了P进行实现。
I2c温度读取温度
首先对测温电路进行研究,观察原理图可以看到,温度测量芯片的通信协议为I2C协议。
根据数据手册可以了解到,由于A0连接GND,所以设备地址为1001000。这也说明同一个I2C总线上最多只能挂载四个温度检测芯片。
DEVICE TWO-WIREADDRESS A0 PIN CONNECTION
1001000 Ground
1001001 VDD
1001010 SDA
1001011 SCL
之后根据I2C的读写时序就可以进行通信了。其中指针寄存器的定义如下表所示,这里选择默认的温度寄存器。
BIT NO. Name Description
Bits 7:2 NA P2 to P7mustalwaysbe 0 during the write command.
Bits 1:0 Pointer[1:0] 00:Temperature register(default)(R only)01: Configuration register(R/w)
10: TLowregister(R/W)
11: THIGH register(R/W)
这里采用的是12bit编码,最低位的单位是0.0625,即T0的单位是0.0625,T4的单位是1,而所以需要对第二个字节的读数除以256。
float readTemperaturec(){
float temp = 0;
uint16_t t = 0;
Wire.beginTransmission(NTS112_ADDRESS);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom((uint16_t)NTS112_ADDRESS, (uint8_t)2);
t = Wire.read();
temp = (float)t;
t = Wire.read();
temp +=(float)t / 256.00;
Wire.endTransmission();
return temp;
}
I2C的实现代码参考群里的一位大佬,由于聊天记录找不到了,无法特别致谢,在此表示抱歉。
旋转编码器设定温度
首先分析旋转编码器的原理,可以看到,他的输出接的是ESP32的模拟IO口。这里可以根据电阻网络分析是哪一个按键按下。
这里直接用ADC去读取相应的值,然后进行分析。结果如下:
静态误差小于100;
旋转误差大于300;
旋转按键大约1200;
按键大约4900。
旋转编码器按键切换调整
由于旋转编码器左旋和右旋的差异很小,尽管可以通过提高ADC的采样频率的方式来具体分析出旋转的方式,但是为了简化开发流程,这里选择使用按键来切换温度的加减调试,而不是通过左旋和右旋。也就是说,在某一个确定的模式下,左旋和右旋的调整结果是一致的,只有当按键按下以后,才会切换调整的方向。
int myinput() {
int res = 0;
// int ana_t0 = 0;
// int ana_t1 = 0;
int diff = 0;
ana_t0 = ana_t1;
ana_t1 = analogRead(A0);
diff = ana_t0 - ana_t1;
if (diff > 4000)
res = BUTT;
else if (diff > 1000)
res = PRES;
else if (diff > 300)
res = REVO;
else if (diff > 300 || (diff < -300 && diff > -1000))
res = REVO;
else
res = 0;
return res;
}
根据观察的规律对ADC的采样值进行判断,差值决定哪个外设有效。
旋转编码器的左转和右转实际上波形是不一致的,但是由于我采用的采样率较低,所以没有进行区分。
int meai = 0;
meai = ((int)(mea*100))/100;
if(Timer_fun_flag == true){
mea = readTemperaturec(); // read temperature
in1 = myinput(); // input detection
}
switch(in1){
case BUTT:
break;
case PRES:
dire_rota = !dire_rota;
// target = target_temp;
break;
case REVO:
if(dire_rota)
target_temp = target_temp + 1;
else
target_temp = target_temp - 1;
if(target_temp > 100)
target_temp = 100;
else if(target_temp < 0)
target_temp = 0;
break;
default:
break;
}
通过旋转调整要设定的温度。
延时确认温度设置
温度调整完以后,需要确定调整后的温度为目标温度。这里本来想通过按键进行相应的确定,后来将方案更改为延时确定。只要一定时间内温度不再变化,那么当前的调整温度会同步为目标温度。当然也可以采用实时确定的方式,但是我感觉那样不优雅。
if(set_val_flag == true){
set_val_flag = false;
if(target_temp_temp != target_temp)
target_temp_temp = target_temp;
else
target = target_temp;
}
通过定时器产生flag信号。若当前设定温度和实际设定温度不一致,则更新实际设定温度。
Spi LCD屏显示温度
LCD的开发花费了很大的功夫,一开始采用的是Adafruit的ST7735的库,但是由于开发板的原理图设计为软件SPI,而软件SPI由于Arduino的开发方式下速度很慢,导致屏幕刷新率低,一直看到屏幕在闪烁,所以之后切换为TFT_eSPI库。
在显示上面,按照题目的要求在LCD上显示温度。为了能够更加好看一点,所以采用了双温度曲线的方案,既显示设定的温度改变曲线和当前设定温度,也显示实际温度曲线和当前的实际温度。
int i = 0;
if(disp_flag == true){
disp_flag = false;
// update
temp_real[ARR_LEN-1] = meai;
temp_set[ARR_LEN-1] = target;
for(i=0; i<ARR_LEN-1; i++){
temp_real[i] = temp_real[i+1];
temp_set[i] = temp_set[i+1];
}
}
uint16_t transparent = 0;
img.createSprite(128, 128); // then create the sprite
img.setColorDepth(8); // Set colour depth first
img.fillSprite(0);
img.setTextColor(TFT_GREEN);
img.setCursor(5, 0);
img.print(mea);
img.setCursor(45, 0);
img.setTextColor(TFT_MAGENTA);
img.println(target_temp);
img.fillCircle(3, 124-temp_real[0], CIR_RDS, TFT_GREEN);
img.drawCircle(3, 124-temp_set[0], CIR_RDS, TFT_MAGENTA);
for(i=1; i<ARR_LEN; i++){
img.fillCircle(3+i*SPACED, 124-temp_real[i], CIR_RDS, TFT_GREEN);
img.drawCircle(3+i*SPACED, 124-temp_set[i], CIR_RDS, TFT_MAGENTA);
img.drawLine(3+(i-1)*SPACED, 124-temp_real[i-1], 3+i*SPACED, 124-temp_real[i], TFT_GREEN);
img.drawLine(3+(i-1)*SPACED, 124-temp_set[i-1], 3+i*SPACED, 124-temp_set[i], TFT_MAGENTA);
}
// Push sprite to TFT screen CGRAM at coordinate x,y (top left corner)
// Specify what colour is to be treated as transparent (black in this example)
img.pushSprite(0, 0);
// Delete Sprite to free memory, creating and deleting takes very little time.
img.deleteSprite();
// }
每次显示之前更新最新的数据。实际温度用绿色显示,曲线为绿色实点。设定温度为紫色显示,曲线为紫色虚点。
RGB彩灯点亮
RGB的点亮由高低电平驱动。根据原理图可以分析得到,三个灯都是低电平有效的,可以采用低电平的驱动方式。
if(meai-target>3 || target-meai>3){
digitalWrite(led3, LOW);
}
else{
digitalWrite(led3, HIGH);
}
当测量温度和设定温度的差距大于三度时,点亮红色LED,否则关闭红色LED。
定时器中断
为了实现中断的效果,采用定时器中断的方式。
void Timer_fun(){
Timer_fun_flag = true;
cnt_tim_disp++;
cnt_tim_set_val++;
if(cnt_tim_disp>=10){
cnt_tim_disp = 0;
disp_flag = true;
}
if(cnt_tim_set_val>=10){
cnt_tim_set_val = 0;
set_val_flag = true;
}
}
定时器的功能只用来计数和生成标志位,最终的功能实现仍由主函数实现。
总结
在设计中遇到的主要问题是LCD的刷新。一开始采用的是Adafruit的ST7735的库,但是由于开发板的原理图设计为软件SPI,而软件SPI由于Arduino的开发方式下速度很慢,导致屏幕刷新率低,一直看到屏幕在闪烁,所以之后切换为TFT_eSPI库。
在本次设计过程中,存在着一些妥协,很多功能由于时间关系没有实现的特别完美,是后续的改善方向。
1、本来希望用esp的工具进行开发的,但是在VSCODE中安装时产生了错误,多次尝试后均无果,最后只能选择Arduino的开发方式。有机会还是希望用原生的工具进行开发;
2、温度控制如果用PID可能曲线和精度会更加理想,需要进行进一步的开发;
3、旋转编码器的左旋右旋设定为一样的功能,可以通过提高采样精度来区分;
4、后续找相关示例,做一个开机动画的,将开机动画融入到主代码中。
致谢
感谢党和国家维护和平稳定的环境,感谢硬禾课堂提供的机会,感谢群内提供I2C例程和帮忙分析LCD刷新率的大佬们。