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 通道电容触摸 |
关键限制总结
- GPIO 6~11:绝对不能用,连接内部 Flash
- GPIO 34/35/36/39:只能输入,没有上拉/下拉,需要外部电阻
- ADC2:WiFi 启动后完全不可用,传感器接 ADC1 通道
- GPIO 0:拉低进入下载模式,不要在上电时接低电平
- 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 频率与分辨率¶
| 频率 | 最大分辨率 | 占空比级数 |
|---|---|---|
| 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)非线性严重==,中间段较准确。
解决方法:
-
ESP-IDF 校准 API(推荐):使用出厂校准数据
-
查表校正:测量多个已知电压点,建立校正表
- 硬件方案:使用外部 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()指定音调频率
触摸传感器不灵敏怎么办?
- 调低阈值(
THRESHOLD值减小) - 增大触摸面积(焊一块铜片或铝箔)
- 使用屏蔽线,减少环境干扰
- 软件滤波:多次读取取平均值
- 避免触摸引脚旁边走高频信号线