2023寒假一起练平台(4)- 小王同学基于MSP430的恒温自动控制系统
本次选择的是项目4 - 实现一个恒温自动控制系统:
IO扩展板上有一处加温电阻,将加热区域用物体(纸巾等)包裹起来,通过电流给电阻加热,并通过温度传感器感知板上温度的变化,测温以及在LCD屏上的温度显示。
一、开发版介绍
1、MSP430F5529:
MSP-EXP430F5529LP是一款针对MSP430F5529 USB微控制器的廉价而简单的开发套件。它为MSP430 MCU提供了一种简单的方法,具有用于编程和调试的板载仿真,以及用于简单用户界面的按钮和LED。
2、主要参数特性:
- 支持 USB 2.0 的 MSP430F5529 16 位微控制器
- 高达 25 MHz
- 128KB 闪存和 8KB 内存
- 12 位 SAR 模数转换器
- 提供各种 USB 设备类示例和嵌入式软件库(CDC、HID、MSC)
- eZ-FET lite:带应用程序 UART 的开源板载调试器
- 通过使用板载 USB 集线器,为仿真器和目标提供 USB 连接
- USB 作为电源:5V 和 3.3V 通过高效 DC/DC 转换器
- 40 针 LaunchPad 标准,利用 BoosterPack 生态系统
4、输入、输出扩展板介绍:
本扩展板包含如下功能:
- 按键、旋转编码器输入 - 以模拟信号的方式
- 双电位计控制输入 - 以数字信号的方式
- RGB三色LED显示
- 1.44寸128*128 LCD,SPI总线访问
- MMA7660三轴姿态传感器
- 电阻加热
- 温度传感器
- 与MSP430 Launch Pad开发板的接口
二、设计思路
正如扩展板的原理图所示的加热-->控温:首先控制MOS管导通,使得电阻流过电流产生热量,进而使电路板升温,再使用温度传感器将温度值读出,根据当前的温度值不断地进行反馈控制MOS管通断,使电路板温度保持在设定值范围。
在MSP430的扩展板上,电阻最大加热功率P=U²/R = 5*5/(68//68//68//68) ≈ 1.47W
根据如上所示的设计思路,结合msp430芯片特性,绘制出大体上的软件流程:
三、开发环境准备
1.Platform IO with Visual Studio Code,用于编写代码及下载程序。
2.ComAssistant,用于串口调试
四、项目任务的代码实现
本次项目使用Arduino框架开发,Arduino上手容易,简单快捷。
综合项目的要求,首先确定项目的具体实现需求:
1.读取温度传感器
2.驱动屏幕进行显示
3.产生PWM波形驱动MOS管导通,通过电流给电阻加热
4.使用编码器/按键进行温度的设置
首先是温度传感器,型号为NST112-DSTR,这是一款超小封装的高精度低功耗温度传感器,通过I2C接口数字进行访问以获取温度。该传感器的用法也较为简单,只需配置两个寄存器即可。
#include "Arduino.h"
#include "Wire.h"//IIC库
#define NST112_ADDRESS 0x48 //温度传感器地址
#define GET_ADDR 0X00//读寄存器
#define SET_ADDR 0X01//写寄存器
#define VAL_BASE 0.0625//基准读数
#define NUM_BIT 12//数据位数
#define CFG_CMD_0 0b01100000//数据高
#define CFG_CMD_1 0b11100000//数据低//8HZ
void NST112_Init()
{
Wire.begin();//默认引脚加入
Wire.beginTransmission(NST112_ADDRESS);
Wire.write(SET_ADDR);//进入设置寄存器
Wire.write(CFG_CMD_0);//写寄存器1
Wire.write(CFG_CMD_1 | (NUM_BIT==13?(1<<4):0));//写寄存器2
Wire.endTransmission();//结束
}
float NST112_Read()
{
byte MSB;
byte LSB;
uint16_t value;
float temp;
Wire.beginTransmission(NST112_ADDRESS);
Wire.write(GET_ADDR);//进入温度寄存器
Wire.endTransmission();//结束
Wire.requestFrom(NST112_ADDRESS,2);
while(Wire.available())
{
MSB = Wire.read();
LSB = Wire.read();
value = ((MSB<<8)|LSB) >>(NUM_BIT==13?3:4);
temp = ((float)value)*VAL_BASE;
}
return temp ;
}
屏幕的驱动IC是ST7735,1.44寸128*128分辨率 ,使用SPI接口驱动屏幕进行显示,为了方便,选用了Ucglib驱动库,在屏幕上显示出设定温度、当前温度、PWM占空比及加热状态。这样可以更为直观的看到温度的变化。
ucg.setColor(0, 0, 0);
ucg.drawBox(50, 32, 60, 80);
ucg.setColor(255, 0, 0);
ucg.setPrintPos(50, 105);
ucg.print("Off");
ucg.setPrintPos(50, 105);
ucg.print("Ok");
ucg.setPrintPos(50, 105);
ucg.print("On");
ucg.setPrintPos(50, 45);
ucg.print(Temp_target);
ucg.setPrintPos(50, 65);
ucg.print(Temp_now);
ucg.setPrintPos(50, 85);
ucg.print(Temp_OUT_PWM);
产生PWM波形则使用的是MSP430微控制器芯片的定时器功能,产生 PWM 信号用于其他设备的控制。在Arduino使用起来也是非常方便的。只需要确定输出引脚及占空比数值,使用analogWrite()函数即可。
pinMode(PWM_Pin, OUTPUT);
analogWrite(PWM_Pin, (uint16_t)Temp_OUT_PWM);
当PWM波形为高电平时,驱动MOS管开启,此时电阻流过电流开始发热,当PWM波形为低电平时,驱动MOS管关闭,此时电阻没有电流不发热。这样,通过控制PWM波形的占空比,就可以控制电阻的平均功率,进而控制发热温度。
在这个过程中,使用经久不衰的PID算法,计算出在设定温度下,相应的PWM波形占空比,并且随着实际温度自动进行调整,最终使其达到设定的温度。
在PID算法中,还采用了积分分离的方式,防止温度超调,但是在最终的测试中,由于温控系统惯性环节较大,仅使用PD两项的系数就能达到较好的目标。
//单闭环位置PID//pid结构—目标-实际
float SinglePID_Position(Position_PID *pid, float Target, float Measure)
{
float Err; //误差
if (pid == NULL)
{
// SerialBT.println("pid is NULL");
return 0.0;
}
Err = Target - Measure; //目标值减实际值
/* 积分分离 */
if (abs(Err) > (pid->Integraldead_zone)) //积分绝对值大于 积分盲区
{
pid->index = 0; //积分指数赋值0
}
else
{
pid->index = 1; //积分指数赋值1
}
pid->Output = pid->Kp * Err + pid->Kd * (Err - pid->Last_Err); //pd输出
pid->Integral += pid->Ki * Err * pid->index; //积分累加
constrain(pid->Output, pid->OutputMin, pid->OutputMax);
pid->Integral = constrain(pid->Integral, pid->I_outputMin, pid->I_outputMax); ////积分限幅
pid->Output += pid->Integral; //pid完整的输出
pid->Output = constrain(pid->Output, pid->OutputMin, pid->OutputMax); ////输出限幅
pid->Last_Err = Err; //保存上一次控制量
return pid->Output; //返回pid控制量
}
在该套控制系统中,由于温度传感器自动更新的频率为8Hz,为保证系统的一致性,所以PID程序的执行频率也应为8Hz,因此编写一个简易的定时任务,每隔125ms运行一次即可,包含温度读取,PID计算,更新占空比,刷新屏幕显示。
if ((millis() > Time_timeout) || (Time_timeout < 125))
{
Temp_now = NST112_Read();
Temp_OUT_PWM += SinglePID_Position(&Position_PID_Temp, Temp_target, Temp_now);
Temp_OUT_PWM = constrain(Temp_OUT_PWM, 0, 255);
ucg.setColor(0, 0, 0);
ucg.drawBox(50, 32, 60, 80);
ucg.setColor(255, 0, 0);
// ucg.setColor(1, 255, 0, 0);
if (Heating == 0)
{
Temp_OUT_PWM = 0;
digitalWrite(LED_R_Pin, 1);
ucg.setPrintPos(50, 105);
ucg.print("Off");
}
else
{
// ledcWrite(ledChannel, (uint32_t)Temp_OUT_PWM);
if (abs(Temp_now - Temp_target) < 1.0F)
{
digitalWrite(LED_R_Pin, 0);
ucg.setPrintPos(50, 105);
ucg.print("Ok");
}
else
{
digitalWrite(LED_R_Pin, 1);
ucg.setPrintPos(50, 105);
ucg.print("On");
}
}
analogWrite(PWM_Pin, (uint16_t)Temp_OUT_PWM);
ucg.setPrintPos(50, 45);
ucg.print(Temp_target);
ucg.setPrintPos(50, 65);
ucg.print(Temp_now);
ucg.setPrintPos(50, 85);
ucg.print(Temp_OUT_PWM);
Time_timeout = millis() + Temp_Timeout; //加上下一次的刷新毫秒输
Serial.print("{T}"); //当前温度
Serial.println(Temp_now);
Serial.print("{Tt}"); //目标温度
Serial.println(Temp_target);
Serial.print("{PWM}"); //当前PWM值
Serial.println(Temp_OUT_PWM);
}
最后是编码器、按键的部分,由于扩展板电路设计比较奇特,按键和编码器使用的是电阻网络分压的方案,因此使用ADC读取相应引脚的数值,即可判断出是某个按键按下,但是编码器比较特殊,硬件电路将编码器的AB相及中央按键均接入了该电阻网络,单次的读取并不能体现是否旋转,需要高频率地进行ADC采样,根据结果进行判断正/反转
下图为采样频率200Hz的ADC采集数据。
图1为顺时针旋转,图2为逆时针旋转,图三分别为编码器中央按键、普通按键上、普通按键下
可以看出来编码器在进行不同方向的旋转时,ADC检测到的电压台阶顺序是不一样的,因此根据这个顺序,在符合电压最低处时判断上一台阶的电压值,即可分析出是哪个方向的旋转。
事先使用手动方式测量出各个操作ADC的测量值,再根据此值判断即可
//ADC的几个测量值,对应无操作、编码器旋转(3个)、编码器按键按下、普通按键上、普通按键下按下。
uint16_t ADC_Val[7] = {3965, 3833, 3580, 3710, 3450, 2942, 1910};
#define ADC_error 50 //ADC的上下范围
//得到按键的值
void key_scan()
{
ADC0 = analogRead(ADC_Pin);
// Serial.print(millis());
Serial.print("{A}");
Serial.println(ADC0);
// Serial.print("{K}");
// Serial.println(key_msg.id);
if ((ADC0 > (ADC_Val[0] - ADC_error)) && (ADC0 < (ADC_Val[0] + ADC_error))) //未按下
{
for (uint8_t i = 0; i < (sizeof(key) / sizeof(KEY)); ++i)
{
key[i].val = 1;
}
}
else if ((ADC0 > (ADC_Val[1] - ADC_error)) && (ADC0 < (ADC_Val[1] + ADC_error))) //旋转1
{
key[0].val = 0;
}
else if ((ADC0 > (ADC_Val[2] - ADC_error)) && (ADC0 < (ADC_Val[2] + ADC_error))) //旋转2
{
key[1].val = 0;
}
else if ((ADC0 > (ADC_Val[3] - ADC_error)) && (ADC0 < (ADC_Val[3] + ADC_error))) //旋转3
{
key[2].val = 0;
}
else if ((ADC0 > (ADC_Val[4] - ADC_error)) && (ADC0 < (ADC_Val[4] + ADC_error))) //编码器按键
{
key[3].val = 0;
}
else if ((ADC0 > (ADC_Val[5] - ADC_error)) && (ADC0 < (ADC_Val[5] + ADC_error))) //普通按键1
{
key[4].val = 0;
}
else if ((ADC0 > (ADC_Val[6] - ADC_error)) && (ADC0 < (ADC_Val[6] + ADC_error))) //普通按键1
{
key[5].val = 0;
}
else
{
}
for (uint8_t i = 0; i < (sizeof(key) / sizeof(KEY)); ++i) //变更状态
{
if (key[i].last_val != key[i].val) //发生改变
{
key[i].last_val = key[i].val; //更新状态
if (key[i].val == LOW)
{
key_msg.id = i;
// key_msg.pressed = true;
}
}
}
if ((key_msg.id == 0) || (key_msg.id == 1) || (key_msg.id == 2))
{
if ((key_msg.id == 1)) //转到中间1的时候,判断上一次的值
{
switch (Encoder_Val_last)
{
case 0: //右转
Encoder_Direction = 1;
Temp_target += Add_Val;
break;
case 2: //左转
Encoder_Direction = 0;
Temp_target -= Add_Val;
break;
default:
break;
}
}
Encoder_Val_last = key_msg.id;
key_msg.id = (-1);
}
}
void KEY_update()
{
if (key_msg.id != (-1))
{
if ((key_msg.id == 0) || (key_msg.id == 1) || (key_msg.id == 2))
{
if ((key_msg.id == 1)) //转到中间1的时候,判断上一次的值
{
switch (Encoder_Val_last)
{
case 0: //右转
Encoder_Direction = 1;
// Temp_target += Add_Val;
break;
case 2: //左转
Encoder_Direction = -1;
// Temp_target -= Add_Val;
break;
default:
break;
}
}
}
else if (key_msg.id == 3) //编码器按键
{
KEY_Encoder = false;
}
else if (key_msg.id == 4) //上按键
{
KEY_Key1 = false; //被按下为0
}
else if (key_msg.id == 5) //下按键
{
KEY_Key2 = false; //被按下为0
}
Encoder_Val_last = key_msg.id;
key_msg.id = (-1);
}
}
五、功能展示
按下编码器中央按键时,切换开启/关闭加热,按下普通按键上时,切换需要设置的温度位数(10、1、0.1),顺时针旋转编码器增加设定值,逆时针旋转编码器减少设定值。
当温度达到设定温度的±1℃时(原先要求的是±3℃,这里稍微提高了一下控制精度),亮起红灯,并在屏幕上显示“OK”字样。
六、遇到的主要难题及解决方法
感觉该项目的主要难点就在编码器/按键方面,通过ADC读值的方式确定按键的状态,这样的好处也很明显,就是只用一个GPIO的端口即可完成多个按键的读取再有就是关于PID的部分,但是缺点就是需要高频率地使用ADC去读取模拟电压值,消耗时间,再加上MSP430微控制器本身频率就不高,还要进行彩屏的驱动。在寻找屏幕可用的驱动库时发现,由于MSP430的Arduino程序,与其他的互联网上大部分库均不能兼容,可能是由于编译器的版本问题,而且找到的这个库也不是很完善,屏幕右侧会出现两列花屏,在翻看了库中的代码后,也没有找到设置偏移是在哪里。最后由于温控系统的大惯性环节,当PWM占空比升高后,温度上升并没有那么迅速,因此PID中的微分系数需要比较大,才能使得系统稳定,且使用analogWrite()函数,占空比只能为8位(0-255),对温控的精确度方面有着不小的阻碍。
七、后续完善计划
作为一个基本能够使用的恒温自动控制系统,已基本满足要求,但是还是有许多不足之处可以完善,比如在界面不够精美,按键交互不太完善,抗干扰性能不强等,由于时间关系暂未解决,后续的话有时间再抽空完善一下,这样总体的感觉也会提升。
八、参考资料
1.Arduino https://www.arduino.cc/
2.Energia https://energia.nu/
2.PlatformIO https://platformio.org/
3.MSP430F5529 https://www.ti.com.cn/product/cn/MSP430F5529?
4.高精度、低功耗数字温度传感器NST112 https://www.novosns.com/news-center-57
5.积分分离PID控制算法https://blog.csdn.net/songyulong8888/article/details/117389191