零知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

相关推荐
蓝斯4973 小时前
从.到实现一个Windows上的虚拟hid键盘设备
windows·单片机·计算机外设
熙芯XiChip4 小时前
硬实时与软实时RTOS核心区别
嵌入式硬件
伟大的前程4 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
小渔村的拉线工5 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
熙芯XiChip5 小时前
CAN与CANopen的本质区别
单片机
lilihewo6 小时前
Altium Designer 封装库的创建(分享pcb 3D封装库)
嵌入式硬件·51单片机
神罚天下ET7 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
碧海银沙音频科技研究院8 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
Zevalin爱灰灰8 小时前
模拟信号链电路——基于TL431的电压基准电路
单片机·嵌入式硬件
csdn杰哥9 小时前
瑞萨单片机开发教程[二十七] DTC 外部中断触发传输实验
单片机·嵌入式硬件·mongodb·瑞萨·ra6m5