✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
(1)项目概述
本项目基于零知增强板和SHT40温湿度传感器,系统通过高精度的SHT40传感器实时采集环境温度和湿度数据,并在240×240分辨率的TFT显示屏上以四种不同的可视化界面进行展示。用户可以通过物理按键在不同显示模式间切换,系统还集成了VPD(蒸气压亏缺)计算功能,为农业智能家居等应用场景提供专业的环境参数监测。
(2)项目难点及解决方案
问题描述:I2C通信的稳定性直接影响传感器数据的准确性
**解决方案:**设置合理的I2C通信时钟频率,添加数据CRC校验机制确保数据完整性
一、硬件系统设计
1.1 硬件清单
| 器件 | 型号 | 数量 |
|---|---|---|
| 主控板 | 零知增强板(STM32F407VET6) | 1 |
| 温湿度传感器 | SHT40 | 1 |
| TFT显示屏 | ST7789 | 1 |
| 按键 | 轻触开关 | 1 |
| 连接线 | 杜邦线 | 若干 |
1.2 接线方案
根据代码中定义的引脚,接线如下:
| 零知增强板引脚 | 连接器件 | 功能 |
|---|---|---|
| 10 | TFT_CS | 显示屏片选 |
| 6 | TFT_MOSI | 显示屏数据输入 |
| 5 | TFT_SCK | 显示屏时钟 |
| 9 | TFT_RST | 显示屏复位 |
| 8 | TFT_DC | 显示屏数据/命令选择 |
| 3 | BUTTON_PIN | 按键输入(下拉) |
| 3.3V | SHT4X VCC | 传感器电源 |
| GND | SHT4X GND | 传感器地 |
| 20/SDA | SHT4X SDA | 传感器数据 |
| 21/SCL | SHT4X SCL | 传感器时钟 |
SHT40温湿度传感器可以采取线对板2.0引脚间距线束直插零知增强板扩展板
1.3 具体接线图

注意:零知SHT40温湿度传感器采用LDO模块 适配增强板板载电源输入**1.5V-5.5V宽电压,**满足芯片1.08V-3.6V供电电压
1.4 连接实物图

零知SHT40温湿度传感器详解
将LDO稳压器布置在距离传感器较远的位置,减少热量对温度测量的影响;在每个IC电源引脚附近布置去耦电容,提高系统稳定性

*1)电源管理设计,*采用SUL6018S5-ADJ LDO稳压器为系统提供稳定的1.8V电源:支持1.2V-5.5V输入范围,适应多种电源环境
*2)信号电平匹配,*使用TXS0102双向电平转换器:解决1.8V和3.3/5V设备间的通信兼容性问题、支持I2C总线的双向电平转换
二、代码架构讲解
代码使用零知DE框架编写,主要分为以下几个部分
2.1 全局对象与变量定义
cpp
#include <Adafruit_GFX.h> // 图形库核心
#include <Adafruit_ST7789.h> // ST7789显示屏驱动
#include <Adafruit_SHT4x.h> // SHT4X传感器驱动
#include <Fonts/FreeSansBold18pt7b.h> // 大号字体
#include <Fonts/FreeSansBold12pt7b.h> // 中号字体
#include <Fonts/FreeSans9pt7b.h> // 小号字体
// ST7789显示引脚定义
#define TFT_CS 10 // 片选信号
#define TFT_MOSI 6 // SPI数据输入
#define TFT_SCK 5 // SPI时钟
#define TFT_RST 9 // 复位信号
#define TFT_DC 8 // 数据/命令选择
// 按键引脚定义
#define BUTTON_PIN 3 // 界面切换按键
// 创建显示对象
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);
// 创建传感器对象
Adafruit_SHT4x sht4 = Adafruit_SHT4x();
// 全局变量
volatile int currentScreen = 0; // 当前显示界面索引
volatile bool screenChanged = false; // 界面切换标志
unsigned long lastButtonPress = 0; // 上次按键时间
const unsigned long BUTTON_COOLDOWN = 300; // 按键防抖时间(ms)
// 颜色定义
#define BACKGROUND_COLOR ST77XX_BLACK // 背景色
#define PRIMARY_COLOR ST77XX_GREEN // 主色调
#define TEMP_COLOR ST77XX_CYAN // 温度颜色
#define HUMIDITY_COLOR ST77XX_BLUE // 湿度颜色
#define VPD_COLOR ST77XX_YELLOW // VPD颜色
#define WARNING_COLOR ST77XX_RED // 警告颜色
TFT_CS:显示屏片选,低电平有效、BUTTON_PIN:配置为输入上拉模式,下降沿触发中断
2.2 温度显示界面显示
cpp
void showTemperatureScreen() {
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);
// 绘制标题
tft.setFont(&FreeSansBold12pt7b);
tft.setTextColor(TEMP_COLOR);
tft.setCursor(50, 30);
tft.print("Temperature");
// 绘制温度圆环 (0-50°C范围)
int centerX = 120, centerY = 110;
int outerRadius = 70, innerRadius = 50;
// 绘制背景圆环
drawRing(centerX, centerY, innerRadius, outerRadius, 0, 360, ST77XX_DARKGREY);
// 计算填充角度并绘制温度圆环
float tempPercent = constrain(temp.temperature, 0, 50) / 50.0;
int endAngle = 360 * tempPercent;
drawRing(centerX, centerY, innerRadius, outerRadius, 0, endAngle, TEMP_COLOR);
// 显示温度数值
tft.setFont(&FreeSansBold18pt7b);
tft.setTextColor(TEMP_COLOR);
char tempStr[8];
dtostrf(temp.temperature, 5, 1, tempStr); // 浮点数转字符串
// 清除中心区域并显示数值
tft.fillCircle(centerX, centerY, innerRadius - 5, BACKGROUND_COLOR);
tft.setCursor(centerX - 50, centerY + 10);
tft.print(tempStr);
tft.setFont(&FreeSans9pt7b);
tft.setCursor(centerX + 30, centerY + 15);
tft.print("C");
}
2.3 图形绘制辅助函数
cpp
// 绘制圆环函数
void drawRing(int x, int y, int innerRadius, int outerRadius,
int startAngle, int endAngle, uint16_t color) {
for (int r = innerRadius; r <= outerRadius; r++) {
drawArc(x, y, r, startAngle, endAngle, color);
}
}
// 绘制圆弧函数
void drawArc(int x, int y, int radius, int startAngle, int endAngle, uint16_t color) {
for (int i = startAngle; i <= endAngle; i++) {
float angle = i * PI / 180; // 角度转弧度
int xp = x + radius * cos(angle);
int yp = y + radius * sin(angle);
tft.drawPixel(xp, yp, color);
}
}
通过 tft.drawPixe(xp, yp, color),在计算出的每个 (xp, yp) 位置画一个像素点;循环遍历 startAngle 到 endAngle 的所有角度
2.4 项目完整代码实现
cpp
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <Adafruit_SHT4x.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
// ST7789显示引脚定义
#define TFT_CS 10
#define TFT_MOSI 6
#define TFT_SCK 5
#define TFT_RST 9
#define TFT_DC 8
// 按键引脚定义
#define BUTTON_PIN 3
// 创建显示对象
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);
// 创建传感器对象
Adafruit_SHT4x sht4 = Adafruit_SHT4x();
// 全局变量
volatile int currentScreen = 0;
volatile bool screenChanged = false;
unsigned long lastButtonPress = 0;
const unsigned long BUTTON_COOLDOWN = 300; // 按键冷却时间(ms)
// 颜色定义
#define BACKGROUND_COLOR ST77XX_BLACK
#define PRIMARY_COLOR ST77XX_GREEN
#define SECONDARY_COLOR ST77XX_WHITE
#define TEMP_COLOR ST77XX_CYAN
#define HUMIDITY_COLOR ST77XX_BLUE
#define VPD_COLOR ST77XX_YELLOW
#define WARNING_COLOR ST77XX_RED
#define ST77XX_DARKGREY 0xAD55
// 已知序列号对应的型号-测试用(每个设备的序列号不一致)
#define SHT41_SERIAL 0x1513FB18
#define SHT40_SERIAL 0xF6C5E9F
// 中断服务函数
void buttonISR() {
if (millis() - lastButtonPress > BUTTON_COOLDOWN) {
currentScreen = (currentScreen + 1) % 4;
screenChanged = true;
lastButtonPress = millis();
}
}
void setup() {
Serial.begin(115200);
// 初始化按键中断
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
// 初始化显示屏
tft.init(240, 240);
tft.setRotation(1);
tft.fillScreen(BACKGROUND_COLOR);
// 显示启动画面
showSplashScreen();
delay(2000);
// 初始化传感器
if (!sht4.begin()) {
showError("SHT4x not found!");
while (1) delay(1000);
}
Serial.println("Found SHT4x sensor");
Serial.print("Serial number 0x");
Serial.println(sht4.readSerial(), HEX);
// 设置传感器精度
sht4.setPrecision(SHT4X_HIGH_PRECISION);
sht4.setHeater(SHT4X_NO_HEATER);
// 显示初始化信息
showInitInfo();
delay(3000);
// 清屏准备主界面
tft.fillScreen(BACKGROUND_COLOR);
}
void loop() {
// 读取传感器数据
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);
// 串口打印数据(简单格式)
Serial.print("Temp:");
Serial.print(temp.temperature, 2);
Serial.print("℃ Humi:");
Serial.print(humidity.relative_humidity, 2);
Serial.println(" %RH");
// 检查是否需要切换界面
if (screenChanged) {
tft.fillScreen(BACKGROUND_COLOR);
screenChanged = false;
}
// 根据当前界面显示相应内容
switch (currentScreen) {
case 0:
showTemperatureScreen();
break;
case 1:
showHumidityScreen();
break;
case 2:
showVPDScreen();
break;
case 3:
showInfoScreen();
break;
}
delay(500);
}
void showSplashScreen() {
tft.fillScreen(BACKGROUND_COLOR);
// 标题
tft.setFont(&FreeSansBold18pt7b);
tft.setTextColor(PRIMARY_COLOR);
tft.setCursor(40, 80);
tft.print("SHT4x");
tft.setFont(&FreeSansBold12pt7b);
tft.setCursor(50, 120);
tft.print("Sensor");
tft.setCursor(60, 150);
tft.print("Monitor");
}
void showInitInfo() {
tft.fillScreen(BACKGROUND_COLOR);
tft.setFont(&FreeSansBold12pt7b);
tft.setTextColor(PRIMARY_COLOR);
tft.setCursor(20, 40);
tft.print("Initialization");
tft.drawLine(20, 50, 220, 50, PRIMARY_COLOR);
// 读取并显示序列号
uint32_t serial = sht4.readSerial();
String sensorModel = "Unknown";
if (serial == SHT41_SERIAL) {
sensorModel = "SHT41";
} else if (serial == SHT40_SERIAL) {
sensorModel = "SHT40";
}
tft.setFont(&FreeSans9pt7b);
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 80);
tft.print("Model: ");
tft.setTextColor(PRIMARY_COLOR);
tft.print(sensorModel);
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 105);
tft.print("Serial: 0x");
tft.setTextColor(PRIMARY_COLOR);
tft.print(serial, HEX);
// 显示精度设置
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 130);
tft.print("Precision: ");
tft.setTextColor(PRIMARY_COLOR);
switch (sht4.getPrecision()) {
case SHT4X_HIGH_PRECISION:
tft.print("High");
break;
case SHT4X_MED_PRECISION:
tft.print("Medium");
break;
case SHT4X_LOW_PRECISION:
tft.print("Low");
break;
}
// 显示加热器设置
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 155);
tft.print("Heater: ");
tft.setTextColor(PRIMARY_COLOR);
switch (sht4.getHeater()) {
case SHT4X_NO_HEATER:
tft.print("Off");
break;
case SHT4X_HIGH_HEATER_1S:
tft.print("High 1s");
break;
case SHT4X_HIGH_HEATER_100MS:
tft.print("High 0.1s");
break;
case SHT4X_MED_HEATER_1S:
tft.print("Medium 1s");
break;
case SHT4X_MED_HEATER_100MS:
tft.print("Medium 0.1s");
break;
case SHT4X_LOW_HEATER_1S:
tft.print("Low 1s");
break;
case SHT4X_LOW_HEATER_100MS:
tft.print("Low 0.1s");
break;
}
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 200);
tft.print("Press button to start");
}
void showTemperatureScreen() {
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);
// 绘制标题
tft.setFont(&FreeSansBold12pt7b);
tft.setTextColor(TEMP_COLOR);
tft.setCursor(50, 30);
tft.print("Temperature");
// 绘制温度圆环 (0-50°C范围)
int centerX = 120;
int centerY = 110;
int outerRadius = 70;
int innerRadius = 50;
int ringWidth = outerRadius - innerRadius;
// 绘制背景圆环
drawRing(centerX, centerY, innerRadius, outerRadius, 0, 360, ST77XX_DARKGREY);
// 绘制温度填充圆环
float tempPercent = constrain(temp.temperature, 0, 50) / 50.0;
int endAngle = 360 * tempPercent;
drawRing(centerX, centerY, innerRadius, outerRadius, 0, endAngle, TEMP_COLOR);
// 绘制中心数值
tft.setFont(&FreeSansBold18pt7b);
tft.setTextColor(TEMP_COLOR);
char tempStr[8];
dtostrf(temp.temperature, 5, 1, tempStr);
// 清除中心区域
tft.fillCircle(centerX, centerY, innerRadius - 5, BACKGROUND_COLOR);
// 显示温度值
tft.setCursor(centerX - 50, centerY + 10);
tft.print(tempStr);
tft.setFont(&FreeSans9pt7b);
tft.setCursor(centerX + 30, centerY + 15);
tft.print("C");
// 显示底部状态
tft.setFont(&FreeSans9pt7b);
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(60, 220);
tft.print("Screen: 1/4");
}
void showHumidityScreen() {
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);
// 绘制标题
tft.setFont(&FreeSansBold12pt7b);
tft.setTextColor(HUMIDITY_COLOR);
tft.setCursor(65, 30);
tft.print("Humidity");
// 绘制湿度圆环 (0-100%范围)
int centerX = 120;
int centerY = 110;
int outerRadius = 70;
int innerRadius = 50;
// 绘制背景圆环
drawRing(centerX, centerY, innerRadius, outerRadius, 0, 360, ST77XX_DARKGREY);
// 绘制湿度填充圆环
float humidityPercent = constrain(humidity.relative_humidity, 0, 100) / 100.0;
int endAngle = 360 * humidityPercent;
drawRing(centerX, centerY, innerRadius, outerRadius, 0, endAngle, HUMIDITY_COLOR);
// 绘制中心数值
tft.setFont(&FreeSansBold18pt7b);
tft.setTextColor(HUMIDITY_COLOR);
char humidityStr[8];
dtostrf(humidity.relative_humidity, 5, 1, humidityStr);
// 清除中心区域
tft.fillCircle(centerX, centerY, innerRadius - 5, BACKGROUND_COLOR);
// 显示湿度值
tft.setCursor(centerX - 50, centerY + 10);
tft.print(humidityStr);
tft.setFont(&FreeSans9pt7b);
tft.setCursor(centerX + 25, centerY + 10);
tft.print("%");
// 显示底部状态
tft.setFont(&FreeSans9pt7b);
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(60, 220);
tft.print("Screen: 2/4");
}
void showVPDScreen() {
sensors_event_t humidity, temp;
int innerRadius = 50;
sht4.getEvent(&humidity, &temp);
// 计算VPD
float svp = 0.6108 * exp(17.27 * temp.temperature / (temp.temperature + 237.3));
float avp = humidity.relative_humidity / 100 * svp;
float vpd = svp - avp;
// 绘制标题
tft.setFont(&FreeSansBold12pt7b);
tft.setTextColor(VPD_COLOR);
tft.setCursor(95, 30);
tft.print("VPD");
// 绘制VPD仪表
int centerX = 120;
int centerY = 110;
int radius = 60;
// 绘制仪表背景
tft.drawCircle(centerX, centerY, radius, ST77XX_DARKGREY);
tft.drawCircle(centerX, centerY, radius + 1, ST77XX_DARKGREY);
// 根据VPD值选择颜色 (正常范围: 0.8-1.2 kPa)
uint16_t vpdColor;
if (vpd >= 0.8 && vpd <= 1.2) {
vpdColor = VPD_COLOR;
} else {
vpdColor = WARNING_COLOR;
}
// 绘制VPD值弧线
float vpdMapped = constrain(vpd, 0, 2.0) / 2.0; // 映射到0-2kPa范围
int vpdAngle = 270 * vpdMapped; // 270度弧线
for (int i = 0; i <= vpdAngle; i += 5) {
float angle = i * PI / 180;
int x1 = centerX + (radius - 10) * cos(angle);
int y1 = centerY + (radius - 10) * sin(angle);
int x2 = centerX + radius * cos(angle);
int y2 = centerY + radius * sin(angle);
tft.drawLine(x1, y1, x2, y2, vpdColor);
}
// 清除中心区域
tft.fillCircle(centerX, centerY, innerRadius - 5, BACKGROUND_COLOR);
// 显示VPD数值
tft.setFont(&FreeSansBold18pt7b);
tft.setTextColor(vpdColor);
char vpdStr[8];
dtostrf(vpd, 4, 2, vpdStr);
tft.setCursor(centerX - 40, centerY + 10);
tft.print(vpdStr);
tft.setFont(&FreeSans9pt7b);
tft.setCursor(centerX + 5, centerY + 30);
tft.print("kPa");
// 清除状态显示
tft.fillRect(30, 180, 210, 20, BACKGROUND_COLOR);
// 显示VPD状态
tft.setFont(&FreeSans9pt7b);
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(30, 195);
if (vpd < 0.8) {
tft.print("Low VPD - Risk of mold");
} else if (vpd > 1.2) {
tft.print("High VPD - Plant stress");
} else {
tft.print("Optimal VPD range");
}
// 显示底部状态
tft.setCursor(60, 220);
tft.print("Screen: 3/4");
}
void showInfoScreen() {
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);
// 计算VPD
float svp = 0.6108 * exp(17.27 * temp.temperature / (temp.temperature + 237.3));
float avp = humidity.relative_humidity / 100 * svp;
float vpd = svp - avp;
// 读取序列号并确定型号
uint32_t serial = sht4.readSerial();
String sensorModel = "Unknown";
if (serial == SHT41_SERIAL) {
sensorModel = "SHT41";
} else if (serial == SHT40_SERIAL) {
sensorModel = "SHT40";
}
// 绘制标题
tft.setFont(&FreeSansBold12pt7b);
tft.setTextColor(PRIMARY_COLOR);
tft.setCursor(50, 30);
tft.print("Sensor Info");
tft.drawLine(20, 45, 220, 45, PRIMARY_COLOR);
tft.setFont(&FreeSans9pt7b);
// 显示传感器型号
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 70);
tft.print("Model:");
tft.setTextColor(PRIMARY_COLOR);
tft.setCursor(100, 70);
tft.print(sensorModel);
// 显示序列号
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 95);
tft.print("Serial:");
tft.setTextColor(PRIMARY_COLOR);
tft.setCursor(100, 95);
tft.print("0x");
tft.print(serial, HEX);
// 显示当前读数
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 120);
tft.print("Temperature:");
tft.fillRect(140, 100, 40, 30, BACKGROUND_COLOR); //局部刷新
tft.setTextColor(TEMP_COLOR);
tft.setCursor(140, 120);
tft.print(temp.temperature, 1);
tft.print(" C");
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 145);
tft.print("Humidity:");
tft.fillRect(140, 125, 40, 30, BACKGROUND_COLOR);
tft.setTextColor(HUMIDITY_COLOR);
tft.setCursor(140, 145);
tft.print(humidity.relative_humidity, 1);
tft.print(" %");
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 170);
tft.print("VPD:");
tft.fillRect(140, 150, 40, 30, BACKGROUND_COLOR);
tft.setTextColor(VPD_COLOR);
tft.setCursor(140, 170);
tft.print(vpd, 2);
tft.print(" kPa");
// 显示精度信息
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 195);
tft.print("Precision:");
tft.setTextColor(PRIMARY_COLOR);
switch (sht4.getPrecision()) {
case SHT4X_HIGH_PRECISION:
tft.setCursor(140, 195);
tft.print("High");
break;
case SHT4X_MED_PRECISION:
tft.setCursor(140, 195);
tft.print("Medium");
break;
case SHT4X_LOW_PRECISION:
tft.setCursor(140, 195);
tft.print("Low");
break;
}
// 显示底部状态
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(60, 220);
tft.print("Screen: 4/4");
}
void drawRing(int x, int y, int innerRadius, int outerRadius, int startAngle, int endAngle, uint16_t color) {
for (int r = innerRadius; r <= outerRadius; r++) {
drawArc(x, y, r, startAngle, endAngle, color);
}
}
void drawArc(int x, int y, int radius, int startAngle, int endAngle, uint16_t color) {
for (int i = startAngle; i <= endAngle; i++) {
float angle = i * PI / 180;
int xp = x + radius * cos(angle);
int yp = y + radius * sin(angle);
tft.drawPixel(xp, yp, color);
}
}
void showError(const char* message) {
tft.fillScreen(BACKGROUND_COLOR);
tft.setFont(&FreeSansBold12pt7b);
tft.setTextColor(WARNING_COLOR);
tft.setCursor(40, 100);
tft.print("ERROR");
tft.setFont(&FreeSans9pt7b);
tft.setTextColor(SECONDARY_COLOR);
tft.setCursor(20, 140);
tft.print(message);
}
2.5 关键算法解析
1)VPD计算算法:VPD(Vapor Pressure Deficit)是农业环境中重要的生理参数,计算公式基于Magnus公式:
cpp
// 饱和蒸气压计算 (Tetens公式)
float svp = 0.6108 * exp(17.27 * T / (T + 237.3));
// 实际蒸气压
float avp = RH / 100 * svp;
// 蒸气压亏缺
float vpd = svp - avp;
参数意义:T:温度(℃)、RH:相对湿度(%)
2)中断服务函数:volatile关键字确保多线程环境下变量的可见性;时间戳防抖算法有效消除机械按键抖动
cpp
void setup() {
Serial.begin(115200);
// 初始化按键中断
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
/*省略其他部分代码*/
}
void buttonISR() {
// 防抖处理:确保两次按键间隔大于冷却时间
if (millis() - lastButtonPress > BUTTON_COOLDOWN) {
currentScreen = (currentScreen + 1) % 4; // 循环切换0-3四个界面
screenChanged = true; // 设置界面更新标志
lastButtonPress = millis(); // 更新按键时间戳
}
}
attachInterrupt()处理按键的中断触发,设置冷却时间以防止按键抖动导致的多次触发,currentScreen变量确保界面索引切换
**中断函数语法:**attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);
pin: 中断引脚号、ISR: 中断服务程序名、mode:中断模式
中断模式(mode)有以下几种形式:LOW: 当引脚为低电平时触发中断服务程序、CHANGE: 当引脚电平发生变化时触发中断服务程序、RISING: 当引脚电平由低电平变为高电平时触发中断服务程序
FALLING: 当引脚电平由高电平变为低电平时触发中断服务程序,代码中采取这种方式进入中断
系统流程图:

三、项目结果演示
3.1 操作流程
1)硬件连接与系统启动:确认所有接线按照接线图正确连接、检查电源电压稳定在3.3V或5V。上电后观察启动画面显示,检查传感器初始化信息

2)功能验证测试:确认序列号读取正确(每个传感器序列号不同)、按键切换四个显示界面、观察温湿度数据更新

3)测试异常状态提示功能:错误界面展示"SHT40通信失败"

4)串口调试数据打印测试:通过零知IDE串口实时输出监测的温湿度数据
eg: Found SHT4x sensor
Serial number 0xF6C5E9F
Temp:27.43℃ Humi:73.33 %RH
Temp:27.45℃ Humi:74.06 %RH

3.2 界面展示说明
>界面1 温度显示,中央圆形进度条显示当前温度(0-50℃范围)、显示当前环境精确温度数据

>界面2 湿度显示,圆形进度条显示相对湿度(0-100%范围)、实时湿度百分比显示

>界面3 VPD显示,弧形仪表显示VPD值(0-2kPa范围)、状态编码:正常(黄色)| 异常(红色)、底部状态提示文字

3.3 视频演示
SHT40温湿度传感器环境监测系统
系统在实际环境中的温度变化时的实时响应、不同湿度条件下的显示效果,以及VPD异常状态下的警告提示
四、SHT4X温湿度传感器工作原理
SHT4X是Sensirion公司的新一代数字温湿度传感器,基于CMOSens®技术,将完整的传感器系统集成在单个芯片上
4.1 温湿度测量原理
SHT4X采用带隙温度传感器原理:

利用半导体PN结的正向电压与温度的线性关系、内置两个不同电流偏置的晶体管,测量其基极-发射极电压差、通过ΔVBE电压与绝对温度成正比的关系计算温度值
湿度测量基于电容式聚合物传感器:传感器由两个电极和吸湿性聚合物介质组成、聚合物吸收水分子后介电常数发生变化、导致电极间电容值与相对湿度成比例变化
4.2 I2C通信协议详解
SHT4X使用标准的I2C通信协议,设备地址为0x44(7位地址)
通信时序

a. 启动条件 → 设备地址(写) → 命令字节 → 停止条件
b. 启动条件 → 设备地址(读) → 数据字节1 → ACK → 数据字节2 → NACK → 停止条件
重要命令集

0xFD → 高精度温度湿度测量、0x89 → 读取32位唯一序列号、0x94 → 软件复位传感器
数据格式

16位温度和湿度原始值,通过公式转换为实际温湿度值
CRC检验和

4.3 寄存器配置详解
SHT4X通过单次命令进行操作,无需寄存器配置,但支持多种测量模式:
| 命令 | 精度模式 | 测量时间 | 温度精度 | 湿度精度 |
|---|---|---|---|---|
| 0xFD | 高精度 | 10ms | ±0.2℃ | ±2%RH |
| 0xF6 | 中精度 | 5ms | ±0.3℃ | ±3%RH |
| 0xE0 | 低精度 | 2ms | ±0.5℃ | ±4%RH |
五、常见问题解答(FAQ)
Q1:显示数据不更新或刷新缓慢?
A1: 可能原因和解决方案:
检查主循环延迟时间,适当调整刷新频率、确认传感器测量模式设置正确、检查SPI通信速率配置、优化图形绘制算法,减少重复绘制
Q2: VPD计算值不准确如何校准?
A2: 校准步骤:
使用标准温湿度计进行对比测量、验证传感器本身的测量精度、检查VPD计算公式参数是否正确
Q3: 按键响应不灵敏或误触发?
A3: 优化建议:
调整按键防抖时间常数BUTTON_COOLDOWN、检查硬件上拉电阻是否正常、优化中断服务函数执行效率
项目资源
SHT40 库文件: Adafruit_SHT4X Library
SHT40 数据手册: Datasheet_SHT4x