零知IDE——STM32F407VET6驱动SHT40温湿度传感器与ST7789实现智能环境监测系统

✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

(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

相关推荐
贝塔实验室3 小时前
Altium Designer 6.3 PCB LAYOUT教程(四)
驱动开发·嵌入式硬件·硬件架构·硬件工程·信息与通信·基带工程·pcb工艺
星辰pid4 小时前
stm32的gpio模式到底该怎么选择?(及iic,spi,定时器原理介绍)
stm32·单片机·嵌入式硬件
brave and determined5 小时前
可编程逻辑器件学习(day3):FPGA设计方法、开发流程与基于FPGA的SOC设计详解
嵌入式硬件·fpga开发·soc·仿真·电路·时序·可编程逻辑器件
axuan126515 小时前
10.【NXP 号令者RT1052】开发——实战-RT 看门狗(RTWDOG)
单片机·嵌入式硬件·mcu
-大头.5 小时前
Rust高级类型与零成本抽象实战
stm32·单片机·rust
Porco.w9 小时前
STM32 DMA
stm32·单片机·嵌入式硬件
BreezeJuvenile9 小时前
外设模块学习(17)——5V继电器模块(STM32)
stm32·单片机·嵌入式硬件·学习·5v继电器模块
GilgameshJSS9 小时前
STM32H743-ARM例程40-U_DISK_IAP
c语言·arm开发·stm32·单片机·嵌入式硬件
hazy1k11 小时前
51单片机基础-GPIO结构详解
stm32·单片机·嵌入式硬件·51单片机