本章节介绍 Pico-G1 扩展板上的 SHT20 温湿度传感器应用示例 —— sht20_display。该应用演示了如何使用 I2C 接口读取 SHT20 传感器的温度和湿度数据,并在 TFT 屏幕上实时显示环境信息。这是学习 I2C 环境监测传感器的基础示例。
应用源码位于 SDK 目录 source/app/08_sht20_display/,提供了完整的 I2C 温湿度传感器读取实现。
| 参数 | 值 |
|---|---|
I2C 接口 |
I2C3(/dev/i2c-3) |
从地址 |
0x40 |
温度范围 |
-40~125°C |
湿度范围 |
0~100% RH |
温度精度 |
±0.3°C |
湿度精度 |
±2% RH |
刷新频率 |
2秒(可配置) |
| index | 名称 | 测试指令 | 预期现象(成功) | 失败可能原因 |
|---|---|---|---|---|
| 1 | 基础读数 | ./sht20_display |
TFT显示温湿度值,控制台输出数据 | I2C连接失败、地址错误 |
| 2 | 温度变化 | 手温传感器 | 温度值上升2~5°C | 传感器响应慢、接触不良 |
| 3 | 湿度变化 | 向传感器哈气 | 湿度值上升10~20% | 传感器污染、湿度测量延迟 |
source/app/08_sht20_display/
├── Makefile # 构建脚本
├── main.c # 主程序
├── sht20.c # SHT20 驱动实现
├── sht20.h # SHT20 驱动头文件
├── i2c_hal.c # I2C HAL 层实现
├── i2c_hal.h # I2C HAL 层头文件
├── spi_hal.c # SPI HAL 层实现
├── spi_hal.h # SPI HAL 层头文件
├── st7789.c # ST7789 驱动实现
├── st7789.h # ST7789 驱动头文件
├── font8x16.h # 8×16 ASCII 点阵字库
└── README.md # 说明文档
| 信号 | 板上 GPIO | 控制器 / 节点 | 说明 |
|---|---|---|---|
| SCL | GPIO4_1 | I2C3 → /dev/i2c-3 |
与 OLED/MPU6050 共享 I2C3 总线 |
| SDA | GPIO4_2 | I2C3 → /dev/i2c-3 |
共享总线 |
| VCC | 3.3V | — | SHT20 供电 2.1~3.6V |
| GND | GND | — | 地线 |
SHT20 接线图:
Pico-G1 SHT20 Module
┌───────────┐ ┌──────────────┐
│ │ │ │
│ GPIO4_1 ──┼────── SCL ───┤ SCL │
│ │ │ │
│ GPIO4_2 ──┼────── SDA ───┤ SDA │
│ │ │ │
│ 3.3V ───┼─────────────┤ VCC │
│ │ │ │
│ GND ───┼─────────────┤ GND │
└───────────┘ └──────────────┘
芯片兼容性
SHT20、SI7021、HTU21 三种芯片 I2C 地址和命令兼容,可以互换使用。
export PATH=$PATH:<SDK>/tools/linux/toolchains/arm-gcc12.2.0-linux-uclibceabi/bin
cd <SDK>/source/app/08_sht20_display
make
scp sht20_display root@<板端IP>:/usr/bin/
ssh root@<板端IP> '/usr/bin/sht20_display'
/mnt # ./sht20_display
[sht20] pad 复用:I2C3(4_1/4_2) -> func2
[sht20] pad 0x100C0010 -> 0x00001002
[sht20] pad 0x100C0014 -> 0x00001002
[sht20] 初始化 SPI 屏(/dev/spidev2.0)...
[spi] pad 0x100C0028 -> 0x00001004
[spi] pad 0x100C002C -> 0x00001004
[spi] pad 0x100C0030 -> 0x00001000
[spi] pad 0x100C0020 -> 0x00001005
[spi] pad 0x100C001C -> 0x00001005
[spi] opening /dev/spidev2.0 ...
[spi] spidev opened, fd=3
[spi] spidev mode/bits/speed set (MODE3/8b/24MHz)
[spi] chardev request DC @ /dev/gpiochip4 line 5 ...
[spi] chardev request RES @ /dev/gpiochip4 line 4 ...
[spi] chardev request CS @ /dev/gpiochip5 line 1 ...
[spi] chardev-verify: DC=0(expect0) RES=1(expect1) CS=1(expect1) ==> OK(chardev 真驱动了引脚)
[spi] spi_hal_init done
[tft] init: SLPOUT
[tft] init: SLPOUT +120ms ok
[tft] init: config cmds ok
[tft] init: DISPON
[tft] init: DISPON ok
[tft] init: clear-flush start
[tft] flush #1 start
[tft] flush #1 done
[tft] init: clear-flush done
[sht20] 初始化 SHT20(/dev/i2c-3 addr 0x40)...
[sht20] @ /dev/i2c-3 addr 0x40 就绪 (user_reg=0x3A)
[sht20] 采集中,每 2s 刷新,Ctrl+C 退出。
[sht20] T=23.7 C RH=60.5 %

SHT20 使用标准 I2C 协议进行通信:
| 命令名称 | 命令代码 | 说明 |
|---|---|---|
| 温度保持 | 0xE3 | Hold Master 模式 |
| 温度非保持 | 0xF3 | No Hold Master 模式 |
| 湿度保持 | 0xE5 | Hold Master 模式 |
| 湿度非保持 | 0xF5 | No Hold Master 模式 |
温度计算:
float sht20_convert_temperature(uint16_t raw_temp)
{
return -46.85f + 175.72f * (raw_temp / 65536.0f);
}
湿度计算:
float sht20_convert_humidity(uint16_t raw_humi)
{
return -6.0f + 125.0f * (raw_humi / 65536.0f);
}
打开 I2C 设备:
int i2c_open(const char *i2c_bus, uint8_t device_addr)
{
int fd = open(i2c_bus, O_RDWR);
if (fd < 0) return -1;
if (ioctl(fd, I2C_SLAVE, device_addr) < 0) {
close(fd);
return -1;
}
return fd;
}
读取温度数据:
float sht20_read_temperature(int i2c_fd)
{
uint8_t cmd = 0xE3; // 温度保持命令
uint8_t data[3];
i2c_write_byte(i2c_fd, SHT20_ADDR, &cmd, 1);
usleep(85000); // 等待测量完成
i2c_read_bytes(i2c_fd, SHT20_ADDR, data, 3);
uint16_t raw_temp = (data[0] << 8) | data[1];
return sht20_convert_temperature(raw_temp);
}
读取湿度数据:
float sht20_read_humidity(int i2c_fd)
{
uint8_t cmd = 0xE5; // 湿度保持命令
uint8_t data[3];
i2c_write_byte(i2c_fd, SHT20_ADDR, &cmd, 1);
usleep(30000); // 等待测量完成
i2c_read_bytes(i2c_fd, SHT20_ADDR, data, 3);
uint16_t raw_humi = (data[0] << 8) | data[1];
return sht20_convert_humidity(raw_humi);
}
CRC8 校验算法:
bool sht20_check_crc(const uint8_t *data, uint8_t len, uint8_t checksum)
{
uint8_t crc = 0xFF;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t bit = 8; bit > 0; bit--) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = crc << 1;
}
}
}
return (crc == checksum);
}
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 读数失败 | I2C 连接失败、地址错误 | 检查接线、确认地址 0x40、使用 i2cdetect -y 3 检查设备可见性 |
| 温度异常 | 传感器损坏、接触不良 | 重新连接传感器、更换传感器 |
| 湿度固定 | 校准问题、传感器污染 | 重新校准传感器、清洁传感器 |
| CRC 错误 | I2C 通信干扰、电气噪声 | 检查 I2C 上拉电阻、增加滤波电容 |
| 数值跳动 | 环境变化快、气流不稳定 | 稳定测量环境、使用数据滤波 |
测量建议
为获得最佳测量效果:
- 预热时间:传感器上电后预热 1~2 分钟再开始测量
- 测量位置:避免阳光直射、空调出风口、热源附近
- 定期校准:每6个月校准一次,使用标准温湿度源
滑动平均滤波:
#define FILTER_WINDOW 5
void sht20_filter_add(float temp, float humi)
{
static float temp_buf[FILTER_WINDOW] = {0};
static float humi_buf[FILTER_WINDOW] = {0};
static int index = 0;
temp_buf[index] = temp;
humi_buf[index] = humi;
index = (index + 1) % FILTER_WINDOW;
// 计算平均值
float temp_avg = 0, humi_avg = 0;
for (int i = 0; i < FILTER_WINDOW; i++) {
temp_avg += temp_buf[i];
humi_avg += humi_buf[i];
}
temp_avg /= FILTER_WINDOW;
humi_avg /= FILTER_WINDOW;
printf("[SHT20] 温度: %.1f°C 湿度: %.1f%% (滤波后)\n", temp_avg, humi_avg);
}
报警检测逻辑:
typedef struct {
float temp_warning_high; // 温度警告上限
float temp_warning_low; // 温度警告下限
float humi_warning_high; // 湿度警告上限
float humi_warning_low; // 湿度警告下限
} sht20_alarm_threshold_t;
sht20_alarm_threshold_t g_alarm_threshold = {
.temp_warning_high = 35.0f,
.temp_warning_low = 15.0f,
.humi_warning_high = 70.0f,
.humi_warning_low = 30.0f,
};
void sht20_check_alarm(float temp, float humi)
{
if (temp > g_alarm_threshold.temp_warning_high) {
printf("[SHT20] 温度过高警告: %.2f°C\n", temp);
}
if (temp < g_alarm_threshold.temp_warning_low) {
printf("[SHT20] 温度过低警告: %.2f°C\n", temp);
}
if (humi > g_alarm_threshold.humi_warning_high) {
printf("[SHT20] 湿度过高警告: %.2f%%\n", humi);
}
if (humi < g_alarm_threshold.humi_warning_low) {
printf("[SHT20] 湿度过低警告: %.2f%%\n", humi);
}
}