零知IDE——基于STM32F407VET6和雨滴传感器的多界面TFT降雨监测显示系统
✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
www.lingzhilab.com
目录
一、硬件连接部分
1.1 硬件清单
1.2 接线方案表
1.3 具体接线图
1.4 连接实物图
二、核心代码解析
2.1 初始化设置
2.2 数据读取与映射
2.3 防闪烁更新机制
2.4 条形图表绘制
2.5 完整代码
三、项目结果演示
3.1 操作流程
3.2 界面展示
3.3 视频演示
四、雨滴传感器工作原理
4.1 基本原理
4.2 工作过程
4.3 模数转换原理
五、常见问题解答
Q1: 为什么雨量百分比显示不正确?
Q2: 如何调整报警阈值?
Q3: 历史数据图表不更新怎么办?
(1)项目概述
本项目基于STM32F407VET6主控芯片,结合雨滴传感器和240×240分辨率ST7789 TFT显示屏,开发了一套智能降雨监测系统。系统能够实时监测降雨情况,通过三种不同的UI界面展示数据,具备数据可视化、历史趋势分析和报警功能。
(2)项目功能及亮点
功能描述:数据映射关系处理,实时雨量百分比监测与显示
系统亮点:正确处理雨滴传感器的电阻特性,实现从模拟值到百分比的合理映射
一、硬件连接部分
1.1 硬件清单
组件 | 规格 | 数量 |
---|---|---|
主控板 | STM32F407VET6 | 1 |
雨滴传感器 | 模拟输出型 | 1 |
TFT显示屏 | ST7789 240×240 | 1 |
报警LED | 5mm LED | 1 |
连接线 | 杜邦线 | 若干 |
1.2 接线方案表
根据代码定义的引脚分配:
组件 | 引脚功能 | 零知增强板引脚 |
---|---|---|
雨滴传感器 | 模拟输入 | A1 |
报警LED | 数字输出 | 9 |
ST7789 | 片选信号 | 53 |
ST7789 | 复位信号 | 6 |
ST7789 | 数据/命令 | 7 |
ST7789 | SPI时钟 | 52 |
ST7789 | SPI数据 | 51 |
1.3 具体接线图
1.4 连接实物图
二、核心代码解析
2.1 初始化设置
void setup() {pinMode(RAIN_SENSOR, INPUT);pinMode(ALERT, OUTPUT);Serial.begin(9600);// 初始化显示屏tft.init(240, 240);tft.setRotation(1);tft.fillScreen(BACKGROUND_COLOR);tft.setTextWrap(false);// 显示启动画面showStartupScreen();delay(2500);// 绘制初始界面drawCurrentPage();Serial.println("Rain Sensor with TFT Display Started");}
tft.setRotation(1)设置屏幕方向为横屏、tft.setTextWrap(false)禁止文本自动换行
2.2 数据读取与映射
void readSensorData() {rawValue = analogRead(RAIN_SENSOR) / 4; // 适配4096的模拟值// 反转映射关系:无雨时电阻大,模拟值大,我们希望无雨时百分比小sensorValue = map(rawValue, 0, 1023, 100, 0); // 反转映射// 保存历史数据historyValues[historyIndex] = sensorValue;historyIndex = (historyIndex + 1) % 15;// 设置报警状态(阈值可调整)alertState = (sensorValue > 70); // 大于70%认为有雨(因为映射已反转)}
模拟值除以4适配4096分辨率ADC、使用map()函数实现数值范围映射
2.3 防闪烁更新机制
void updateCurrentPage() {// 只有在数据变化时才更新显示,避免闪烁if (sensorValue != lastSensorValue || alertState != lastAlertState || rawValue != lastRawValue) {switch(currentPage) {case 0: updatePage1(); break;case 1: updatePage2(); break;case 2: updatePage3(); break;}// 更新上一次的值lastSensorValue = sensorValue;lastAlertState = alertState;lastRawValue = rawValue;}// 页面2(图表页面)需要持续更新if (currentPage == 1 && millis() - lastChartUpdate > 1000) {updatePage2Chart();lastChartUpdate = millis();}
}
数据变化检测避免不必要刷新、图表页面单独处理,保证实时性、时间戳控制刷新频率
2.4 条形图表绘制
void updatePage2Chart() {// 计算条形图参数(居中显示,增加间隔)int barCount = 12;int barWidth = 10;int barSpacing = 6;int totalWidth = barCount * barWidth + (barCount - 1) * barSpacing;int startX = 130 - totalWidth / 2; // 居中计算// 绘制条形图for (int i = 0; i < barCount; i++) {int valueIndex = (historyIndex - barCount + i + barCount) % barCount;int barHeight = map(historyValues[valueIndex], 0, 100, 0, chartHeight);// 选择颜色 - 使用渐变效果uint16_t barColor;if (historyValues[valueIndex] > 70) {barColor = WARNING_COLOR; // 红色报警} else if (historyValues[valueIndex] > 40) {barColor = ST77XX_ORANGE; // 橙色警告} else {barColor = BAR_COLOR; // 蓝色正常}tft.fillRect(x, y, barWidth, barHeight, barColor);}
}
渐变颜色指示不同状态、时间轴标签显示
2.5 完整代码
#include <Adafruit_GFX.h>#include <Adafruit_ST7789.h>#include <SPI.h>// 引脚定义#define ST77XX_DARKGREY 0x7453#define RAIN_SENSOR A1#define ALERT 9#define TFT_CS 53#define TFT_RST 6#define TFT_DC 7// 创建显示屏对象Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);// 变量定义int sensorValue = 0;int rawValue = 0;bool alertState = false;unsigned long lastDisplayUpdate = 0;unsigned long lastSensorRead = 0;unsigned long lastChartUpdate = 0;int currentPage = 0;const int TOTAL_PAGES = 3;const int PAGE_DURATION = 10000; // 页面切换时间8秒// 颜色定义#define BACKGROUND_COLOR ST77XX_BLACK#define TEXT_COLOR ST77XX_WHITE#define WARNING_COLOR ST77XX_RED#define NORMAL_COLOR ST77XX_GREEN#define BAR_COLOR ST77XX_BLUE#define ACCENT_COLOR ST77XX_CYAN#define GRID_COLOR 0x4208 // 深灰色// 页面切换动画相关int animationOffset = 0;bool isAnimating = false;unsigned long animationStartTime = 0;// 历史数据记录int historyValues[15] = {0};int historyIndex = 0;// 防止闪烁的变量int lastSensorValue = -1;bool lastAlertState = false;int lastRawValue = -1;void setup() {pinMode(RAIN_SENSOR, INPUT);pinMode(ALERT, OUTPUT);Serial.begin(9600);// 初始化显示屏tft.init(240, 240);tft.setRotation(1);tft.fillScreen(BACKGROUND_COLOR);tft.setTextWrap(false);// 显示启动画面showStartupScreen();delay(2500);// 绘制初始界面drawCurrentPage();Serial.println("Rain Sensor with TFT Display Started");}void loop() {unsigned long currentTime = millis();// 每800ms读取一次传感器数据if (currentTime - lastSensorRead >= 800) {readSensorData();lastSensorRead = currentTime;}// 每8秒切换页面if (currentTime - lastDisplayUpdate >= PAGE_DURATION) {switchToNextPage();lastDisplayUpdate = currentTime;}// 处理页面切换动画if (isAnimating) {handlePageAnimation();} else {// 更新当前页面数据(局部刷新)updateCurrentPage();}// 控制报警LEDdigitalWrite(ALERT, alertState ? HIGH : LOW);}void readSensorData() {rawValue = analogRead(RAIN_SENSOR) / 4; // 适配4096的模拟值// 反转映射关系:无雨时电阻大,模拟值大,我们希望无雨时百分比小sensorValue = map(rawValue, 0, 1023, 100, 0); // 反转映射// 保存历史数据historyValues[historyIndex] = sensorValue;historyIndex = (historyIndex + 1) % 15;// 设置报警状态(阈值可调整)alertState = (sensorValue > 70); // 大于70%认为有雨(因为映射已反转)Serial.print("Raw: ");Serial.print(rawValue);Serial.print(" - Rain Level: ");Serial.print(sensorValue);Serial.print("% - Alert: ");Serial.println(alertState ? "ON" : "OFF");}void switchToNextPage() {currentPage = (currentPage + 1) % TOTAL_PAGES;isAnimating = true;animationOffset = 240; // 从右侧开始animationStartTime = millis();// 重置上一次的值,确保新页面完全绘制lastSensorValue = -1;lastAlertState = !alertState;lastRawValue = -1;}void handlePageAnimation() {unsigned long currentTime = millis();unsigned long elapsed = currentTime - animationStartTime;// 动画持续时间400msif (elapsed < 400) {// 计算动画偏移量(缓动效果)float progress = (float)elapsed / 400.0;animationOffset = 240 - (int)(240 * easeOutCubic(progress));// 绘制动画帧drawPageAnimation();} else {isAnimating = false;animationOffset = 0;drawCurrentPage(); // 最终绘制完整页面}}float easeOutCubic(float x) {return 1 - pow(1 - x, 3);}void drawPageAnimation() {tft.fillScreen(BACKGROUND_COLOR);// 绘制新页面(带偏移)switch(currentPage) {case 0: drawPage1(animationOffset); break;case 1: drawPage2(animationOffset); break;case 2: drawPage3(animationOffset); break;}}void drawCurrentPage() {switch(currentPage) {case 0: drawPage1(0); break;case 1: drawPage2(0); break;case 2: drawPage3(0); break;}}void updateCurrentPage() {// 只有在数据变化时才更新显示,避免闪烁if (sensorValue != lastSensorValue || alertState != lastAlertState || rawValue != lastRawValue) {switch(currentPage) {case 0: updatePage1(); break;case 1: updatePage2(); break;case 2: updatePage3(); break;}// 更新上一次的值lastSensorValue = sensorValue;lastAlertState = alertState;lastRawValue = rawValue;}// 页面2(图表页面)需要持续更新if (currentPage == 1 && millis() - lastChartUpdate > 1000) {updatePage2Chart();lastChartUpdate = millis();}}void showStartupScreen() {tft.fillScreen(ST77XX_BLACK);// 主标题tft.setTextColor(ST77XX_GREEN);tft.setTextSize(3);tft.setCursor(25, 80);tft.println("RAIN SENSOR");// 副标题tft.setTextColor(ST77XX_WHITE);tft.setTextSize(2);tft.setCursor(85, 120);tft.println("SYSTEM");// 进度条 - 粗进度条tft.drawRoundRect(40, 160, 160, 20, 10, ST77XX_WHITE);for(int i = 0; i <= 160; i += 8) {tft.fillRoundRect(40, 160, i, 20, 10, ST77XX_BLUE);delay(30);}}// 页面1: 简洁数据展示void drawPage1(int offset) {tft.fillScreen(BACKGROUND_COLOR);// 标题 - 左上角tft.setTextColor(ACCENT_COLOR);tft.setTextSize(2);tft.setCursor(10 + offset, 10);tft.println("RAIN LEVEL");// 分隔线tft.drawFastHLine(10 + offset, 35, 220, GRID_COLOR);// 绘制静态内容drawPage1Static(offset);}void drawPage1Static(int offset) {// 状态指示标签tft.setTextColor(TEXT_COLOR);tft.setTextSize(2);tft.setCursor(20 + offset, 160);tft.print("Raw: ");// 页面指示tft.setCursor(80 + offset, 220);tft.print("Page 1/");tft.print(TOTAL_PAGES);}void updatePage1() {// 清除数据区域tft.fillRect(10, 50, 220, 100, BACKGROUND_COLOR);// 大字体显示百分比tft.setTextColor(sensorValue > 70 ? WARNING_COLOR : TEXT_COLOR);tft.setTextSize(4);tft.setCursor(85, 70);tft.print(sensorValue);tft.setTextSize(2);tft.println("%");// 状态指示tft.setTextSize(2);tft.setCursor(75, 120);if (sensorValue > 70) {tft.setTextColor(WARNING_COLOR);tft.println("RAINING!");} else if (sensorValue > 30) {tft.setTextColor(ST77XX_YELLOW);tft.println("CLOUDY");} else {tft.setTextColor(NORMAL_COLOR);tft.println("CLEAR");}// 原始值显示tft.setTextColor(ACCENT_COLOR);tft.fillRect(50, 150, 100, 30, BACKGROUND_COLOR);tft.setTextSize(2);tft.setCursor(20, 160);tft.print("Raw: ");tft.print(rawValue);}// 页面2: 现代化图表展示void drawPage2(int offset) {tft.fillScreen(BACKGROUND_COLOR);// 标题 - 左上角tft.setTextColor(ACCENT_COLOR);tft.setTextSize(2);tft.setCursor(10 + offset, 10);tft.println("RAIN HISTORY");// 分隔线tft.drawFastHLine(10 + offset, 35, 220, GRID_COLOR);// 绘制图表框架drawChartFrame(offset);// 绘制静态标签tft.setTextColor(TEXT_COLOR);tft.setTextSize(1);tft.setCursor(10 + offset, 200);tft.print("Time");// 页面指示tft.setTextSize(2);tft.setCursor(80 + offset, 220);tft.print("Page 2/");tft.print(TOTAL_PAGES);}void drawChartFrame(int offset) {// 绘制坐标轴tft.drawFastVLine(30 + offset, 50, 140, TEXT_COLOR); // Y轴tft.drawFastHLine(30 + offset, 190, 190, TEXT_COLOR); // X轴// Y轴标签tft.setTextColor(TEXT_COLOR);tft.setTextSize(1);tft.setCursor(5 + offset, 45);tft.print("100%");tft.setCursor(10 + offset, 90);tft.print("75%");tft.setCursor(10 + offset, 135);tft.print("50%");tft.setCursor(10 + offset, 180);tft.print("25%");// X轴箭头tft.drawLine(220 + offset, 190, 215 + offset, 185, TEXT_COLOR);tft.drawLine(220 + offset, 190, 215 + offset, 195, TEXT_COLOR);// Y轴箭头tft.drawLine(30 + offset, 50, 25 + offset, 55, TEXT_COLOR);tft.drawLine(30 + offset, 50, 35 + offset, 55, TEXT_COLOR);}void updatePage2() {// 更新当前值显示tft.fillRect(120, 205, 100, 12, BACKGROUND_COLOR);tft.setTextColor(ACCENT_COLOR);tft.setTextSize(1);tft.setCursor(120, 205);tft.print("Now: ");tft.print(sensorValue);tft.print("%");}void updatePage2Chart() {// 计算条形图参数(居中显示,增加间隔)int barCount = 12;int barWidth = 10;int barSpacing = 6;int totalWidth = barCount * barWidth + (barCount - 1) * barSpacing;int startX = 130 - totalWidth / 2; // 居中计算int chartBottom = 190;int chartHeight = 130;// 清除图表区域(只清除条形区域,保留坐标轴)tft.fillRect(startX, 60, totalWidth + 5, chartHeight, BACKGROUND_COLOR);// 绘制条形图for (int i = 0; i < barCount; i++) {int valueIndex = (historyIndex - barCount + i + barCount) % barCount;int barHeight = map(historyValues[valueIndex], 0, 100, 0, chartHeight);int x = startX + i * (barWidth + barSpacing);int y = chartBottom - barHeight;// 选择颜色 - 使用渐变效果uint16_t barColor;if (historyValues[valueIndex] > 70) {barColor = WARNING_COLOR;} else if (historyValues[valueIndex] > 40) {barColor = ST77XX_ORANGE;} else {barColor = BAR_COLOR;}// 绘制条形(带阴影效果)tft.fillRect(x, y, barWidth, barHeight, barColor);// 条形顶部边框tft.drawFastHLine(x, y, barWidth, TEXT_COLOR);// 数值标签(只显示较高的条形)if (barHeight > 20) {tft.setTextColor(TEXT_COLOR);tft.setTextSize(1);tft.setCursor(x + 1, y - 10);tft.print(historyValues[valueIndex]);}// 时间标签(底部)if (i % 3 == 0) {tft.setTextColor(GRID_COLOR);tft.setTextSize(1);tft.setCursor(x - 3, chartBottom + 5);tft.print("-");tft.print(barCount - i);}}}// 页面3: 详细信息void drawPage3(int offset) {tft.fillScreen(BACKGROUND_COLOR);// 标题 - 左上角tft.setTextColor(ACCENT_COLOR);tft.setTextSize(2);tft.setCursor(10 + offset, 10);tft.println("SENSOR INFO");// 分隔线tft.drawFastHLine(10 + offset, 35, 220, GRID_COLOR);// 绘制静态内容drawPage3Static(offset);}void drawPage3Static(int offset) {// 静态标签tft.setTextColor(TEXT_COLOR);tft.setTextSize(2);tft.setCursor(5 + offset, 60);tft.print("Rain Level");tft.setCursor(5 + offset, 90);tft.print("Raw Value: ");tft.setCursor(5 + offset, 120);tft.print("Alert: ");tft.setCursor(5 + offset, 150);tft.print("Threshold: ");// 映射说明tft.setTextColor(GRID_COLOR);tft.setTextSize(1);tft.setCursor(5 + offset, 180);tft.println("Dry=Low%, Wet=High%");// 页面指示tft.setTextColor(ACCENT_COLOR);tft.setTextSize(2);tft.setCursor(80 + offset, 220);tft.print("Page 3/");tft.print(TOTAL_PAGES);}void updatePage3() {// 更新雨量百分比tft.fillRect(130, 60, 80, 20, BACKGROUND_COLOR);tft.setTextColor(sensorValue > 70 ? WARNING_COLOR : NORMAL_COLOR);tft.setTextSize(2);tft.setCursor(130, 60);tft.print(sensorValue);tft.println("%");// 更新原始值tft.fillRect(130, 90, 100, 20, BACKGROUND_COLOR);tft.setTextColor(ACCENT_COLOR);tft.setCursor(130, 90);tft.print(rawValue);tft.println("/1023");// 更新报警状态tft.fillRect(130, 120, 100, 20, BACKGROUND_COLOR);if (alertState) {tft.setTextColor(WARNING_COLOR);tft.setCursor(130, 120);tft.println("ACTIVE");} else {tft.setTextColor(NORMAL_COLOR);tft.setCursor(130, 120);tft.println("INACTIVE");}// 更新阈值信息tft.fillRect(130, 150, 80, 20, BACKGROUND_COLOR);tft.setTextColor(TEXT_COLOR);tft.setCursor(130, 150);tft.println(">70%");}
数据展示思维导图
三、项目结果演示
3.1 操作流程
①系统启动显示启动界面
②自动进入主监测界面(页面1)
③每10秒自动切换至下一页面
历史雨量数据柱状统计图模拟界面设计,根据时间戳显示雨量大小
④页面1: 实时雨量百分比显示、页面2: 历史数据趋势图表、页面3: 详细传感器信息
⑤零知IDE串口打印输出
串口输出示例:
Rain Sensor with TFT Display Started
Raw: 245 - Rain Level: 76% - Alert: ON
3.2 界面展示
页面1: 大字体显示当前雨量百分比和状态指示
页面2: 条形柱状图展示历史数据趋势
页面3: 详细的传感器参数和报警状态
3.3 视频演示
雨滴传感器的多界面TFT降雨监测显示系统
系统从启动到运行的完整流程,包括三种界面的自动切换、模拟降雨检测、报警触发等关键功能
四、雨滴传感器工作原理
4.1 基本原理
雨滴传感器基于电阻变化原理工作。传感器表面有交错排列的导电线,当雨水落到表面时,水分的导电性会在导线之间形成电阻通路
模拟电压输出特性:模拟雨量增大之后,导通电阻降低,输出电压下降
4.2 工作过程
(1)干燥状态: 导线间电阻极大(兆欧级),输出高电压
(2)湿润状态: 水分形成导电通路,电阻显著降低,输出电压下降
(3)雨量检测: 通过ADC读取电压值,映射为雨量百分比,雨量阈值超过70%亮红灯报警
4.3 模数转换原理
零知增强板的STM32F407VET6主控芯片内置12位ADC,将0-3.3V模拟电压转换为0-4095数字值:
五、常见问题解答
Q1: 为什么雨量百分比显示不正确?
A: 检查雨滴传感器的接线:
确保模拟输入引脚正确。调整
map()
函数的参数以适应具体传感器特性
Q2: 如何调整报警阈值?
A: 修改代码:
调整
alertState = (sensorValue > 70);
的阈值数值,根据实际需求设置合适的报警点
Q3: 历史数据图表不更新怎么办?
A: 检查historyValues
数组的索引管理:
确保新数据正确覆盖旧数据。验证
updatePage2Chart()
函数的调用频率