ESP32 Arduino 语法 是指在使用 Arduino IDE 或 PlatformIO 配合 Arduino 核心开发 ESP32 应用时所遵循的编程规范和 API。它将 ESP32 强大的硬件功能封装成简单易用的函数和类,使得开发者能够以 Arduino 熟悉的编程范式来操控 ESP32 的 Wi-Fi、蓝牙、GPIO、串口等功能,极大地降低了 ESP32 的学习曲线和开发难度。

核心思想:将复杂底层的 ESP-IDF 功能抽象成 Arduino 风格的函数调用,让开发者能够像使用 Arduino Uno/Mega 一样,快速上手 ESP32 的 Wi-Fi、蓝牙和多核特性。


一、Arduino 核心与 ESP32

1.1 什么是 Arduino 核心 (Board Support Package - BSP)?

Arduino 核心是一套针对特定微控制器(如 AVR 芯片、STM32 芯片、ESP32 芯片)的底层驱动、库和编译器配置,旨在将微控制器的复杂硬件操作抽象成 Arduino 统一的 API。当我们在 Arduino IDE 中选择“ESP32 Dev Module”等板卡时,实际上就是激活了 ESP32 的 Arduino 核心。

1.2 ESP32 Arduino 核心的特点

ESP32 Arduino 核心由乐鑫科技和社区共同维护,主要特点包括:

  1. 兼容性:尽可能兼容标准的 Arduino API,如 digitalWrite()analogRead()Serial 等。
  2. 扩展性:提供了大量 ESP32 特有的 API,用于控制 Wi-Fi、蓝牙、双核任务、触摸传感器、DAC、高级定时器等。
  3. 底层基于 ESP-IDF:ESP32 Arduino 核心实际上是基于乐鑫官方的 ESP-IDF (Espressif IoT Development Framework) 构建的,将复杂的 FreeRTOS 任务管理、Wi-Fi/蓝牙协议栈、外设驱动等封装起来。
  4. 多核支持:提供了在两颗核心 (Pro_CPU 和 App_CPU) 上创建任务的机制。

二、基本 Arduino 语法复习 (适用于 ESP32)

对于熟悉 Arduino 的开发者来说,ESP32 上的基础语法和结构是相同的。

2.1 Sketch 结构

每个 Arduino 程序(称为 Sketch)都包含两个必备函数:

  • void setup():程序启动时只执行一次,用于初始化串口、引脚模式、Wi-Fi 等。
  • void loop():在 setup() 执行完毕后,无限循环执行,是程序的主体逻辑。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
void setup() {
Serial.begin(115200); // 初始化串口通信,波特率为 115200
pinMode(LED_BUILTIN, OUTPUT); // 设置内置 LED 引脚为输出模式
}

void loop() {
digitalWrite(LED_BUILTIN, HIGH); // 点亮 LED
Serial.println("LED ON");
delay(1000); // 延迟 1 秒
digitalWrite(LED_BUILTIN, LOW); // 熄灭 LED
Serial.println("LED OFF");
delay(1000); // 延迟 1 秒
}

2.2 基本函数

以下是 ESP32 Arduino 也支持的常用基础函数:

  • 数字 I/O
    • pinMode(pin, mode):设置引脚模式 (INPUT, OUTPUT, INPUT_PULLUP)。
    • digitalWrite(pin, value):向数字引脚写入高电平 (HIGH) 或低电平 (LOW)。
    • digitalRead(pin):读取数字引脚状态。
  • 模拟 I/O
    • analogRead(pin):从模拟引脚读取值(ESP32 ADC 默认 12 位)。
    • analogWrite(pin, value):ESP32 不直接支持 analogWrite(),而是通过 LED PWM (LEDC) 库来实现 PWM 输出。
  • 时间
    • delay(ms):暂停程序指定毫秒数。
    • delayMicroseconds(us):暂停程序指定微秒数。
    • millis():返回程序运行的毫秒数。
    • micros():返回程序运行的微秒数。
  • 串口通信
    • Serial.begin(baud_rate):初始化串口。
    • Serial.print() / Serial.println():发送数据到串口。
    • Serial.read():从串口读取单个字节。
    • Serial.available():查询串口缓冲区可用字节数。

三、ESP32 特有及增强的 Arduino API

ESP32 在 Arduino 核心中提供了丰富的功能,以利用其独特的硬件特性。

3.1 Wi-Fi 操作

这是 ESP32 最常用的功能之一。

  • WiFi.h:标准库,用于连接 Wi-Fi、创建 SoftAP、获取 IP 地址等。
  • 主要函数
    • WiFi.begin(ssid, password):连接到指定的 Wi-Fi 网络。
    • WiFi.status():返回当前 Wi-Fi 连接状态。
    • WiFi.localIP():获取设备的局域网 IP 地址。
    • WiFi.softAP(ssid, password):将 ESP32 配置为接入点 (SoftAP)。
    • WiFi.softAPIP():获取 SoftAP 模式下的 IP 地址。
    • WiFi.mode(mode):设置 Wi-Fi 模式 (WIFI_STA, WIFI_AP, WIFI_AP_STA)。
  • 网络客户端/服务器
    • WiFiClient client; 创建 TCP 客户端实例。
    • WiFiServer server(port); 创建 TCP 服务器实例。
    • WebClientWebServer 库:更高级的 HTTP/HTTPS 客户端和服务器。

示例 (ESP32 Wi-Fi 客户端):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <WiFi.h>

const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

void setup() {
Serial.begin(115200);
delay(10);

Serial.print("Connecting to WiFi: ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

Serial.println("\nWiFi connected.");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}

void loop() {
// 可以在这里进行网络通信,例如发送 HTTP 请求
}

3.2 蓝牙操作 (双模)

ESP32 支持经典蓝牙 (Bluetooth Classic) 和低功耗蓝牙 (BLE)。

  • BLE (低功耗蓝牙):使用 BluetoothSerial.h (简化用法) 或 BLEDevice.h 等库。
    • BLE 示例:通常涉及创建 BLE 服务器/客户端,定义服务 (Services) 和特性 (Characteristics)。
  • 经典蓝牙 (Bluetooth Classic):例如,用于串口透传 (SPP)。
    • BluetoothSerial.h:提供串口蓝牙功能。

示例 (ESP32 BLE GATT 服务器 - 模拟串口):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h> // 用于描述符

// BLE 服务 UUID
#define SERVICE_UUID "4fafc201-1fb5-459e-8a58-bc4a0a149666"
// BLE 特性 UUID (用于写入)
#define CHARACTERISTIC_UUID_RX "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// BLE 特性 UUID (用于通知/读取)
#define CHARACTERISTIC_UUID_TX "bab5483e-36e1-4688-b7f5-ea07361b26a8"

BLECharacteristic *pCharacteristicTX; // 用于发送数据到客户端
BLECharacteristic *pCharacteristicRX; // 用于接收客户端数据

bool deviceConnected = false;

// 定义 Callback 类用于处理连接事件
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("BLE Client Connected.");
};

void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("BLE Client Disconnected.");
}
};

// 定义 Callback 类用于处理特性写入事件
class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();

if (rxValue.length() > 0) {
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i]);
}
Serial.println();
}
}
};

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

// 初始化 BLE 设备
BLEDevice::init("ESP32 BLE Serial"); // 设置设备名称

// 创建 BLE 服务器
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());

// 创建 BLE 服务
BLEService *pService = pServer->createService(SERVICE_UUID);

// 创建 TX 特性 (用于通知/读取)
pCharacteristicTX = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristicTX->addDescriptor(new BLE2902()); // 添加客户端特性配置描述符
// 可以设置初始值
// pCharacteristicTX->setValue("Hello World");

// 创建 RX 特性 (用于写入)
pCharacteristicRX = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_WRITE_NR
);
pCharacteristicRX->setCallbacks(new MyCharacteristicCallbacks()); // 设置写入回调

// 启动服务
pService->start();

// 启动广播
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
Serial.println("BLE device waiting for connections...");
}

void loop() {
if (deviceConnected) {
// 假设每 2 秒发送一个心跳包
static unsigned long lastSendTime = 0;
if (millis() - lastSendTime > 2000) {
std::string txValue = "Heartbeat: " + String(millis()).c_str();
pCharacteristicTX->setValue(txValue); // 设置特性值
pCharacteristicTX->notify(); // 发送通知给已连接的客户端
Serial.print("Sent: ");
Serial.println(txValue.c_str());
lastSendTime = millis();
}
}
delay(10); // 避免 CPU 空转
}

3.3 多核编程 (FreeRTOS 任务)

ESP32 是双核微控制器,Arduino 核心提供了 xTaskCreatePinnedToCore() 等函数来利用这一特性。

  • TaskHandle_t:任务句柄类型。
  • xTaskCreatePinnedToCore(taskCode, name, stackDepth, parameters, priority, handle, coreID)
    • taskCode:任务函数指针。
    • name:任务名称 (字符串)。
    • stackDepth:任务栈大小 (字节)。
    • parameters:传递给任务函数的参数。
    • priority:任务优先级 (0-24,数字越大优先级越高)。
    • handle:任务句柄 (输出参数)。
    • coreID:指定任务运行的核心 (0: Pro_CPU, 1: App_CPU)。APP_CPU_NUMPRO_CPU_NUM 宏也可用。

示例 (在不同核心上运行任务):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 任务函数,运行在 Core 0
void taskOne(void * parameter) {
for (;;) { // 任务函数通常是一个无限循环
Serial.print("Task One running on core ");
Serial.println(xPortGetCoreID()); // 获取当前运行核心 ID
delay(1000);
}
}

// 任务函数,运行在 Core 1
void taskTwo(void * parameter) {
for (;;) {
Serial.print("Task Two running on core ");
Serial.println(xPortGetCoreID());
delay(1500);
}
}

void setup() {
Serial.begin(115200);
delay(100); // 确保串口初始化

// 创建 Task One,并将其固定到 Core 0
xTaskCreatePinnedToCore(
taskOne, // 任务函数
"Task_One", // 任务名称
10000, // 堆栈大小 (字节)
NULL, // 参数
1, // 优先级 (0-24)
NULL, // 任务句柄(不需要时设置为 NULL)
0 // 运行在 Core 0 (Pro_CPU)
);

// 创建 Task Two,并将其固定到 Core 1
xTaskCreatePinnedToCore(
taskTwo, // 任务函数
"Task_Two", // 任务名称
10000, // 堆栈大小 (字节)
NULL, // 参数
1, // 优先级
NULL, // 任务句柄
1 // 运行在 Core 1 (App_CPU)
);

// loop() 函数也可以被理解为一个 FreeRTOS 任务,
// 默认运行在 Core 1,优先级为 1。
// xTaskCreatePinnedToCore() 中推荐使用高于 0 的优先级。
}

void loop() {
// 主循环可以做一些其他任务,或者保持空闲
// Serial.print("Loop running on core ");
// Serial.println(xPortGetCoreID());
delay(2000);
}

3.4 触摸传感器

ESP32 内置了电容式触摸传感器。

  • touchRead(pin):读取触摸引脚的模拟值。
  • touchAttachInterrupt(pin, ISR, threshold):将中断附加到触摸引脚。

示例 (触摸按键检测):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const int TOUCH_PIN = T0; // GPIO4
const int TOUCH_THRESHOLD = 20; // 根据实际情况调整阈值
// 触摸时读数会下降,未触摸时读数较高

void setup() {
Serial.begin(115200);
delay(100);
Serial.println("ESP32 Touch Test");
// 触摸引脚不需要像 pinMode 那样额外设置
}

void loop() {
int touchValue = touchRead(TOUCH_PIN);
Serial.print("Touch value for T0 (GPIO4): ");
Serial.println(touchValue);

if (touchValue < TOUCH_THRESHOLD) {
Serial.println("TOUCH DETECTED!");
// 执行触摸后的操作
}
delay(500);
}

3.5 LED PWM (LEDC)

ESP32 使用专门的 LEDC (LED Control) 外设来生成 PWM 信号,而非 analogWrite()

  • ledcSetup(channel, freq, resolution_bits):配置 LEDC 通道。
    • channel:LEDC 通道 (0-15)。
    • freq:PWM 频率 (Hz)。
    • resolution_bits:PWM 分辨率 (例如 8 代表 0-255,10 代表 0-1023)。
  • ledcAttachPin(pin, channel):将引脚附加到 LEDC 通道。
  • ledcWrite(channel, duty_cycle):设置指定通道的占空比。

示例 (LED 呼吸灯):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const int LED_PIN = 2; // 连接 LED 的 GPIO 引脚
const int LEDC_CHANNEL = 0; // 使用 LEDC 通道 0
const int FREQUENCY = 5000; // PWM 频率 5 KHz
const int RESOLUTION = 8; // 8 位分辨率,占空比范围 0-255

void setup() {
Serial.begin(115200);
// 配置 LEDC 通道
ledcSetup(LEDC_CHANNEL, FREQUENCY, RESOLUTION);
// 将 LED 引脚附加到 LEDC 通道
ledcAttachPin(LED_PIN, LEDC_CHANNEL);
}

void loop() {
// 逐渐升高亮度
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
ledcWrite(LEDC_CHANNEL, dutyCycle);
delay(5);
}
// 逐渐降低亮度
for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) {
ledcWrite(LEDC_CHANNEL, dutyCycle);
delay(5);
}
}

3.6 深度睡眠与唤醒

ESP32 的低功耗特性在 Arduino 中也得到了支持。

  • esp_deep_sleep_start():进入深度睡眠模式。
  • esp_sleep_enable_timer_wakeup(time_in_us):通过定时器唤醒。
  • esp_sleep_enable_ext0_wakeup(gpio_num, level):通过外部引脚唤醒。

示例 (定时器唤醒):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <esp_sleep.h>

const int DEEP_SLEEP_SECONDS = 10; // 深度睡眠 10 秒

void setup() {
Serial.begin(115200);
delay(100);
Serial.println("Going to deep sleep in 3 seconds...");
delay(3000); // 3秒后进入深度睡眠

// 设置定时器唤醒,唤醒时间单位是微秒
esp_sleep_enable_timer_wakeup(DEEP_SLEEP_SECONDS * 1000000);

Serial.println("Entering deep sleep...");
Serial.flush(); // 确保所有串口数据发送完毕
esp_deep_sleep_start(); // 进入深度睡眠
}

void loop() {
// ESP32 进入深度睡眠后,loop() 不会再执行
}

3.7 文件系统 (SPIFFS / LittleFS)

ESP32 可以使用其内部 Flash 存储实现文件系统,常用于存储网页文件、配置文件、图像等。

  • SPIFFS.hLittleFS.h:提供文件系统操作接口。
  • 常用函数
    • SPIFFS.begin() / LittleFS.begin():挂载文件系统。
    • SPIFFS.exists(path) / LittleFS.exists(path):检查文件是否存在。
    • SPIFFS.open(path, mode) / LittleFS.open(path, mode):打开文件。
    • File.print() / File.println() / File.write():写入数据。
    • File.read() / File.available():读取数据。

示例 (写入并读取文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <FS.h> // Common file system abstraction
#include <LittleFS.h> // ESP32 preferred file system

void setup() {
Serial.begin(115200);
delay(100);

if (!LittleFS.begin(true)) { // true 参数表示如果失败则格式化
Serial.println("LittleFS Mount Failed");
return;
}
Serial.println("LittleFS Mounted Successfully");

// 1. 写入文件
File file = LittleFS.open("/hello.txt", "w"); // "w" 为写入模式
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
if (file.print("Hello from ESP32 using LittleFS!")) {
Serial.println("File written successfully");
} else {
Serial.println("Write failed");
}
file.close();

// 2. 读取文件
file = LittleFS.open("/hello.txt", "r"); // "r" 为读取模式
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
Serial.println("Reading file:");
while (file.available()) {
Serial.write(file.read());
}
file.close();

// 3. 删除文件
// if(LittleFS.remove("/hello.txt")){
// Serial.println("File deleted");
// } else {
// Serial.println("File delete failed");
// }
}

void loop() {
// Nothing to do here
}

注意LittleFSSPIFFS 的改进版本,在 ESP32 上更推荐使用 LittleFS。要使用它,您可能需要安装 ESP32 LittleFS 文件系统上传工具。

3.8 RTC (实时时钟)

ESP32 的 RTC (Real-Time Clock) 外设可以在深度睡眠时保持计时。

  • RTC_DATA_ATTR:标记变量,使其在深度睡眠后保留。
  • time.h:用于 NTP 时间同步等。

示例 (深度睡眠 RTC 记忆):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <Arduino.h>

// RTC_DATA_ATTR 关键字确保变量在深度睡眠后不丢失
RTC_DATA_ATTR int bootCount = 0;

void setup() {
Serial.begin(115200);
// 每次唤醒都会执行 setup()
bootCount++;
Serial.printf("Boot count: %d\n", bootCount);

// 如果启动次数达到 5 次,就停止深度睡眠测试,保持活跃
if (bootCount >= 5) {
Serial.println("Reached 5 boots, staying awake.");
// 清除 RTC 存储,以便下次启动时重新开始计数
bootCount = 0;
} else {
Serial.println("Entering deep sleep in 3 seconds...");
delay(3000);
esp_sleep_enable_timer_wakeup(5 * 1000000); // 5秒后唤醒
Serial.println("Entering deep sleep...");
Serial.flush();
esp_deep_sleep_start();
}
}

void loop() {
// 当 ESP32 不再进入深度睡眠时,loop() 将持续执行
delay(1000);
Serial.println("Still awake...");
}

四、开发流程与工具链

4.1 Arduino IDE 设置

  1. 安装 Arduino IDE
  2. 添加 ESP32 板卡管理器 URL:在 文件 -> 首选项 -> 附加开发板管理器网址 中添加 https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. 安装 ESP32 板卡:在 工具 -> 开发板 -> 开发板管理器 中搜索 ESP32 并安装。
  4. 选择开发板和端口:在 工具 -> 开发板 中选择您的 ESP32 开发板(如 ESP32 Dev Module),在 工具 -> 端口 中选择正确的串口。

4.2 PlatformIO 使用

推荐使用 VS Code 配合 PlatformIO 插件。

  1. 安装 VS Code
  2. 安装 PlatformIO IDE 插件
  3. 新建项目:选择 ESP32 Dev Module,框架选择 Arduino
  4. 编辑 platformio.ini:配置编译选项、库依赖等。

示例 platformio.ini

1
2
3
4
5
6
7
8
9
10
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
# 如果有其他库依赖可以在这里添加,例如
# Adafruit Unified Sensor
# AsyncTCP
# ESP Async WebServer

五、安全性考虑 (Arduino 视角)

在使用 Arduino 语法开发 ESP32 时,也应牢记安全性:

  1. Wi-Fi 凭据安全:不要在代码中硬编码敏感的 Wi-Fi SSID 和密码并公开。考虑使用 WiFiManager 等库,或者通过文件系统存储。
  2. OTA (Over-The-Air) 更新:ESP32 支持通过 Wi-Fi 进行固件更新。务必启用 OTA 的身份验证和加密,防止恶意固件上传。
  3. 数据传输加密:对于敏感数据,通过 HTTPS/TLS 进行传输而非普通 HTTP。Arduino 核心支持 WiFiClientSecure
  4. 硬件安全特性:Arduino 核心也间接支持 ESP32 的硬件安全特性,如 Secure Boot 和 Flash Encryption。这些通常在烧录工具或高级配置中设置,而非 Arduino Sketch 内部。
  5. 内存管理:在多任务和网络密集型应用中,注意内存泄漏和堆栈溢出,尤其是使用 String 对象和动态内存分配。
  6. 错误处理:对网络连接、文件操作等可能失败的函数进行充分的错误检查。

六、总结

ESP32 结合 Arduino 语法,为物联网开发提供了一个强大而便捷的平台。它不仅继承了 Arduino 简单易学的优点,还充分发挥了 ESP32 芯片在 Wi-Fi、蓝牙和多核处理方面的优势。无论是初学者进行快速原型开发,还是经验丰富的工程师构建复杂的互联系统,ESP32 Arduino 都是一个极具吸引力的选择。深入理解这些特有和增强的 API,将帮助开发者更高效、更稳定地实现各种物联网创新。