采用设备

主控:Raspberry Pi Pico 2 (RP2350)

传感器:SGP30(I2C 地址 0x58),检测 eCO₂ 和 TVOC

显示屏:SSD1306 128×64 I2C OLED(地址 0x3C)

接线方式:

Pico 2 引脚 连接设备 说明
GPIO4 SGP30 SDA 软件 I2C
GPIO4 SGP30 SCL 软件 I2C
GPIO4 SSD1306 SDA 硬件 I2C0
GPIO4 SSD1306 SCL 硬件 I2C0
3V3(OUT) VCC (两设备) 供电
GND

GND (两设备)

共地

两设备均使用 3.3V 供电,共地

程序

一、SGP30 驱动库结构(sgp30.h + sgp30.c)

这两个文件是我从 STM32 的 HAL 库 和 Arduino UNO 的 Adafruit_SGP30 库 移植到 Pico SDK 上的。核心改动是把硬件 I2C 换成了软件模拟,并保留了所有官方命令和 CRC 校验。

头文件 sgp30.h 重点

// 可自定义引脚(默认 GPIO4/5)
#define SGP30_SDA_PIN   4
#define SGP30_SCL_PIN   5

// 所有 SGP30 命令码
#define SGP30_CMD_INIT_AIR_QUALITY      0x2003
#define SGP30_CMD_MEASURE_AIR_QUALITY   0x2008
#define SGP30_CMD_SET_HUMIDITY          0x2061
// ...

// 数据结构
typedef struct {
    uint16_t co2;      // eCO₂ (ppm)
    uint16_t tvoc;     // TVOC (ppb)
    uint8_t  crc_ok;   // 两段 CRC 是否全部通过
} sgp30_data_t;

// 公开 API
void   SGP30_Init(void);
uint8_t SGP30_Write(uint8_t cmd_high, uint8_t cmd_low);
uint8_t SGP30_Read(sgp30_data_t *data);
uint8_t SGP30_SetHumidity(uint16_t abs_humidity);
uint8_t SGP30_GetBaseline(uint16_t *eco2, uint16_t *tvoc);
uint8_t SGP30_SetBaseline(uint16_t eco2, uint16_t tvoc);
uint8_t SGP30_SoftReset(void);
uint8_t SGP30_MeasureTest(uint16_t *result);
uint8_t SGP30_GetFeatureSet(uint16_t *version);
uint8_t SGP30_MeasureRaw(uint16_t *ethanol, uint16_t *h2);
uint8_t SGP30_CRC8(const uint8_t *data, uint8_t len);

实现文件 sgp30.c 核心 – 软件 I2C

// 起始条件
static void i2c_start(void) {
    SDA_HIGH(); SCL_HIGH(); DELAY_US();
    SDA_LOW();  DELAY_US(); SCL_LOW(); DELAY_US();
}

// 写一个字节 + 等待 ACK
static void i2c_write_byte(uint8_t data) {
    for (int i = 0; i < 8; i++) {
        (data & 0x80) ? SDA_HIGH() : SDA_LOW();
        data <<= 1;
        DELAY_US(); SCL_HIGH(); DELAY_US(); SCL_LOW(); DELAY_US();
    }
}

// 读一个字节,可选择回复 ACK 或 NACK
static uint8_t i2c_read_byte(uint8_t send_ack) { ... }

CRC‑8 校验完全按照 SGP30 数据手册实现(多项式 0x31,初始值 0xFF):

uint8_t SGP30_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 j = 0; j < 8; j++)
            crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1);
    }
    return crc;
}

二、主程序结构与功能(pico-sgp30.c)

程序结构

main()
├── stdio_init_all()          // USB 串口调试输出
├── ssd1306_init()            // 硬件 I2C 初始化 OLED
│   ├── i2c_init(i2c0, 400kHz)
│   ├── 发送 OLED 初始化命令序列
│   └── 清屏 + 显示标题 "SGP30 Sensor"
├── SGP30_Init()              // 软件 I2C 初始化 SGP30
│   └── 发送 init_air_quality 命令
└── while(1):
    ├── 发送 measure_air_quality 命令
    ├── 读取 6 字节 + CRC 校验
    ├── 【预热阶段】(前 15 秒,CO₂=400, TVOC=0)
    │   ├── OLED 显示倒计时
    │   └── 串口打印剩余秒数
    └── 【正常运行】
        ├── OLED 显示 eCO₂ (ppm) + TVOC (ppb)
        ├── 若 CRC 错误则额外显示 "CRC FAIL!"
        ├── 串口打印数据
        └── sleep 2 秒

OLED 显示布局(128×64,8 个 page)

Page 内容
0

SGP30 Sensor (固定标题)

2

eCO2:   400 ppm (5位右对齐)

4

TVOC:   0 ppb (挥发性有机物浓度)

6

CRC FAIL! (仅出错时显示)

关键代码片段 – 预热检测

SGP30 传感器上电后前 15 秒会强制输出 CO₂=400 ppm、TVOC=0 ppb。主程序利用这一特性:

if (!warmed_up && (now - start_ms) < 15000 && data.co2 == 400 && data.tvoc == 0) {
    uint32_t elapsed = (now - start_ms) / 1000;
    snprintf(buf, sizeof(buf), "Warm-up %lus...  ", 15 - elapsed);
    ssd1306_draw_string(2, 0, buf);
    printf("Warming up... %lu s remaining\n", 15 - elapsed);
    sleep_ms(1000);
    continue;
}

编译与烧录

1. 前置工具

Raspberry Pi Pico C/C++ SDK​https://github.com/raspberrypi/pico-sdk

ARM GCC 交叉编译器 (arm-none-eabi-gcc)

CMake (≥ 3.13) + Ninja

2. 构建命令

git clone https://github.com/jasonobama/pico-sgp30.git
cd pico-sgp30
export PICO_SDK_PATH=/path/to/pico-sdk   # 设置 SDK 路径
cmake -B build -G Ninja .
cmake --build build

3. 烧录到 Pico 2

  • 按住 BOOTSEL 键,插入 USB

  • 将 pico-sgp30.uf2 拖入弹出的 RPI-RP2 盘符

  • 程序自动运行,OLED 点亮

源码

Raspberry Pi Pico C/C++ SDK​https://github.com/Jasonobama/pico-sgp30.git

更多推荐