跳转至

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. 避免触摸引脚旁边走高频信号线