2. ESP-IDF 与 Arduino 环境搭建

好,咱们正式开始动手。这一章的目标很明确——把开发环境搭起来,让 ESP32 能跑通第一个 I2C 扫描程序。别小看这一步,我见过太多人卡在环境配置上,代码写好了却编译不过,最后发现是工具链没装对。

ESP32 的开发,主流就两条路:ESP-IDF 和 Arduino IDE。两条路我都走过,各有各的脾气。下面我带你一条条捋清楚。

2.1 ESP-IDF 开发环境配置

ESP-IDF 是乐鑫官方的框架,功能全、性能强,但配置起来稍微有点门槛。我个人习惯用 VS Code + ESP-IDF 插件,这套组合最省心。

2.1.1 安装步骤

  1. 安装 Python 3.8+——ESP-IDF 的编译脚本依赖 Python。记得勾选“Add Python to PATH”。
  2. 安装 Git——用来拉取 ESP-IDF 仓库。
  3. 安装 VS Code——然后搜索并安装 ESP-IDF Extension(由 Espressif 官方提供)。
  4. 配置 ESP-IDF——在 VS Code 中按 F1,输入 ESP-IDF: Configure ESP-IDF Extension,选择 Express 模式,它会自动下载工具链和 SDK。
我的经验:如果你网络不好,可以手动从乐鑫官网下载 esp-idf-tools-setup-xxx.exe(Windows)或使用 install.sh(Linux/macOS)。我第一次装的时候,自动下载卡了半小时,后来换成离线包,5 分钟搞定。

2.1.2 验证安装

打开终端,运行:

idf.py --version

如果看到类似 ESP-IDF v5.1.2 的输出,说明环境就绪了。嗯,这里要注意:不同版本的 API 可能有细微差异,建议统一用 v5.0 以上版本。

2.2 Arduino IDE 环境配置

Arduino IDE 的优势是简单。你想想看,写个传感器驱动,几行代码就能跑起来。适合快速验证和原型开发。

2.2.1 安装步骤

  1. 从 arduino.cc 下载 Arduino IDE(建议 2.x 版本,界面更现代)。
  2. 打开 IDE,进入 文件 > 首选项,在 附加开发板管理器网址 中添加:
    https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. 进入 工具 > 开发板 > 开发板管理器,搜索 ESP32,安装 esp32 by Espressif Systems。
  4. 选择开发板:工具 > 开发板 > ESP32 Dev Module。
避坑:我曾经遇到过 Arduino IDE 1.x 版本下,ESP32 包安装后编译报错“找不到 esp32-hal-i2c.h”。后来发现是 IDE 缓存问题,重启 IDE 或者手动删除 %LOCALAPPDATA%/Arduino15 下的 staging 文件夹再重装就好了。

2.3 I2C 库函数对比:ESP-IDF vs Wire 库

两个环境下的 I2C 操作,底层都是同一套硬件,但 API 风格完全不同。我整理了一张对比表,你一看就明白。

功能 ESP-IDF (driver/i2c.h) Arduino (Wire 库)
初始化 i2c_param_config() + i2c_driver_install() Wire.begin(SDA, SCL)
写数据 i2c_master_write_to_device() Wire.beginTransmission() + Wire.write() + Wire.endTransmission()
读数据 i2c_master_read_from_device() Wire.requestFrom() + Wire.read()
错误处理 返回 esp_err_t 错误码 返回 byte 状态码(0=成功)
时钟频率 配置时设置 master.clk_speed Wire.setClock()
超时控制 配置时设置 master.timeout 无原生支持,需手动实现

说白了,ESP-IDF 更底层、更灵活,适合做产品级开发;Wire 库更抽象、更易用,适合快速验证。我在项目中通常这样选:原型阶段用 Arduino,量产阶段切到 ESP-IDF。

2.4 第一个 I2C 扫描程序

不管用哪个环境,第一个程序我都建议写 I2C 扫描器。它能帮你确认硬件连接是否正确、设备地址是多少。我每次拿到一块新传感器板,第一件事就是跑这个程序。

2.4.1 Arduino 版本

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);  // ESP32 默认 SDA=21, SCL=22
  Serial.println("\nI2C Scanner");
}

void loop() {
  byte error, address;
  int nDevices = 0;

  for (address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16) Serial.print("0");
      Serial.print(address, HEX);
      Serial.println(" !");
      nDevices++;
    }
    else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000);
}

2.4.2 ESP-IDF 版本

#include <stdio.h>
#include "driver/i2c.h"
#include "esp_log.h"

#define I2C_MASTER_SCL_IO    22
#define I2C_MASTER_SDA_IO    21
#define I2C_MASTER_NUM       I2C_NUM_0
#define I2C_MASTER_FREQ_HZ   100000

static const char *TAG = "i2c-scanner";

void app_main(void) {
  i2c_config_t conf = {
    .mode = I2C_MODE_MASTER,
    .sda_io_num = I2C_MASTER_SDA_IO,
    .scl_io_num = I2C_MASTER_SCL_IO,
    .sda_pullup_en = GPIO_PULLUP_ENABLE,
    .scl_pullup_en = GPIO_PULLUP_ENABLE,
    .master.clk_speed = I2C_MASTER_FREQ_HZ,
  };

  ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf));
  ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0));

  ESP_LOGI(TAG, "I2C scanner started");

  while (1) {
    int nDevices = 0;
    for (uint8_t addr = 1; addr < 127; addr++) {
      i2c_cmd_handle_t cmd = i2c_cmd_link_create();
      i2c_master_start(cmd);
      i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
      i2c_master_stop(cmd);
      esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(100));
      i2c_cmd_link_delete(cmd);

      if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Device found at 0x%02X", addr);
        nDevices++;
      } else if (ret == ESP_ERR_TIMEOUT) {
        ESP_LOGW(TAG, "Timeout at 0x%02X", addr);
      }
    }
    if (nDevices == 0) {
      ESP_LOGI(TAG, "No devices found");
    }
    vTaskDelay(pdMS_TO_TICKS(5000));
  }
}
关键点:两个版本的逻辑完全一样——遍历地址 1~126,发送起始信号+设备地址,根据 ACK 判断设备是否存在。区别在于 ESP-IDF 需要手动管理命令链(i2c_cmd_link_create),而 Arduino 的 Wire 库帮你封装好了。

2.5 知识结构图

下面这张图帮你理清本章的核心脉络:

ESP32 I2C 开发环境搭建与扫描程序 ESP-IDF 环境 Arduino IDE 环境 配置要点 VS Code + ESP-IDF Extension idf.py 工具链 / 离线包安装 配置要点 Arduino IDE 2.x + ESP32 包 开发板管理器 / 附加网址 I2C 库函数对比 ESP-IDF: i2c_master_write/read vs Arduino: Wire.beginTransmission/requestFrom 第一个 I2C 扫描程序(地址遍历 1~126) 串口输出:设备地址 / 无设备 / 超时

2.6 常见问题与调试技巧

  • 扫描不到设备?先检查接线——SDA 和 SCL 有没有接反?上拉电阻有没有接?ESP32 内部虽然有弱上拉,但外部 4.7kΩ 电阻更可靠。
  • 地址重复?有些传感器有多个地址引脚(如 AD0),确保硬件设置与代码一致。
  • Wire 库编译报错?检查 Arduino IDE 中是否选择了正确的 ESP32 开发板型号。
  • ESP-IDF 编译卡住?试试 idf.py fullclean 然后重新编译,有时候缓存会捣乱。
我的习惯:每次焊接完新板子,先用 Arduino 的 I2C 扫描程序跑一遍,确认所有设备地址都正确,再切到 ESP-IDF 写正式驱动。这样能快速排除硬件问题,避免在软件里瞎猜。

好了,环境搭好了,扫描程序也跑通了。接下来你就可以放心地接传感器、写驱动了。记住这个流程——先扫地址,再读数据,最后调效率。一步一个脚印,不会出大问题。