Chapter 07

传感器驱动实战

掌握 I2C/SPI 总线协议,驱动温湿度、气压、IMU 与 OLED 显示模块

I2C 总线协议

I2C(Inter-Integrated Circuit,I²C)是菲利浦半导体(现 NXP)于 1982 年发明的串行总线协议,只需两根线(SDA 数据线、SCL 时钟线)即可连接多达 127 个设备,是传感器模块最常用的通信接口。

I2C 总线拓扑: ESP32 (Master) │ │ SCL SDA │ │ ├──────────┤ 4.7kΩ 上拉到 3.3V(开漏总线必需) │ │ ├──[BMP280]──┤ 地址 0x76 或 0x77 │ │ ├──[MPU6050]─┤ 地址 0x68 或 0x69 │ │ └──[OLED]───┤ 地址 0x3C(通常固定) I2C 数据帧格式: S 地址(7bit) R/W ACK 数据字节 ACK ... P │ ───────── ─── ─── ──────── ─── │ Start Stop

I2C 总线核心概念

SDA / SCL
SDA(Serial Data)传数据,SCL(Serial Clock)提供时钟。两者均为开漏(Open-Drain)输出,多个设备挂在同一总线上不会冲突,上拉电阻确保空闲时为高。
设备地址
7bit 地址(0x00~0x7F)唯一标识总线上的每个设备,通常可通过 ADDR 引脚的高低配置 1~2 位,允许同一总线挂 2~4 个相同型号的设备。
ACK / NACK
每传输 8bit 后,接收方拉低 SDA 一个时钟周期表示确认(ACK);不拉低表示 NACK(未响应或出错)。Master 发送最后一字节读数据后通常回 NACK 再发 Stop。
时钟速率
标准模式 100kHz,快速模式 400kHz,高速模式 3.4MHz(ESP32 支持前两种)。速率越高,传输越快,但对布线质量要求越高,长线路应降低速率。

ESP-IDF I2C Master 初始化(v5.x 新 API)

#include "driver/i2c_master.h"
#include "esp_log.h"

#define I2C_PORT    I2C_NUM_0
#define I2C_SDA     GPIO_NUM_21
#define I2C_SCL     GPIO_NUM_22
#define I2C_SPEED   400000    // 400kHz

static i2c_master_bus_handle_t  i2c_bus;
static i2c_master_dev_handle_t  bmp280_dev;

void i2c_init(void)
{
    i2c_master_bus_config_t bus_cfg = {
        .clk_source         = I2C_CLK_SRC_DEFAULT,
        .i2c_port           = I2C_PORT,
        .sda_io_num         = I2C_SDA,
        .scl_io_num         = I2C_SCL,
        .glitch_ignore_cnt  = 7,
        .flags.enable_internal_pullup = 1,  // 启用内部上拉
    };
    ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &i2c_bus));

    i2c_device_config_t bmp_cfg = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address  = 0x76,    // BMP280 地址(SDO 接 GND)
        .scl_speed_hz    = I2C_SPEED,
    };
    ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &bmp_cfg, &bmp280_dev));
}

void i2c_write_reg(uint8_t reg, uint8_t val)
{
    uint8_t buf[2] = {reg, val};
    i2c_master_transmit(bmp280_dev, buf, sizeof(buf), -1);
}

void i2c_read_reg(uint8_t reg, uint8_t *buf, size_t len)
{
    i2c_master_transmit_receive(bmp280_dev, ®, 1, buf, len, -1);
}

DHT22 温湿度传感器(单总线)

DHT22(又名 AM2302)是一款极为常见的数字温湿度传感器,测量范围:温度 -40~+80°C(精度 ±0.5°C),湿度 0~100% RH(精度 ±2%)。它使用专有的单总线(1-Wire-like)协议通信,只需一根 GPIO 线。

DHT22 通信时序: 主机拉低 18ms → 主机释放 → DHT22 响应(拉低80μs,再拉高80μs) → 传输 40bit 数据(16bit 湿度 + 16bit 温度 + 8bit 校验和) 每个 bit 的编码方式: 低电平 50μs 高电平26-28μs = bit 0 低电平 50μs 高电平 70μs = bit 1 数据格式(40bit): [湿度高8bit][湿度低8bit][温度高8bit][温度低8bit][校验和8bit] 校验和 = 前四字节之和 & 0xFF
/* 简化版 DHT22 读取(实际项目推荐使用 esp-idf-lib 的 dht 组件)*/
#include "driver/gpio.h"
#include "esp_timer.h"

#define DHT22_PIN GPIO_NUM_4

static int64_t wait_level(int level, int64_t timeout_us)
{
    int64_t start = esp_timer_get_time();
    while (gpio_get_level(DHT22_PIN) != level) {
        if (esp_timer_get_time() - start > timeout_us) return -1;
    }
    return esp_timer_get_time() - start;
}

esp_err_t dht22_read(float *temperature, float *humidity)
{
    uint8_t data[5] = {0};

    /* 发送起始信号:拉低 18ms */
    gpio_set_direction(DHT22_PIN, GPIO_MODE_OUTPUT);
    gpio_set_level(DHT22_PIN, 0);
    esp_rom_delay_us(18000);
    gpio_set_level(DHT22_PIN, 1);
    esp_rom_delay_us(40);
    gpio_set_direction(DHT22_PIN, GPIO_MODE_INPUT);

    /* 等待 DHT22 响应 */
    if (wait_level(0, 100) < 0) return ESP_ERR_TIMEOUT;
    if (wait_level(1, 100) < 0) return ESP_ERR_TIMEOUT;
    if (wait_level(0, 100) < 0) return ESP_ERR_TIMEOUT;

    /* 读取 40bit 数据 */
    for (int i = 0; i < 40; i++) {
        if (wait_level(1, 65) < 0) return ESP_ERR_INVALID_RESPONSE;
        int64_t high_us = wait_level(0, 90);
        if (high_us < 0) return ESP_ERR_INVALID_RESPONSE;
        data[i / 8] <<= 1;
        if (high_us > 45) data[i / 8] |= 1;   // >45μs = bit 1
    }

    /* 校验 */
    if (data[4] != ((data[0] + data[1] + data[2] + data[3]) & 0xFF))
        return ESP_ERR_INVALID_CRC;

    *humidity    = ((uint16_t)data[0] << 8 | data[1]) / 10.0f;
    *temperature = ((uint16_t)(data[2] & 0x7F) << 8 | data[3]) / 10.0f;
    if (data[2] & 0x80) *temperature = -*temperature;   // 负温度
    return ESP_OK;
}
DHT22 最小采样间隔

DHT22 采样间隔至少 2 秒。连续读取会导致传感器内部状态错误,返回错误数据。如果需要更高频率的温度测量,请考虑使用 I2C 接口的 BMP280 或 SHT31。

BMP280 气压/温度传感器(I2C)

BMP280 是博世(Bosch)出品的高精度气压温度传感器,气压精度 ±1hPa,温度精度 ±1°C,通过 I2C 或 SPI 接口通信。常用于海拔计算(气压每升高 100m 约下降 12hPa)。

/* 使用 ESP-IDF 组件管理器安装 bmp280 驱动:*/
// idf.py add-dependency "idf-extra-components/bmp280"

#include "bmp280.h"

void bmp280_task(void *arg)
{
    bmp280_params_t params;
    bmp280_init_default_params(¶ms);
    bmp280_t bmp280_dev = {
        .i2c_dev = {
            .port   = I2C_NUM_0,
            .addr   = BMP280_I2C_ADDRESS_0,   // 0x76
            .cfg    = { .sda_io_num = 21, .scl_io_num = 22,
                        .master.clk_speed = 400000 }
        }
    };
    bmp280_init(&bmp280_dev, ¶ms);

    float pressure, temperature, humidity;
    while (1) {
        bmp280_read_float(&bmp280_dev, &temperature, &pressure, &humidity);
        ESP_LOGI("BMP280", "温度: %.2f°C  气压: %.2f hPa",
                 temperature, pressure / 100.0f);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

OLED 显示(SSD1306 + I2C)

SSD1306 是 128×64 点阵 OLED 显示控制器,通过 I2C(地址 0x3C/0x3D)或 SPI 驱动。ESP-IDF 生态中常用的驱动库是 esp_lvgl_port(配合 LVGL GUI 框架)或轻量级的 ssd1306 组件。

// idf.py add-dependency "nopnop2002/ssd1306"
#include "ssd1306.h"

void oled_demo(void)
{
    SSD1306_t oled;
    i2c_master_init(&oled, 21, 22, -1);      // SDA=21, SCL=22, RST=-1(无)
    ssd1306_init(&oled, 128, 64);
    ssd1306_clear_screen(&oled, false);
    ssd1306_contrast(&oled, 0xFF);

    ssd1306_display_text(&oled, 0, "ESP32 IoT", 9, false);
    ssd1306_display_text(&oled, 2, "Temp: 25.6C", 11, false);
    ssd1306_display_text(&oled, 4, "Humi: 65.3%", 11, false);
}

MPU6050 六轴 IMU

MPU6050 集成三轴加速度计和三轴陀螺仪,I2C 地址 0x68(AD0 接 GND)或 0x69(AD0 接 VCC)。常用于姿态检测、计步器、抖动补偿等场景。

#define MPU6050_ADDR   0x68
#define MPU_PWR_MGT1   0x6B
#define MPU_ACCEL_XOUT 0x3B
#define MPU_GYRO_XOUT  0x43

void mpu6050_init(void)
{
    i2c_write_reg(MPU_PWR_MGT1, 0x00);  // 唤醒,退出休眠模式
}

void mpu6050_read(void)
{
    uint8_t raw[14];
    i2c_read_reg(MPU_ACCEL_XOUT, raw, 14);   // 一次读取加速度+温度+陀螺仪

    int16_t ax = (raw[0] << 8) | raw[1];
    int16_t ay = (raw[2] << 8) | raw[3];
    int16_t az = (raw[4] << 8) | raw[5];
    int16_t gx = (raw[8] << 8) | raw[9];
    int16_t gy = (raw[10] << 8) | raw[11];
    int16_t gz = (raw[12] << 8) | raw[13];

    /* 量程 ±2g 时:1g = 16384 LSB */
    ESP_LOGI("MPU", "Accel: X=%.2fg Y=%.2fg Z=%.2fg",
             ax/16384.0f, ay/16384.0f, az/16384.0f);
    /* 量程 ±250°/s 时:1°/s = 131 LSB */
    ESP_LOGI("MPU", "Gyro:  X=%.1f Y=%.1f Z=%.1f deg/s",
             gx/131.0f, gy/131.0f, gz/131.0f);
}
推荐使用 esp-idf-lib

esp-idf-lib 是 ESP-IDF 生态中最完整的传感器驱动库集合,包含 DHT、BMP280、MPU6050、SSD1306、DS18B20、ADS1115 等数十种传感器的经过测试的驱动,大多数情况下无需自己从零实现。