零知IDE——基于STM32F103RBT6和SHT40温湿度传感器的环境监测系统

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

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

www.lingzhilab.com

目录

一、硬件系统设计

[1.1 硬件清单](#1.1 硬件清单)

[1.2 接线方案表](#1.2 接线方案表)

[1.3 具体接线图](#1.3 具体接线图)

[1.4 连接实物图](#1.4 连接实物图)

二、代码架构讲解

[2.1 软件架构设计](#2.1 软件架构设计)

[2.2 软件I2C通信基础](#2.2 软件I2C通信基础)

[2.3 数据读取完整流程](#2.3 数据读取完整流程)

[2.4 VPD计算与环境评估](#2.4 VPD计算与环境评估)

[2.5 项目源工程代码](#2.5 项目源工程代码)

三、SHT40传感器技术原理

[3.1 温湿度测量原理](#3.1 温湿度测量原理)

[3.2 I2C通信协议详解](#3.2 I2C通信协议详解)

[3.3 寄存器配置详解](#3.3 寄存器配置详解)

四、项目结果演示

[4.1 硬件SPI性能测试](#4.1 硬件SPI性能测试)

[4.2 界面展示详情](#4.2 界面展示详情)

[4.3 视频操作演示](#4.3 视频操作演示)

五、常见问题解答(FAQ)

[Q1:传感器读数一直显示"Sensor Error"怎么办?](#Q1:传感器读数一直显示"Sensor Error"怎么办?)

Q2:VPD计算结果异常

Q3:如何进一步提升系统性能?


(1)项目概述

本项目基于零知标准板和SHT40高精度温湿度传感器,实现了一个功能完整的智能环境监测系统。系统通过软件I2C通信协议驱动SHT40传感器,实时采集环境温湿度数据,并在240×240像素的ST7789 TFT显示屏上以图形化界面展示。系统支持四界面循环切换显示,包括温度环形图、湿度环形图、VPD(蒸气压力差)仪表盘和传感器信息界面,为用户提供直观的环境数据监测体验

(2)项目难点及解决方案

问题描述:使用arduino-i2c-sht4x库时出现TwoWire类方法签名不匹配

**解决方案:**采用SoftWire软件I2C库完全规避硬件I2C依赖,通过软件模拟I2C时序确保通信稳定性

一、硬件系统设计

1.1 硬件清单

器件 规格型号 数量 备注
零知标准板 STM32F103RBT6主控 1 核心处理单元
零知标准板-扩展板 扩展板直插 1 IIC直插
SHT40传感器 温湿度传感器 1 高精度测量
ST7789显示屏 240×240 TFT 1 图形化显示
按键开关 轻触开关 1 界面切换
连接线束 4pin卧贴座子 1套 传感器连接

1.2 接线方案表

根据代码引脚定义,具体接线如下:

零知标准板引脚 连接器件 引脚功能 备注
引脚10 ST7789 TFT_CS 片选信号
引脚8 ST7789 TFT_DC 数据/命令选择
引脚9 ST7789 TFT_RST 复位信号
引脚11 ST7789 TFT_MOSI 硬件SPI数据线
引脚13 ST7789 TFT_SCK 硬件SPI时钟线
A5 SHT40 SCL 软件I2C时钟
A4 SHT40 SDA 软件I2C数据
引脚3 按键 信号输入 内部上拉
3.3V SHT40、ST7789 电源正极 统一供电
GND SHT40、ST7789、按键 电源地 共地连接

SHT40温湿度传感器采用线对板2.0引脚间距线束直插零知标准板-扩展板IIC连接器

1.3 具体接线图

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

1.4 连接实物图

零知SHT40温湿度传感器详解
优化电路布局,将LDO稳压器安装在远离传感器的位置,以降低热源对温度测量的干扰。采用4pin卧贴插座设计,实现快速接线操作。模块化结构设计使SHT40/SHT41传感器能够便捷更换

​*1)电源管理设计,*采用SUL6018S5-ADJ LDO稳压芯片为系统提供稳定的1.8V电源:支持1.5V-5.5V输入范围,适应多种电源环境

2)信号电平匹配,选用TXS0102双向电平转换器:解决1.8V和3.3/5V设备间的通信兼容性问题、支持I2C总线的双向电平转换

二、代码架构讲解

2.1 软件架构设计

代码使用零知DE框架编写,主要包含以下核心模块:

1)传感器驱动模块--SHT40通信协议实现;2)显示控制模块--TFT屏幕图形渲染;3)用户交互模块--按键中断处理;4)数据处理模块--温湿度数据计算和VPD计算

2.2 软件I2C通信基础

cpp 复制代码
// 软件I2C引脚定义
#define SCL_PIN A5
#define SDA_PIN A4

// SHT40传感器地址
#define SHT40_ADDRESS 0x44

// 创建软件I2C对象,使用引脚20(SCL)和19(SDA)
SoftWire sht40Wire(SCL_PIN, SDA_PIN, SOFT_STANDARD);

// I2C初始化配置
// SHT40初始化函数
bool sht40_init() {
  sht40Wire.begin();
  sht40Wire.setClock(100000);
  
  // 发送软复位命令
  sht40Wire.beginTransmission(SHT40_ADDRESS);
  sht40Wire.write(SHT40_SOFT_RESET);
  if (sht40Wire.endTransmission() != 0) {
    return false;
  }
  
  delay(10);
  
  // 读取序列号
  if (!sht40_read_serial()) {
    sensorModel = "SHT4x"; // 如果读取失败,使用默认型号
  }
  
  return true;
}

SOFT_STANDARD:标准速度模式,适合大多数应用场景;setClock(100000):I2C通信频率100kHz,平衡速度和稳定性

2.3 数据读取完整流程

cpp 复制代码
SHT40_Data sht40_read_data() {
  SHT40_Data data = {0, 0, false};
  uint8_t rx_bytes[6] = {0};
  
  // 发送高精度测量命令
  sht40Wire.beginTransmission(SHT40_ADDRESS);
  sht40Wire.write(SHT40_MEASURE_HIGH_PRECISION);
  if (sht40Wire.endTransmission() != 0) {
    data.success = false;
    return data;
  }
  
  delay(10); // 高精度模式测量时间约10ms
  
  // 读取6字节数据
  uint8_t bytes_read = sht40Wire.requestFrom(SHT40_ADDRESS, 6);
  if (bytes_read != 6) {
    data.success = false;
    return data;
  }
  
  for (int i = 0; i < 6; i++) {
    rx_bytes[i] = sht40Wire.read();
  }
  
  // CRC校验
  if ((sht40_crc8(rx_bytes, 2) == rx_bytes[2]) && 
      (sht40_crc8(&rx_bytes[3], 2) == rx_bytes[5])) {
    
    uint16_t t_ticks = (rx_bytes[0] << 8) | rx_bytes[1];
    uint16_t rh_ticks = (rx_bytes[3] << 8) | rx_bytes[4];
    
    data.temperature = -45.0 + 175.0 * (float)t_ticks / 65535.0;
    data.humidity = -6.0 + 125.0 * (float)rh_ticks / 65535.0;
    
    if (data.humidity > 100.0) data.humidity = 100.0;
    if (data.humidity < 0.0) data.humidity = 0.0;
    
    data.success = true;
  } else {
    data.success = false;
  }
  
  return data;
}

温度公式:T = -45 + 175 × (ST / 65535);湿度公式:RH = -6 + 125 × (SRH / 65535)

2.4 VPD计算与环境评估

cpp 复制代码
void showVPDScreen(SHT40_Data data) {
  // 计算饱和蒸气压(SVP)
  float svp = 0.6108 * exp(17.27 * data.temperature / (data.temperature + 237.3));
  // 计算实际蒸气压(AVP)
  float avp = data.humidity / 100 * svp;
  // 计算蒸气压亏缺(VPD)
  float vpd = svp - avp;
  
  // 环境状态评估
  if (vpd < 0.8) {
    // 低VPD:霉变风险
  } else if (vpd > 1.2) {
    // 高VPD:植物胁迫
  } else {
    // 适宜范围
  }
}

<0.8 kPa:湿度过高,真菌病害风险;0.8-1.2 kPa:植物生长最佳范围;>1.2 kPa:蒸腾过强,植物水分胁迫

2.5 项目源工程代码

cpp 复制代码
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SoftWire.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>

// ST7789显示引脚定义
#define TFT_CS   10
#define TFT_MOSI 11
#define TFT_SCK  13
#define TFT_RST  9
#define TFT_DC   8

// 按键引脚定义
#define BUTTON_PIN 3

// 软件I2C引脚定义
#define SCL_PIN A5
#define SDA_PIN A4

// SHT40传感器地址
#define SHT40_ADDRESS 0x44

// 创建SPI显示
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

// 创建软件I2C对象
SoftWire sht40Wire(SCL_PIN, SDA_PIN, SOFT_STANDARD);

// 全局变量
volatile int currentScreen = 0;
volatile bool screenChanged = false;
unsigned long lastButtonPress = 0;
unsigned long lastSensorRead = 0;
unsigned long lastDisplayUpdate = 0;
const unsigned long BUTTON_COOLDOWN = 300;
const unsigned long SENSOR_READ_INTERVAL = 500;    // 传感器读取间隔(ms)
const unsigned long DISPLAY_UPDATE_INTERVAL = 200; // 显示更新间隔(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

// 温湿度数据结构
struct SHT40_Data {
  float temperature;
  float humidity;
  bool success;
};

// SHT40命令定义
#define SHT40_MEASURE_HIGH_PRECISION 0xFD
#define SHT40_SOFT_RESET 0x94
#define SHT40_READ_SERIAL 0x89  // 读取序列号命令

// 已知序列号对应的型号
#define SHT41_SERIAL 0x1513FB18
#define SHT40_SERIAL 0xF6C5E9F

// 缓存上一次成功读取的数据
SHT40_Data cachedData = {0, 0, false};
String sensorModel = "SHT4x"; // 默认型号
uint32_t sensorSerial = 0;    // 传感器序列号

// CRC8校验函数
uint8_t sht40_crc8(const uint8_t *data, uint8_t len) {
  uint8_t crc = 0xFF;
  for (uint8_t i = 0; i < len; i++) {
    crc ^= data[i];
    for (uint8_t bit = 0; bit < 8; bit++) {
      if (crc & 0x80) {
        crc = (crc << 1) ^ 0x31;
      } else {
        crc = (crc << 1);
      }
    }
  }
  return crc;
}

// 读取传感器序列号
bool sht40_read_serial() {
  uint8_t rx_bytes[6] = {0};
  
  // 发送读取序列号命令
  sht40Wire.beginTransmission(SHT40_ADDRESS);
  sht40Wire.write(SHT40_READ_SERIAL);
  if (sht40Wire.endTransmission() != 0) {
    return false;
  }
  
  delay(10);
  
  // 读取6字节数据
  uint8_t bytes_read = sht40Wire.requestFrom(SHT40_ADDRESS, 6);
  if (bytes_read != 6) {
    return false;
  }
  
  for (int i = 0; i < 6; i++) {
    rx_bytes[i] = sht40Wire.read();
  }
  
  // CRC校验
  if ((sht40_crc8(rx_bytes, 2) == rx_bytes[2]) && 
      (sht40_crc8(&rx_bytes[3], 2) == rx_bytes[5])) {
    
    // 提取序列号
    sensorSerial = (rx_bytes[0] << 24) | (rx_bytes[1] << 16) | 
                   (rx_bytes[3] << 8) | rx_bytes[4];
    
    // 根据序列号判断型号
    if (sensorSerial == SHT41_SERIAL) {
      sensorModel = "SHT41";
    } else if (sensorSerial == SHT40_SERIAL) {
      sensorModel = "SHT40";
    } else {
      sensorModel = "SHT4x"; // 未知序列号,统一设置为SHT4x
    }
    
    return true;
  }
  
  return false;
}

// SHT40初始化函数
bool sht40_init() {
  sht40Wire.begin();
  sht40Wire.setClock(100000);
  
  // 发送软复位命令
  sht40Wire.beginTransmission(SHT40_ADDRESS);
  sht40Wire.write(SHT40_SOFT_RESET);
  if (sht40Wire.endTransmission() != 0) {
    return false;
  }
  
  delay(10);
  
  // 读取序列号
  if (!sht40_read_serial()) {
    sensorModel = "SHT4x"; // 如果读取失败,使用默认型号
  }
  
  return true;
}

// 读取SHT40数据
SHT40_Data sht40_read_data() {
  SHT40_Data data = {0, 0, false};
  uint8_t rx_bytes[6] = {0};
  
  // 发送高精度测量命令
  sht40Wire.beginTransmission(SHT40_ADDRESS);
  sht40Wire.write(SHT40_MEASURE_HIGH_PRECISION);
  if (sht40Wire.endTransmission() != 0) {
    data.success = false;
    return data;
  }
  
  delay(10); // 高精度模式测量时间约10ms
  
  // 读取6字节数据
  uint8_t bytes_read = sht40Wire.requestFrom(SHT40_ADDRESS, 6);
  if (bytes_read != 6) {
    data.success = false;
    return data;
  }
  
  for (int i = 0; i < 6; i++) {
    rx_bytes[i] = sht40Wire.read();
  }
  
  // CRC校验
  if ((sht40_crc8(rx_bytes, 2) == rx_bytes[2]) && 
      (sht40_crc8(&rx_bytes[3], 2) == rx_bytes[5])) {
    
    uint16_t t_ticks = (rx_bytes[0] << 8) | rx_bytes[1];
    uint16_t rh_ticks = (rx_bytes[3] << 8) | rx_bytes[4];
    
    data.temperature = -45.0 + 175.0 * (float)t_ticks / 65535.0;
    data.humidity = -6.0 + 125.0 * (float)rh_ticks / 65535.0;
    
    if (data.humidity > 100.0) data.humidity = 100.0;
    if (data.humidity < 0.0) data.humidity = 0.0;
    
    data.success = true;
  } else {
    data.success = false;
  }
  
  return data;
}

// 中断服务函数
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 (!sht40_init()) {
    showError("SHT4x not found!");
    while (1) delay(1000);
  }
  Serial.println("Found SHT4x sensor");
  Serial.print("Model: ");
  Serial.println(sensorModel);
  Serial.print("Serial: 0x");
  Serial.println(sensorSerial, HEX);
  
  // 显示初始化信息
  showInitInfo();
  delay(3000);
  
  // 清屏准备主界面
  tft.fillScreen(BACKGROUND_COLOR);
}

void loop() {
  unsigned long currentTime = millis();
  
  // 定时读取传感器数据
  if (currentTime - lastSensorRead >= SENSOR_READ_INTERVAL) {
    SHT40_Data sensor_data = sht40_read_data();
    
    if (sensor_data.success) {
      cachedData = sensor_data; // 缓存成功读取的数据
      
      // 串口打印数据
      static unsigned long lastSerialPrint = 0;
      if (currentTime - lastSerialPrint >= 1000) {
        Serial.print("Temp:");
        Serial.print(sensor_data.temperature, 2);
        Serial.print("℃ Humi:");
        Serial.print(sensor_data.humidity, 2);
        Serial.println(" %RH");
        lastSerialPrint = currentTime;
      }
    } else {
      Serial.println("Read SHT4x failed!");
    }
    lastSensorRead = currentTime;
  }
  
  // 检查是否需要切换界面
  if (screenChanged) {
    tft.fillScreen(BACKGROUND_COLOR);
    screenChanged = false;
    lastDisplayUpdate = 0; // 强制立即更新显示
  }
  
  // 定时更新显示
  if (currentTime - lastDisplayUpdate >= DISPLAY_UPDATE_INTERVAL) {
    // 根据当前界面显示相应内容
    switch (currentScreen) {
      case 0:
        showTemperatureScreen(cachedData);
        break;
      case 1:
        showHumidityScreen(cachedData);
        break;
      case 2:
        showVPDScreen(cachedData);
        break;
      case 3:
        showInfoScreen(cachedData);
        break;
    }
    lastDisplayUpdate = currentTime;
  }
}

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);
  
  tft.setFont(&FreeSans9pt7b);
  tft.setTextColor(SECONDARY_COLOR);
  
  // 显示传感器型号
  tft.setCursor(20, 80);
  tft.print("Model: ");
  tft.setTextColor(PRIMARY_COLOR);
  tft.print(sensorModel);
  
  // 显示软件I2C地址
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 105);
  tft.print("Address: 0x");
  tft.setTextColor(PRIMARY_COLOR);
  tft.print(SHT40_ADDRESS, HEX);
  
  // 显示引脚信息
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 130);
  tft.print("SCL Pin: ");
  tft.setTextColor(PRIMARY_COLOR);
  tft.print(SCL_PIN);
  
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 155);
  tft.print("SDA Pin: ");
  tft.setTextColor(PRIMARY_COLOR);
  tft.print(SDA_PIN);
  
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 180);
  tft.print("Mode: High Precision");
  
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 200);
  tft.print("Press button to start");
}

void showTemperatureScreen(SHT40_Data data) {
  if (!data.success) {
    showError("Sensor Error!");
    return;
  }
  
  // 绘制标题
  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;
  
  // 绘制背景圆环
  drawRing(centerX, centerY, innerRadius, outerRadius, 0, 360, ST77XX_DARKGREY);
  
  // 绘制温度填充圆环
  float tempPercent = constrain(data.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(data.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(SHT40_Data data) {
  if (!data.success) {
    showError("Sensor Error!");
    return;
  }
  
  // 绘制标题
  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(data.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(data.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(SHT40_Data data) {
  if (!data.success) {
    showError("Sensor Error!");
    return;
  }
  
  // 计算VPD
  float svp = 0.6108 * exp(17.27 * data.temperature / (data.temperature + 237.3));
  float avp = data.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;
  int innerRadius = 50;
  
  // 绘制仪表背景
  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(SHT40_Data data) {
  float vpd = 0;
  if (data.success) {
    float svp = 0.6108 * exp(17.27 * data.temperature / (data.temperature + 237.3));
    float avp = data.humidity / 100 * svp;
    vpd = svp - avp;
  }
  
  // 绘制标题
  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);
  
  // 显示软件I2C地址
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 95);
  tft.print("Address: ");
  tft.setTextColor(PRIMARY_COLOR);
  tft.setCursor(100, 95);
  tft.print("0x");
  tft.print(SHT40_ADDRESS, HEX);
  
  // 显示引脚信息
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 120);
  tft.print("SCL Pin: ");
  tft.setTextColor(PRIMARY_COLOR);
  tft.setCursor(100, 120);
  tft.print(SCL_PIN);
  
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 145);
  tft.print("SDA Pin: ");
  tft.setTextColor(PRIMARY_COLOR);
  tft.setCursor(100, 145);
  tft.print(SDA_PIN);
  
  // 显示当前读数
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 170);
  tft.print("Temperature:");
  tft.fillRect(140, 150, 80, 20, BACKGROUND_COLOR);
  if (data.success) {
    tft.setTextColor(TEMP_COLOR);
    tft.setCursor(140, 170);
    tft.print(data.temperature, 1);
    tft.print(" C");
  } else {
    tft.setTextColor(WARNING_COLOR);
    tft.setCursor(140, 170);
    tft.print("Error");
  }
  
  tft.setTextColor(SECONDARY_COLOR);
  tft.setCursor(20, 195);
  tft.print("Humidity:");
  tft.fillRect(140, 175, 80, 20, BACKGROUND_COLOR);
  if (data.success) {
    tft.setTextColor(HUMIDITY_COLOR);
    tft.setCursor(140, 195);
    tft.print(data.humidity, 1);
    tft.print(" %");
  } else {
    tft.setTextColor(WARNING_COLOR);
    tft.setCursor(140, 195);
    tft.print("Error");
  }
  
  // 显示底部状态
  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);
}

软件I2C库技术

*1)SoftWire库核心特性:*纯软件实现,不依赖硬件I2C外设;支持标准模式(100kHz)和快速模式(400kHz);开漏输出模拟

2)通信时序关键点:

cpp 复制代码
// 起始条件:SCL高电平时SDA下降沿
void i2c_start() {
    set_sda(LOW);
    set_scl(LOW);
}

// 停止条件:SCL高电平时SDA上升沿  
void i2c_stop() {
    set_sda(LOW);
    set_scl(HIGH);
    set_sda(HIGH);
}

软件I2C起始和停止调节

CRC8校验算法解析

CRC校验原理:

0x31,对应 x⁸ + x⁵ + x⁴ + 1;

0xFF,确保全零数据也有非零校验和

cpp 复制代码
uint8_t sht40_crc8(const uint8_t *data, uint8_t len) {
  uint8_t crc = 0xFF;  // 初始值
  for (uint8_t i = 0; i < len; i++) {
    crc ^= data[i];    // 异或操作
    for (uint8_t bit = 0; bit < 8; bit++) {
      if (crc & 0x80) {
        crc = (crc << 1) ^ 0x31;  // 多项式 0x31 (x^8 + x^5 + x^4 + 1)
      } else {
        crc = (crc << 1);
      }
    }
  }
  return crc;
}
名称 生成多项式 简记式
CRC-4 x4+x+1 0x03
CRC-8 x8+x5+x4+1 0x31
CRC-8 x8+x2+x1+1 0x07
CRC-16 x16+x15+x2+1 0x8005
CRC-32 x32+x26+x23+...+x2+x+1 0x04C11DB7

选定生成多项式→准备数据→模二除法→附加校验码

三、SHT40传感器技术原理

SHT40采用Sensirion先进的CMOSens®技术,将温湿度传感元件、信号放大、模数转换、数字处理和I2C接口集成在1.5×1.5mm的DFN封装内

3.1 温湿度测量原理

SHT4X采用带隙温度传感器原理:VBE = VG0 - (kT/q) × ln(ATγ/IC)

VG0:外推带隙电压、k:玻尔兹曼常数、T:绝对温度、q:电子电荷

利用半导体PN结的正向电压与温度的线性关系、内置两个不同电流偏置的晶体管,测量其基极-发射极电压差、通过ΔVBE电压与绝对温度成正比的关系计算温度值

湿度测量基于电容式聚合物传感器:C = ε₀εr(A/d)

εr:聚合物相对介电常数(湿度函数)、A:电极面积、d:聚合物厚度

3.2 I2C通信协议详解

SHT4X使用标准的I2C通信协议,设备地址为0x44(7位地址)

通信时序

a. 启动条件 → 设备地址(写) → 命令字节 → 停止条件

b. 启动条件 → 设备地址(读) → 数据字节1 → ACK → 数据字节2 → NACK → 停止条件

重要命令集

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

数据格式

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

CRC检验和

3.3 寄存器配置详解

SHT4X通过单次命令进行操作,无需寄存器配置,但支持多种测量模式:

命令 精度模式 测量时间 温度精度 湿度精度
0xFD 高精度 10ms ±0.2℃ ±2%RH
0xF6 中精度 5ms ±0.3℃ ±3%RH
0xE0 低精度 2ms ±0.5℃ ±4%RH

四、项目结果演示

4.1 硬件SPI性能测试

使用逻辑分析仪捕捉SPI时序,SPI时钟速率高,温度测量响应时间<1秒

4.2 界面展示详情

>界面1 温度显示,环形温度计平滑动画显示、实时温度数值居中显示,刷新无闪烁

>界面2 湿度显示,环形湿度计动态渐变效果、湿度百分比精确显示,刷新流畅

>界面3 VPD显示,弧形刻度仪表平滑旋转动画、颜色编码状态指示,切换无卡顿

>界面4 传感器信息,设备型号和地址显示、实时数据监控,刷新迅速、系统状态汇总

4.3 视频操作演示

零知标准版驱动SHT40温湿度传感器

系统在实际环境中的温度变化时的实时响应、不同湿度条件下的显示效果,以及VPD状态改变

五、常见问题解答(FAQ)

Q1:传感器读数一直显示"Sensor Error"怎么办?

A:按以下步骤排查:

检查电源电压是否为3.3V;确认SCL(A5)、SDA(A4)引脚连接正确;使用逻辑分析仪检查I2C通信波形;尝试降低I2C通信频率

Q2:VPD计算结果异常

A: 排查步骤:

验证温湿度传感器数据准确性、检查计算公式参数是否正确、确认温湿度单位换算无误、验证环境条件在传感器量程内

Q3:如何进一步提升系统性能?

A:高级优化策略:

SPI双缓冲:实现显示数据的双缓冲机制、传感器轮询优化:采用事件驱动代替定时轮询、内存优化:使用PROGMEM存储字体数据

项目资源

软件I2C 驱动库:SoftWire_Library

SHT40 库文件: Adafruit_SHT4x_Library

SHT40 数据手册: Datasheet_SHT4x

相关推荐
0南城逆流02 小时前
【STM32】知识点介绍四:时钟体系
stm32·单片机·嵌入式硬件
清风与日月3 小时前
c# 上位机作为控制端与下位机通信方式
单片机·嵌入式硬件·c#
奋斗的牛马4 小时前
OFDM理解
网络·数据库·单片机·嵌入式硬件·fpga开发·信息与通信
蓁蓁啊5 小时前
Ubuntu 虚拟机文件传输到 Windows的一种好玩的办法
linux·运维·windows·单片机·ubuntu
EVERSPIN5 小时前
32位MCU芯片国产品牌(32系列单片机常用型号有哪些)
单片机·嵌入式硬件·mcu单片机·32系列单片机
爱吃汽的小橘6 小时前
使用DSI TX IP驱动LCD显示屏
单片机·嵌入式硬件
芯联智造6 小时前
【stm32协议外设篇】- PAJ7620手势识别传感器
c语言·stm32·单片机·嵌入式硬件
从零点6 小时前
STM32F407运动资源分配
stm32·单片机·嵌入式硬件
d111111111d6 小时前
STM32外设学习-串口发送数据-接收数据(笔记)
笔记·stm32·学习