✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
目录
[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 项目源工程代码)
[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 视频操作演示)
[Q1:传感器读数一直显示"Sensor Error"怎么办?](#Q1:传感器读数一直显示"Sensor Error"怎么办?)
(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