项目介绍
功能介绍
本项目基于 搭配带屏12指神探的传感器扩展板 实现的恒温控制器,实现功能有,加热控制,恒温控制,实现温度显示,目标温度显示,目标与实时温度曲线控制。使用PID进行恒温控制。
硬件介绍
本项目使用的硬件为带屏版的12指神探,它是在原板基础上,配备了一块240*240分辨率的LCD彩屏以及两个可程控按键和一个拨轮,丰富了人机交互功能,方便信息观察、界面切换等使用方式。此外还配备了白色外壳,精心设计的包装不仅使板卡日常使用时更加美观也便于板卡的站立以及使用安全。
主控芯片:采用树莓派Pico核心芯片RP2040
- 双Arm Cortex M0+内核,可以运行到133MHz
- 264KBSRAM,板卡上外扩2MBFlash
- 性能强大、高度灵活的可编程IO(PIO)可用于高速数字接口
- 拥有2个UART、2个SPI、2个I2C、16个PWM通道以及4路12位精度ADC
- 支持MicroPython、C、C++编程
- 拖拽UF2固件至U盘的方式烧录固件,方便快捷
搭配一块 12指神探的传感器扩展板,接口完全适配,正确方向插入后即可使用。扩展板搭载了几款常见传感器和功能模块,包括为初学者准备的麦克风、蜂鸣器、、红外收发、霍尔效应开关、加热电阻,为进阶操作准备的温湿度传感器、六轴传感器、接近/环境光/IR传感器、颜色传感器。其中温湿度传感器、六轴传感器、接近传感器、颜色传感器可拆卸为单个模块,通过杜邦线等连接线延伸其使用的空间范围。出厂默认传感器正面朝上使用。若需背面朝上使用,则自行焊接排母后按指示方向插入。
软件流程图
如上图所示,程序启动后,对各部分进行初始化,如:按钮输入,加热控制输出,温度采集,LCD显示等。初始化完成后,程序进入主循环,在主循环中需要监听按键状态,采集温度数据,并更新绘制到LCD屏幕上。按键的功能主要为控制,是否启动加热,以及调整目标温度值。
设计思路
基于 搭配带屏12指神探的传感器扩展板 实现的恒温控制器。所需实现的基本功能有:温度采集、温度显示、恒温控制、加热控制、目标温度设定、温度曲线绘制。 本次实现采用 c
语言开发,软件开发环境为:VSCode + PlatformIO + Arduino 基于以上需求,实现原理,采集到 NSHT30 传感器的温度数据,根据 PID 算法与 目标温度 进行比较计算,得出控制加热器的 PWM 输出频率。并使用 PWM 输出到加热引脚控制 MOSFET 的开关来控制加热以及加热的功率,以达到恒定在目标温度的目的。
功能实现
按键监听
如上图所示,带屏12指神探的按钮,连接到 RP2040 的 5、6、7、8、9 五个管脚,因此通过监听该五个管脚的按键状态,即可知道是哪个按键被按下。 五个按键分别定义为 Menu键、Select键,拔轮左键、拔轮中键、拔轮右键,各按键定义的功能如下:
- Menu键、Select键:停止加热。
- 拔轮左键:减小目标温度 1 摄氏度。
- 拔轮中键:启动加热控制。
- 拔轮右键:增加目标温度 1 摄氏度。
实现部分关键代码如下:
/* 初始化按键监听 */
#define BUTTON_MENU p5 // Menu 键
#define BUTTON_SELECT p6 // Select 键
#define BUTTON_LEFT p7 // 拔轮中键
#define BUTTON_OK p8 // 拔轮左键
#define BUTTON_RIGHT p9 // 拔轮右键
// 按键初始化
OneButton btnMenu(BUTTON_MENU, true);
OneButton btnSelect(BUTTON_SELECT, true);
OneButton btnLeft(BUTTON_LEFT, true);
OneButton btnOk(BUTTON_OK, true);
OneButton btnRight(BUTTON_RIGHT, true);
//
void menu_select_interrupt(void *parameter) {
if(parameter == &btnMenu || parameter == &btnSelect) {
Serial.println("key menu or select");
heater_power = false; // 关闭加热
} else if(parameter == &btnLeft) {
Serial.println("key left");
// 目标温度减小1度
target_temperature -= 1.0;
tft.fillRect(50, 200, 80, 80, TFT_WHITE);
tft.setTextColor(TFT_RED);
tft.drawFloat(target_temperature, 1, 80, 225);
} else if(parameter == &btnOk) {
Serial.println("key ok");
heater_power = true; // 打开加热
} else if(parameter == &btnRight) {
Serial.println("key right");
// 目标温度增加1度
target_temperature += 1.0;
tft.fillRect(50, 200, 80, 80, TFT_WHITE);
tft.setTextColor(TFT_RED);
tft.drawFloat(target_temperature, 1, 80, 225);
}
}
void init_key_listener() {
// 按键回调函数配置
btnMenu.attachClick(menu_select_interrupt, &btnMenu);
btnSelect.attachClick(menu_select_interrupt, &btnSelect);
btnLeft.attachClick(menu_select_interrupt, &btnLeft);
btnOk.attachClick(menu_select_interrupt, &btnOk);
btnRight.attachClick(menu_select_interrupt, &btnRight);
}
void key_ticks() {
btnMenu.tick();
btnSelect.tick();
btnLeft.tick();
btnOk.tick();
btnRight.tick();
}
LCD屏显示
扩展板上带有一块1.54寸的TFT LCD屏幕,采用SPI通信。使用 TFT_eSPI 图形库绘制UI显示温度及温升曲线。
实现部分关键代码如下:
tft.begin();
tft.setRotation(0);
// 填充屏幕为白色
tft.fillScreen(TFT_WHITE);
tft.drawRoundRect(10, -10, 220, 50, 10, TFT_LIGHTGREY);
tft.setTextColor(TFT_BLACK);
tft.setTextSize(2);
tft.setCursor(90, 10);
tft.println("Heater");
// Graph area is 200 pixels wide, 150 pixels high, dark grey background
gr.createGraph(210, 110, TFT_WHITE);
// x scale units is from 0 to 100, y scale units is -512 to 512
gr.setGraphScale(gxLow, gxHigh, gyLow, gyHigh);
// X grid starts at 0 with lines every 20 x-scale units
// Y grid starts at -512 with lines every 64 y-scale units
// blue grid
gr.setGraphGrid(gxLow, 20.0, gyLow, 26.0, TFT_LIGHTGREY);
// Draw empty graph, top left corner at pixel coordinate 40,10 on TFT
gr.drawGraph(20, 60);
tft.setTextSize(1);
// Draw the x axis scale
tft.setTextDatum(TC_DATUM); // Top centre text datum
tft.drawNumber(0, gr.getPointX(0.0), gr.getPointY(-50.0) + 3);
tft.drawNumber(50, gr.getPointX(50.0), gr.getPointY(-50.0) + 3);
tft.drawNumber(100, gr.getPointX(100.0), gr.getPointY(-50.0) + 3);
// Draw the y axis scale
tft.setTextDatum(MR_DATUM); // Middle right text datum
tft.drawNumber(-50, gr.getPointX(0.0), gr.getPointY(-50.0));
tft.drawNumber(0, gr.getPointX(0.0), gr.getPointY(0.0));
tft.drawNumber(50, gr.getPointX(0.0), gr.getPointY(50.0));
tft.setCursor(10, 220);
tft.setTextColor(TFT_RED);
tft.println("target:");
tft.drawFloat(target_temperature, 1, 80, 225);
tft.setCursor(130, 220);
tft.setTextColor(TFT_GREEN);
tft.println("curr: ");
// Restart traces with new colours
tr1.startTrace(TFT_RED);
tr2.startTrace(TFT_GREEN);
温度采集
如上原理图所示,温度采集是基于I2C通信协议的 NSHT30 芯片,通过I2C协议与芯片通信,获取当前传感器采集到的实时温度。
实现部分关键代码如下:
if(nsht30.measure()) { // 刷新当前温度
tft.fillRect(170, 200, 80, 80, TFT_WHITE);
current_temperature = nsht30.temperature();
tft.setTextColor(TFT_GREEN);
tft.drawFloat(current_temperature, 1, 200, 225);
}
加热控制
监听按键按下,软件判断当前加热工作状态,对加热器进行打开或关闭操作。从上面原理图可得知控制 RP2040 的相应管脚输出PWM,即可控制加热电阻的功率。系统的目标是恒温,因此需要根据当前采集到的实时温度与目标温度进行比较,使用 PID 算法计算出一个合理的PWM值,以控制加热器稳定在目标温度附近。
实现部分关键代码如下:
// 加热控制
if(heater_power) {
PWM_Instance->setPWM(p22, frequency, myPID.step(target_temperature, current_temperature));
} else {
PWM_Instance->setPWM(p22, frequency, 0);
}
功能展示
UI设计稿
加热与恒温控制曲线
总结
本次任务实现过程遇到了挺多困难的,原本以为可以使用 Arduino 进行开发,实际使用时才发现虽然可以使用 Arduino 进行开发,但很多库出现了不兼容的情况,导致为了实现某一功能不得不加入了一些额外的库,例如:加热控制的 PWM 输出,就使用了 RP2040_PWM 库。而原本打算使用 LVGL 来绘制 UI,结果实现时发现 LVGL 的 timer 在 RP2040 上也无法正常运行,因为找不到解决方案,只能退而求其次,使用 TFT_eSPI 进行 UI 的绘制。
最后感谢电子森林推出的 《寒假练》 系列活动,对于我来说是个很好的学习机会,理论结合实践。我们下期活动再见!
参考资料
- https://www.eetree.cn/doc/2356
- https://www.novosns.com/files/NSHT30_LGA_Datasheet(EN)_1.0.pdf
- https://www.novosns.com/files/ApplicationNote_wenshiduchuanganqiNSHT30shiyongshejizhinan.pdf
- https://github.com/khoih-prog/RP2040_PWM.git
- https://github.com/Bodmer/TFT_eSPI.git
- https://github.com/Bodmer/TFT_eWidget.git
- https://github.com/mathertel/OneButton.git
- https://github.com/mike-matera/FastPID.git