硬件介绍:
硬件分为两个部分:第一部分为一个带屏版的12指神探,它是一款基于树莓派基金会推出的微控制器RP2040制作的多功能硬件调试助手,它有12根引脚,提供了5V和3.3V的电压,其中有9根GPIO,功能灵活,通过搭配不同程序可以做成各种调试器。第二部分:是一块扩展板。搭载了几款常见传感器和功能模块,包括麦克风、蜂鸣器、、红外收发、霍尔效应开关、加热电阻,温湿度传感器、六轴传感器、接近/环境光/IR传感器、颜色传感器。
任务选择:
我选择的是任务1:恒温或恒湿控制系统。任务要求:至少使用一种自控算法控制,如PID等,可用按键或拨轮控制目标温度可在LCD屏上显示目标温度及实时温度。
编程语言我使用Arduino。开发工具使用 Vscode+platformio
任务实现:
查看电路图可以得知,扩展板上有4颗电阻加一个MOS管构成了一个可控加热装置。rp2040可以通过管脚控制MOS管的开关。加热装置上还有一颗NSHT30传感器,用来测量温湿度,通过IIC与rp2040通讯。
第一步,先搞定温湿度传感器。这里传感器使用了sht30,在platformio的库里搜索,找到一个“SHTSensor”库,能够很方便很方便地驱动起这颗温湿度传感器。
#include <Wire.h>
#include "SHTSensor.h"
SHTSensor sht;
void setup()
{
Wire.begin();
Serial.begin(115200);
if (sht.init())
{
Serial.print("init(): success\n");
}
else
{
Serial.print("init(): failed\n");
}
sht.setAccuracy(SHTSensor::SHT_ACCURACY_MEDIUM); // only supported by SHT3x
}
void loop()
{
// put your main code here, to run repeatedly:
if (sht.readSample())
{
Serial.print("SHT: ");
Serial.print(" RH: ");
Serial.print(sht.getHumidity(), 2);
Serial.print(" T: ");
Serial.print(sht.getTemperature(), 2);
Serial.print("\n");
}
else
{
Serial.print("Error in readSample()\n");
}
delay(1000);
}
这里需要留意一下,这个板子的IIC接的管脚为SDA(20),SCL(21),和默认管脚不同,需要在配置文件中做修改。
第二步,驱动屏幕。这里的屏幕使用的是ST7789驱动,使用TFT_eSPI库就轻易地将屏幕驱动起来了。有尝试过LVGL,但是移植失败。TFT_eSPI显示中文比较麻烦,就增加了个U8g2_for_TFT_eSPI库,用来显示中文。
#include "SPI.h"
#include "TFT_eSPI.h"
#include "U8g2_for_TFT_eSPI.h"
TFT_eSPI tft = TFT_eSPI(); // tft instance
U8g2_for_TFT_eSPI u8f; // U8g2 font instance
void setup()
{
tft.begin();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
u8f.begin(tft); // connect u8g2 procedures to TFT_eSPI
}
unsigned long x = 0;
void loop()
{
u8f.setFontMode(0); // use u8g2 none transparent mode
u8f.setFontDirection(0); // left to right (this is default)
u8f.setForegroundColor(TFT_WHITE); // apply color
u8f.setFont(u8g2_font_wqy15_t_gb2312);
// u8g2.drawStr(10,20,"你好");
u8f.setCursor(0, 20);
u8f.print("你好");
u8f.setCursor(0, 40); // start writing at this position
u8f.print("Umlaut ÄÖÜ"); // UTF-8 string with german umlaut chars
u8f.setFont(u8g2_font_inb63_mn); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
u8f.setFontMode(0); // use u8g2 none transparent mode
while (1)
{
u8f.setCursor(0, 110); // start writing at this position
u8f.print(x); // numerical value
x++;
delay(250);
}
}
第三步,驱动按键。需要使用按键与系统交互,用来控制系统运行状态和修改设定温度。这里使用了AceButton库,定义了三个按键。分别对应旋钮的 左、中、右。中间键控制运行状态,运行状态分为两类:运行态和设置态。运行态时通过PWM控制mos管进行加热。设置态分为两档,慢速调节,左右两个按键每次上下调节1度,快速调节,左右两个按键每次上下调节2度。设置态时禁止mos管加热。
// The event handler for both buttons.
void handleEvent(AceButton *button, uint8_t eventType, uint8_t buttonState)
{
if (eventType == AceButton::kEventReleased)
{
switch (button->getPin())
{
case BUTTONL_PIN:
settemperature -= setstat;
break;
case BUTTONR_PIN:
settemperature += setstat;
break;
case BUTTONO_PIN:
setstat = (setstat + 1) % 3;
break;
}
if (settemperature > TEMPMAXVAL)
settemperature = TEMPMAXVAL;
if (settemperature < TEMPMINVAL)
settemperature = TEMPMINVAL;
diapSetTemp();
}
}
第四步,添加PID控制。PID算法在自动化控制里边真的是很厉害的一个算法,鲁棒性超强。本质上就是面多了加水、水多了加面的一个操作。这里使用PWM控制mosmos管的开关,需要加热时,就打开模式管,让电流通过电阻,进行加热。当温度达到或超过设定温度时,就关闭mos管,通过环境给电阻降温。所以设定温度不能低于环境温度,否则无法降温。
#include "tempturepid.h"
PID pid;
void PID_Init()
{
pid.Kp = 210;
pid.ki=0.125;
pid.kd=0.08;
}
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; //更新偏差
}
第五步,调整PID的参数,让温度震荡在可接受的范围内。设计个简单的UI。最上方使用小字体显示当前运行状态和湿度。中间一行用较大字体显示设定值。最下方用大字体显示实时温度。当处于设置模式时,慢速设定时使用棕色字体显示设定值,以1度为步进值;当为快速设定时,使用红色字体显示设定值,以2度为步进值。当切换到运行状态时,用绿色字体显示设定值,当mos管导通时,红色LED灯会亮起,并且通过串口将pwm输出值显示出来。
心得体会:
非常开心参加寒假一起练活动,边玩边学,即获得了开心,又打开了单片机的世界。开发过程中得到了交流群里很多老师的帮助,在此表示非常感谢!期待学习各位老师的作品!