跳转至

GPIO 与模拟外设

ESP32 拥有灵活的 GPIO 矩阵和丰富的模拟外设,包括 LEDC PWM(任意引脚)、12 位 ADC、8 位 DAC 和电容式触摸传感器。掌握这些外设是 ESP32 开发的基础。


一、GPIO 引脚概览

ESP32-DevKitC V4 引脚图

             ┌─────[USB]─────┐
        3V3  │[ ]          [ ]│ GND
         EN  │[ ]          [ ]│ GPIO 23  (MOSI)
  SVP  GP36  │[ ]          [ ]│ GPIO 22  (SCL)
  SVN  GP39  │[ ]          [ ]│ GPIO 1   (TX0)
       GP34  │[ ]          [ ]│ GPIO 3   (RX0)
       GP35  │[ ]          [ ]│ GPIO 21  (SDA)
       GP32  │[ ]          [ ]│ GND
       GP33  │[ ]          [ ]│ GPIO 19  (MISO)
       GP25  │[ ]          [ ]│ GPIO 18  (SCK)
       GP26  │[ ]          [ ]│ GPIO 5   (SS)
       GP27  │[ ]          [ ]│ GPIO 17  (TX2)
       GP14  │[ ]          [ ]│ GPIO 16  (RX2)
       GP12  │[ ]          [ ]│ GPIO 4
        GND  │[ ]          [ ]│ GPIO 0   (BOOT)
       GP13  │[ ]          [ ]│ GPIO 2   (LED)
        D9   │[ ]          [ ]│ GPIO 15
        D10  │[ ]          [ ]│ D8
        D11  │[ ]          [ ]│ D7
        5V   │[ ]          [ ]│ D6
             └───────────────┘

引脚分类与限制

分类 引脚 说明
通用 GPIO 0, 2, 4, 5, 12~19, 21~23, 25~27, 32, 33 输入/输出均可
仅输入 34, 35, 36 (SVP), 39 (SVN) 无内部上拉/下拉
禁止使用 6, 7, 8, 9, 10, 11 连接内部 Flash SPI
启动相关 0, 2, 5, 12, 15 影响启动模式,使用需注意
ADC1 32~39 WiFi 不影响
ADC2 0, 2, 4, 12~15, 25~27 WiFi 开启时不可用
DAC 25 (DAC1), 26 (DAC2) 8 位输出
触摸 0, 2, 4, 12~15, 27, 32, 33 10 通道电容触摸

关键限制总结

  1. GPIO 6~11:绝对不能用,连接内部 Flash
  2. GPIO 34/35/36/39:只能输入,没有上拉/下拉,需要外部电阻
  3. ADC2:WiFi 启动后完全不可用,传感器接 ADC1 通道
  4. GPIO 0:拉低进入下载模式,不要在上电时接低电平
  5. GPIO 12:影响 Flash 电压选择,3.3V Flash 不要在启动时拉高

二、数字 IO

ESP32 的数字 IO 操作兼容 Arduino API:

// 引脚模式
pinMode(pin, INPUT);           // 输入(高阻态)
pinMode(pin, OUTPUT);          // 输出
pinMode(pin, INPUT_PULLUP);    // 内部上拉输入
pinMode(pin, INPUT_PULLDOWN);  // 内部下拉输入(Arduino UNO 不支持!)

// 读写
digitalWrite(pin, HIGH);       // 输出高电平(3.3V)
int val = digitalRead(pin);    // 读取电平

LED 闪烁

const int LED_PIN = 2;  // ESP32-DevKitC 板载 LED 在 GPIO 2

void setup() {
    pinMode(LED_PIN, OUTPUT);
}

void loop() {
    digitalWrite(LED_PIN, HIGH);
    delay(500);
    digitalWrite(LED_PIN, LOW);
    delay(500);
}

GPIO 中断

ESP32 的所有 GPIO 引脚都支持中断:

volatile int count = 0;
volatile bool flag = false;

void IRAM_ATTR buttonISR() {  // IRAM_ATTR 确保 ISR 在 RAM 中执行
    count++;
    flag = true;
}

void setup() {
    Serial.begin(115200);
    pinMode(0, INPUT_PULLUP);  // GPIO 0(BOOT 按键)
    attachInterrupt(digitalPinToInterrupt(0), buttonISR, FALLING);
}

void loop() {
    if (flag) {
        flag = false;
        Serial.printf("按键次数: %d\n", count);
    }
}

IRAM_ATTR 必须加

ESP32 的代码默认运行在外部 Flash 上。ISR 如果不加 IRAM_ATTR,在 Flash 被占用时(如 WiFi/BLE 操作、写 Flash)可能导致崩溃。始终给 ISR 加上 IRAM_ATTR。


三、LEDC PWM

ESP32 没有 Arduino 的 analogWrite(),而是使用更强大的 LEDC(LED Control)模块:

  • 16 个独立通道(高速 8 个 + 低速 8 个)
  • 可映射到任意 GPIO
  • 可配置频率和分辨率

基本使用(Arduino ESP32 3.x API)

const int LED_PIN = 2;

void setup() {
    // 新版 API(ESP32 Arduino Core 3.x)
    ledcAttach(LED_PIN, 5000, 8);  // 引脚, 频率 5kHz, 分辨率 8 位
}

void loop() {
    // 呼吸灯
    for (int duty = 0; duty <= 255; duty++) {
        ledcWrite(LED_PIN, duty);
        delay(5);
    }
    for (int duty = 255; duty >= 0; duty--) {
        ledcWrite(LED_PIN, duty);
        delay(5);
    }
}

旧版 API(ESP32 Arduino Core 2.x)

const int LED_PIN = 2;
const int PWM_CHANNEL = 0;
const int PWM_FREQ = 5000;
const int PWM_RESOLUTION = 8;  // 8 位 → 0~255

void setup() {
    ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);
    ledcAttachPin(LED_PIN, PWM_CHANNEL);
}

void loop() {
    ledcWrite(PWM_CHANNEL, 128);  // 50% 占空比
}

PWM 频率与分辨率

\[最大分辨率 = \log_2\left(\frac{80MHz}{f_{PWM}}\right)\]
频率 最大分辨率 占空比级数
1 kHz 16 bit 65536 级
5 kHz 13~14 bit 8192~16384 级
40 kHz 11 bit 2048 级
312.5 kHz 8 bit 256 级
1 MHz ~6 bit 64 级

舵机控制

// 舵机需要 50Hz、周期 20ms
const int SERVO_PIN = 13;

void setup() {
    ledcAttach(SERVO_PIN, 50, 16);  // 50Hz, 16 位分辨率
}

void setServoAngle(int angle) {
    // 0° → 0.5ms, 180° → 2.5ms
    // 16 位 50Hz: 1ms = 3277, 占空比 = 1638~8192
    int duty = map(angle, 0, 180, 1638, 8192);
    ledcWrite(SERVO_PIN, duty);
}
也可以直接使用 ESP32Servo 库,API 与 Arduino Servo 库相同。


四、ADC(模数转换)

ESP32 内置 2 个 12 位 SAR ADC,共 18 个通道。

ADC 通道分配

ADC 通道 GPIO WiFi 影响
ADC1 CH0~CH7 36, 37, 38, 39, 32, 33, 34, 35 ✅ 无影响
ADC2 CH0~CH9 4, 0, 2, 15, 13, 12, 14, 27, 25, 26 ⚠️ WiFi 开启后不可用

基本读取

void setup() {
    Serial.begin(115200);
    analogReadResolution(12);     // 设置 12 位分辨率(0~4095)
    analogSetAttenuation(ADC_11db); // 设置衰减(满量程 ~3.3V)
}

void loop() {
    int raw = analogRead(34);     // 读取 GPIO 34(ADC1_CH6)
    float voltage = raw * (3.3 / 4095.0);

    Serial.printf("ADC: %d, 电压: %.2f V\n", raw, voltage);
    delay(100);
}

衰减设置

衰减等级 输入范围 精度
ADC_0db 0~1.1V 最高
ADC_2_5db 0~1.5V 高
ADC_6db 0~2.2V 中
ADC_11db 0~3.3V 较低(两端非线性)

ESP32 ADC 非线性问题

ESP32 的 ADC 在两端(接近 0V 和 3.3V)非线性严重,中间段较准确。

解决方法:

  1. ESP-IDF 校准 API(推荐):使用出厂校准数据

    #include <esp_adc_cal.h>
    esp_adc_cal_characteristics_t adc_chars;
    esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, 
                              ADC_WIDTH_BIT_12, 1100, &adc_chars);
    uint32_t voltage = esp_adc_cal_raw_to_voltage(raw, &adc_chars);
    

  2. 查表校正:测量多个已知电压点,建立校正表

  3. 硬件方案:使用外部 ADC(如 ADS1115,16 位 I2C ADC)

五、DAC(数模转换)

ESP32 有 2 个 8 位 DAC 通道,可以输出真正的模拟电压:

DAC 通道 GPIO
DAC1 GPIO 25
DAC2 GPIO 26
void setup() {
    // DAC 不需要 pinMode
}

void loop() {
    // 输出锯齿波
    for (int i = 0; i < 256; i++) {
        dacWrite(25, i);        // 输出 0~3.3V
        delayMicroseconds(100);
    }
}

生成正弦波

void loop() {
    for (int i = 0; i < 360; i++) {
        float rad = i * PI / 180.0;
        int val = (int)(127.5 + 127.5 * sin(rad));  // 0~255
        dacWrite(25, val);
        delayMicroseconds(50);
    }
}

DAC 性能

  • 分辨率:8 位(256 级)
  • 输出范围:0~3.3V
  • ESP32-S2 的 DAC 支持 DMA,可以高速输出波形
  • 如果需要更高精度的模拟输出,考虑使用外部 DAC(MCP4725 等)

六、触摸传感器

ESP32 内置 10 通道电容式触摸传感器,无需任何外部器件:

触摸引脚

触摸通道 GPIO
T0 GPIO 4
T1 GPIO 0
T2 GPIO 2
T3 GPIO 15
T4 GPIO 13
T5 GPIO 12
T6 GPIO 14
T7 GPIO 27
T8 GPIO 33
T9 GPIO 32

基本读取

const int TOUCH_PIN = 4;   // T0 = GPIO 4
const int THRESHOLD = 40;  // 触摸阈值(需要根据实际调整)

void setup() {
    Serial.begin(115200);
}

void loop() {
    int touchValue = touchRead(TOUCH_PIN);
    Serial.printf("触摸值: %d", touchValue);

    if (touchValue < THRESHOLD) {
        Serial.println(" → 触摸检测到!");
        digitalWrite(2, HIGH);
    } else {
        Serial.println();
        digitalWrite(2, LOW);
    }
    delay(100);
}

触摸值说明

  • 未触摸:值较大(通常 50~80)
  • 触摸时:值变小(通常 10~20)
  • 阈值需要根据实际接线和环境调整
  • 可以在触摸引脚焊接一块金属触摸片或直接连一根导线

触摸中断

volatile bool touchDetected = false;

void gotTouch() {
    touchDetected = true;
}

void setup() {
    Serial.begin(115200);
    touchAttachInterrupt(4, gotTouch, 40);  // GPIO 4, 回调, 阈值
}

void loop() {
    if (touchDetected) {
        touchDetected = false;
        Serial.println("触摸!");
    }
}

触摸唤醒(Deep Sleep)

void setup() {
    // 设置触摸引脚为唤醒源
    touchAttachInterrupt(4, callback, 40);
    esp_sleep_enable_touchpad_wakeup();

    Serial.println("即将进入 Deep Sleep...");
    delay(1000);
    esp_deep_sleep_start();
}

七、GPIO 矩阵

ESP32 的一大特性是 GPIO 矩阵——几乎所有外设信号都可以映射到任意 GPIO:

// UART2 可以映射到任意引脚
Serial2.begin(115200, SERIAL_8N1, 16, 17);  // RX=16, TX=17
Serial2.begin(115200, SERIAL_8N1, 4, 5);    // 改为 RX=4, TX=5 也行!

// SPI 也可以自定义引脚
SPI.begin(14, 12, 13, 15);  // SCK, MISO, MOSI, SS

// I2C 也可以自定义
Wire.begin(21, 22);   // SDA=21, SCL=22(默认)
Wire.begin(4, 5);     // 改为 SDA=4, SCL=5

GPIO 矩阵 vs 直接 IO

通过 GPIO 矩阵映射的信号最高频率约 40MHz。 如果需要更高速度(如 SPI 80MHz),需要使用默认引脚(直连外设,不经过矩阵)。


八、常用函数速查

函数 功能 说明
pinMode(pin, mode) 设置引脚模式 支持 INPUT_PULLDOWN
digitalWrite(pin, val) 数字输出 3.3V 逻辑
digitalRead(pin) 数字输入 —
analogRead(pin) ADC 读取 12 位,0~4095
analogReadResolution(bits) 设置 ADC 分辨率 9~12 位
analogSetAttenuation(atten) 设置 ADC 衰减 影响输入范围
dacWrite(pin, val) DAC 输出 8 位,GPIO 25/26
touchRead(pin) 触摸传感器读取 触摸时值变小
touchAttachInterrupt() 触摸中断 可用于唤醒
ledcAttach(pin, freq, res) PWM 配置 3.x API
ledcWrite(pin, duty) PWM 输出 —
ledcWriteTone(pin, freq) 输出方波音调 —

九、常见问题

ESP32 能接 5V 传感器吗?

不能直接接! ESP32 GPIO 电压为 3.3V,没有 5V 容忍。接 5V 信号会烧毁芯片。

解决方法:

  • 使用 电平转换模块(如 TXS0108E 双向电平转换)
  • 使用 电阻分压(5V → 3.3V,仅适用于输入)
  • 选择 3.3V 版本的传感器模块

為什么 analogRead() 在 WiFi 连接后读值为 0?

你可能使用了 ADC2 通道的引脚。WiFi 开启后 ADC2 被 WiFi 驱动独占。

解决方法:将传感器改接到 ADC1 通道(GPIO 32~39)。

LEDC PWM 频率怎么选?

  • LED 调光:1~10 kHz,8 位分辨率即可
  • 舵机控制:50 Hz,10~16 位分辨率
  • 电机 PWM:20~40 kHz(超出人耳范围),10 位分辨率
  • 蜂鸣器:使用 ledcWriteTone() 指定音调频率

触摸传感器不灵敏怎么办?

  1. 调低阈值(THRESHOLD 值减小)
  2. 增大触摸面积(焊一块铜片或铝箔)
  3. 使用屏蔽线,减少环境干扰
  4. 软件滤波:多次读取取平均值
  5. 避免触摸引脚旁边走高频信号线