ESP32-C2 摘要
2025-10-28
ESP32-C2 简介
- Chip: ESP32-C2, ESP8684H2
- Module Size:15mm17.3mm3mm
- 2MByte embedded flash
- 576KB ROM
- 272KB SRAM (16 KB CACHE)
启动流程
- 固化在ROM引导加载程序,从FLASH的0x0地址加载二级引导(bootloader)到RAM中。
- 二级引导(bootloader)从FLASH加载分区表和主程序并运行主程序。
- RTOS调度器启动,直到
app_main()被调用。
分区表
- 分区表地址: FLASH偏移0x8000处 占据了整个 flash 扇区,大小为 0x1000 (4 KB)
- 分区表字段:Name(标签)、Type(app、data 等)、SubType 以及在 flash 中的偏移量(分区的加载地址)。
分区表参数
- Name:任何有意义的名称,但不能超过 16 个字节,其中包括一个空字节
- Type: 指定分区的类型
app (0x00)偏移地址必须与 0x10000 (64 KB) 对齐, 大小必须与 flash 扇区大小对齐 0x1000 (4 KB) 的倍数data (0x01)偏移地址必须与 0x1000 (4 KB) 对齐, 大小必须与 flash 扇区大小对齐 0x1000 (4 KB) 的倍数
- SubType:指定分区的子类型
app factory (0x00)引导加载程序默认加载该应用程序app ota_0 (0x10)第一个 OTA 应用程序分区app ota_1 (0x11)第二个 OTA 应用程序app test (0x20)测试应用程序分区data ota (0x00)OTA 数据分区、 大小需要设定为 0x2000 (8 KB)data phy (0x01): 用于存放 PHY 初始化数据data nvs (0x02):存储每台设备的 PHY 校准数据 Wi-Fi 数据 至少 0x3000 字节, 默认是 0x6000 字节data coredump (0x03): 用于存储 core dump 数据data nvs_keys (0x04): 用于存储加密密钥和其他安全相关数据 至少设定为 4096 字节data efuse (0x05): 使用 虚拟 eFuse 模拟 eFuse 位。data undefined (0x06): 隐式用于未指定子类型(即子类型为空)的数据分区,但也可显式将其标记为未定义。data fat (0x81): 用于 FAT 文件系统。data spiffs (0x82): 用于 SPIFFS 文件系统。data littlefs (0x83): 用于 LittleFS 文件系统data 0x40-0xfe: 自定义数据分区类型
- Offset:分区在 flash 中的起始地址,必须是 0x1000 (4 KB) 的倍数
- Size:分区的大小,必须是 0x1000 (4 KB)
- Flags:可选字段,通常为空
示例
Single factory app, no OTA
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,Factory app, two OTA definitions
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
ota_0, app, ota_0, 0x110000, 1M,
ota_1, app, ota_1, 0x210000, 1M,非易失性存储 (NVS)
非易失性存储 (NVS) 主要用于在 flash 中存储键值格式的数据
- 每1MB的NVS flash分区会占用22KB的RAM,每1000个键会占用5.5 KB的RAM 近似值
- 每有 1000 个键,NVS 的初始化
nvs_flash_init()时间则增加约 0.5 秒。 - 最大键长度为 15 字节
键值对类型
- 以 0 结尾的字符串
- 整数型:uint8_t、int8_t、uint16_t、int16_t、uint32_t、int32_t、uint64_t 和 int64_t
- 可变长度的二进制数据 (BLOB)
事件循环
事件循环库使组件能够声明事件,允许其他组件注册处理程序(即在事件发生时执行的代码片段)
以下为事件循环库的使用流程:
- 定义一个函数,并在事件发布到事件循环中时运行该函数。此函数被称为事件处理程序,应具有与
esp_event_handler_t同类型的签名。 - 调用
esp_event_loop_create()创建事件循环,该函数输出类型为esp_event_loop_handle_t的循环句柄,使用此 API 创建的事件循环称为用户事件循环。另有一种特殊事件循环,请参阅 默认事件循环。 - 调用
esp_event_handler_register_with()将事件处理程序注册到循环中。处理程序可以注册到多个循环中,请参阅 注册处理程序注意事项。 - 事件源调用
esp_event_post_to()将事件发布到事件循环中。 - 调用
esp_event_handler_unregister_with(),组件可以在事件循环中取消注册事件处理程序。 - 调用
esp_event_loop_delete()删除不再需要的事件循环。
默认事件循环
默认事件循环是一种特殊循环,用于处理系统事件(如 Wi-Fi 事件)。用户无法使用该循环的句柄,创建、删除、注册/注销处理程序以及事件发布均通过用户事件循环 API 的变体完成,下表列出了这些变体及其对应用户事件循环。
| 用户事件循环 API | 默认事件循环 API |
|---|---|
esp_event_loop_create() | esp_event_loop_create_default() |
esp_event_loop_delete() | esp_event_loop_delete_default() |
esp_event_handler_register_with() | esp_event_handler_register() |
esp_event_handler_unregister_with() | esp_event_handler_unregister() |
esp_event_post_to() | esp_event_post() |
IDF FREERTOS
IDF FreeRTOS 源代码基于 Vanilla FreeRTOS v10.5.1,但内核行为和 API 都有重大修改,以支持双核 SMP。
WIFI 驱动
Wi-Fi 驱动程序可以看作是一个无法感知上层代码(如 TCP/IP 堆栈、应用程序任务、事件任务等)的黑匣子。通常,应用程序任务(代码)负责调用 Wi-Fi 驱动程序 APIs 来初始化 Wi-Fi,并在必要时处理 Wi-Fi 事件。然后,Wi-Fi 驱动程序接收并处理 API 数据,并在应用程序中插入事件。
Wi-Fi 事件处理是在 esp_event 库 的基础上进行的。Wi-Fi 驱动程序将事件发送至 默认事件循环,应用程序便可以使用 esp_event_handler_register() 中的回调函数处理这些事件。除此之外,esp_netif 组件 也负责处理 Wi-Fi 事件,并产生一系列默认行为。例如,当 Wi-Fi station 连接至一个 AP 时,esp_netif 将自动开启 DHCP 客户端服务(系统默认)。
Wi-Fi 事件场景
station 模式
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT)
{
switch (event_id)
{
// s3.2 接收 WIFI_EVENT_STA_START 事件
case WIFI_EVENT_STA_START:
// s3.3
{
// s4.1 连接到配置的 AP
esp_wifi_connect();
}
break;
// s4.2 接收 WIFI_EVENT_STA_CONNECTED 事件
case WIFI_EVENT_STA_CONNECTED:
// s4.3
{
}
// s5.1 启动DHCP 客户端
break;
// s6.1 接收 WIFI_EVENT_STA_DISCONNECTED 事件
case WIFI_EVENT_STA_DISCONNECTED:
// s6.2
{
// s6.3
}
break;
default:
break;
}
}
else if (event_base == IP_EVENT)
{
switch (event_id)
{
// s5.2 s7.1 接收 IP_EVENT_STA_GOT_IP 事件
case IP_EVENT_STA_GOT_IP:
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
// 6.2
if(event->ip_changed) {
// 6.3
}
// s5.3
else {
// s5.4
}
// TODO: 你的应用代码
{
// s8.1
esp_wifi_disconnect();
// s8.2
esp_wifi_stop();
// s8.3
esp_wifi_deinit();
}
break;
default:
break;
}
}
}
void app_main(void)
{
// s1.1 创建一个 LwIP 核心任务,并初始化 LwIP 相关工作。
ESP_ERROR_CHECK(esp_netif_init());
// s1.2 创建一个系统事件任务,并初始化应用程序事件的回调函数,将事件中继到应用程序。
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
// s1.3 创建有 TCP/IP 堆栈的默认网络接口实例绑定 station
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// s1.4 注册 Wi-Fi 事件处理程序
// s2 配置wifi
wifi_config_t wifi_config = {
.sta = {
.ssid = "xx",
.password = "xxxxxxxx",
.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
// s3.1 启动 Wi-Fi 驱动程序
ESP_ERROR_CHECK(esp_wifi_start());
}softap 模式
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
//s3.2
if(event_id == WIFI_EVENT_AP_START) {
//s3.3
ESP_LOGI(TAG, "wifi ap start");
}
//s4.1
else if (event_id == WIFI_EVENT_AP_STACONNECTED) {
//s4.2
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
//s5.1
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
//s5.2
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d, reason=%d",
MAC2STR(event->mac), event->aid, event->reason);
//s5.3
//s6.1
esp_wifi_disconnect();
//s6.2
esp_wifi_stop();
//s6.2
esp_wifi_deinit();
}
}
void app_main(void)
{
// s1.1 创建一个 LwIP 核心任务,并初始化 LwIP 相关工作。
ESP_ERROR_CHECK(esp_netif_init());
// s1.2 创建一个系统事件任务,并初始化应用程序事件的回调函数,将事件中继到应用程序。
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));
// s1.3 创建有 TCP/IP 堆栈的默认网络接口实例绑定 ap
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// s1.4 注册 Wi-Fi 事件处理程序
// s2 配置wifi
wifi_config_t wifi_config = {
.ap = {
.ssid = "esp32-c2",
.password = "esp32-c2",
.ssid_len = 8,
.channel = 1,
.max_connection = 2,
#ifdef CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT
.authmode = WIFI_AUTH_WPA3_PSK,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
#else /* CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT */
.authmode = WIFI_AUTH_WPA2_PSK,
#endif
.pmf_cfg = {
.required = true,
},
},
};
// if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
// wifi_config.ap.authmode = WIFI_AUTH_OPEN;
// }
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
// s3.1 启动 Wi-Fi 驱动程序
ESP_ERROR_CHECK(esp_wifi_start());
}softap_station 模式
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d, reason=%d",
MAC2STR(event->mac), event->aid, event->reason);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
// ESP_LOGI(vTaskGenericNotifyGiveFromISR, "Station started");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
}
}
void app_main(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* Register Event handler */
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL,
NULL));
/*Initialize WiFi */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
/* Initialize AP */
ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");
esp_netif_t *esp_netif_ap = esp_netif_create_default_wifi_ap();
wifi_config_t wifi_ap_config = {
.ap = {
.ssid = "esp32-c2",
.ssid_len = 8,
.channel = 1,
.max_connection = 2,
.authmode = WIFI_AUTH_OPEN,
.pmf_cfg = {
.required = true,
},
},
};
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config));
ESP_LOGI(TAG, "wifi_init_softap finished.");
esp_netif_t *esp_netif_sta = esp_netif_create_default_wifi_sta();
wifi_config_t wifi_sta_config = {
.sta = {
.ssid = "xx",
.password = "xxxxxxxx",
.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config) );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Start WiFi */
ESP_ERROR_CHECK(esp_wifi_start());
}httpd 服务器
/* URI 处理函数,在客户端发起 GET /uri 请求时被调用 */
esp_err_t hi_handler(httpd_req_t *req)
{
/* 发送简单的响应数据包 */
const char resp[] = "<html><head><meta charset='utf-8'><title>hi</title></head><body>"
"<h1>hi</h1>"
"<a href=\"/\">返回</a>"
"</body></html>";
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
httpd_uri_t hi_get = {
.uri = "/",
.method = HTTP_GET,
.handler = hi_handler,
.user_ctx = NULL,
};
/* URI 处理函数,在客户端发起 POST/uri 请求时被调用 */
esp_err_t hello_handler(httpd_req_t *req)
{
char content[100] = {0};
size_t recv_size = MIN(req->content_len, sizeof(content));
ESP_LOGI(TAG, "recv_size %d. req->content_len %d", recv_size, req->content_len);
int ret = httpd_req_recv(req, content, recv_size);
ESP_LOGI(TAG, "ret %d %s", ret, content);
/* 发送简单的响应数据包 */
const char resp[] = "<html><head><meta charset='utf-8'><title>hi</title></head><body>"
"<h1>hi</h1>"
"<a href=\"/\">返回</a>"
"</body></html>";
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
httpd_uri_t hello_post = {
.uri = "/",
.method = HTTP_POST,
.handler = hello_handler,
.user_ctx = NULL,
};
void httpd_init(EventGroupHandle_t event_group) {
if(server != NULL)
return;
s_event_group = event_group;
/* 生成默认的配置参数 */
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
/* 启动 httpd server */
if (httpd_start(&server, &config) == ESP_OK)
{
/* 注册 URI 处理程序 */
httpd_register_uri_handler(server, &hi_get);
httpd_register_uri_handler(server, &hello_post);
}
}