一、GPIO外设API

(一)核心函数及说明

1. 中断相关函数

  • gpio_install_isr_service:创建全局GPIO中断服务,统一管理所有GPIO中断,适用于简单场景。

  • gpio_isr_handler_add:为指定GPIO引脚添加中断处理函数,绑定到全局中断服务,推荐简单场景使用。

  • gpio_isr_register:注册自定义全局中断处理函数,需手动管理各引脚中断逻辑,适用于复杂场景。

2. 睡眠模式相关函数

  • gpio_sleep_sel_en(gpio_num_t gpio_num):使能GPIO在Light-sleep模式下的功能,避免复位为默认状态,维持必要的输入/输出。

  • gpio_sleep_set_direction(gpio_num_t gpio_num, gpio_mode_t mode):单独配置GPIO在睡眠模式下的输入/输出方向,与正常模式独立。

  • gpio_deep_sleep_hold_en(void):在Deep-sleep模式下保持所有GPIO状态,解决传统型号唤醒后状态丢失问题(ESP32-S3新增)。

3. 状态保持函数

  • gpio_hold_en(gpio_num_t gpio_num):启用单个GPIO的状态保持,锁存当前电平及配置,后续修改不生效。

  • gpio_force_hold_all(void):强制所有支持的GPIO进入保持状态,适用于系统复位前保存关键状态。

4. 引脚复用函数

  • gpio_iomux_in(uint32_t gpio_num, uint32_t signal_idx):将内部外设信号映射到指定GPIO引脚(输入方向),实现引脚复用(如UART接收、SPI输入)。

  • gpio_iomux_out(uint32_t gpio_num, uint32_t signal_idx, bool inv, bool oen):将内部外设信号映射到指定GPIO引脚(输出方向),支持反相输出和输出使能控制。

(二)中断注册示例代码

esp_err_t err;
gpio_isr_handle_t isr_handle = NULL;

// 全局GPIO中断服务初始化(中断优先级1,不自动清除中断标志)
err = gpio_install_isr_service(1, 0, &isr_handle);
if (err != ESP_OK) {
    ESP_LOGE(TAG, "中断注册失败: %d", err);
    return;
}

ESP_LOGI(TAG, "全局GPIO中断注册成功");
// 后续可通过 isr_handle 卸载中断(如需)
// gpio_isr_unregister(isr_handle);

关键说明

简单场景推荐使用 gpio_install_isr_service + gpio_isr_handler_add,复杂场景可考虑 gpio_isr_register,根据实际需求选择中断管理方式。

二、UART外设API

(一)核心功能

实现“串口数据收发”“硬件参数配置”“事件/中断管理”,具体包括:初始化UART硬件(引脚、波特率、数据格式等);发送/接收数据(支持阻塞/非阻塞模式);管理通信事件;配置特殊功能(硬件流控、DMA传输等)。

(二)常用API及作用

1. 初始化相关API(核心)

  • uart_param_config:设置UART波特率、数据位、校验位、停止位等核心参数。

  • uart_set_pin:绑定UART的TX、RX引脚,可配置流控引脚(如需)。

  • uart_driver_install:启动UART驱动,分配收发缓冲区,创建事件队列(用于事件驱动模式)。

2. 数据收发相关API

  • uart_write_bytes:发送数据(支持阻塞模式,可指定超时时间)。

  • uart_read_bytes:读取数据(支持阻塞/非阻塞模式,可指定读取长度和超时时间)。

3. 事件与中断相关API

  • uart_event_get:从事件队列中获取UART事件(如数据接收完成、缓冲区满、错误等)。

  • uart_pattern_det_config:配置模式检测(如检测指定连续字符),用于触发特定逻辑。

4. 辅助控制API

  • uart_flush:清空收发缓冲区,重置UART状态。

  • uart_driver_delete:释放UART驱动占用的内存和硬件资源。

(三)典型使用流程

1. 配置参数:uart_param_config → uart_set_pin; 2. 启动驱动:uart_driver_install; 3. (可选)配置特殊功能:uart_pattern_det_config; 4. 数据通信:uart_write_bytes(发送)、uart_event_get + uart_read_bytes(接收); 5. 异常处理:通过事件检测错误,调用uart_flush重置。

(四)核心函数详解:uart_driver_install

1. 函数原型

esp_err_t uart_driver_install(uart_port_t uart_num,
                             int rx_buffer_size,
                             int tx_buffer_size,
                             int queue_size,
                             QueueHandle_t *uart_queue,
                             int intr_alloc_flags);

2. 参数说明

  • uart_num:目标UART端口(如UART_NUM_0、UART_NUM_1)。

  • rx_buffer_size:接收缓冲区大小(字节),建议1024~4096字节。

  • tx_buffer_size:发送缓冲区大小(字节),可设0(无缓冲区)。

  • queue_size:事件队列长度,需大于0(无需事件可设0,uart_queue传NULL)。

  • uart_queue:事件队列句柄,用于接收UART事件。

  • intr_alloc_flags:中断分配标志,0为默认配置。

3. 返回值

ESP_OK(成功);ESP_ERR_NO_MEM(内存不足);ESP_ERR_INVALID_ARG(参数无效)。

4. 使用注意事项

需在uart_param_config和uart_set_pin之后调用;缓冲区大小需匹配通信频率,避免溢出或内存浪费;程序退出前需调用uart_driver_delete释放资源。

(五)完整示例代码

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "esp_log.h"

static const char *TAG = "uart_events";

#define EX_UART_NUM UART_NUM_0
#define PATTERN_CHR_NUM    (3)         
#define BUF_SIZE (1024)
#define RD_BUF_SIZE (BUF_SIZE)
static QueueHandle_t uart0_queue;

static void uart_event_task(void *pvParameters)
{
    uart_event_t event;
    size_t buffered_size;
    uint8_t* dtmp = (uint8_t*) malloc(RD_BUF_SIZE);
    for (;;) {
        if (xQueueReceive(uart0_queue, (void *)&event, (TickType_t)portMAX_DELAY)) {
            bzero(dtmp, RD_BUF_SIZE);
            ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM);
            switch (event.type) {
            case UART_DATA:
                ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
                uart_read_bytes(EX_UART_NUM, dtmp, event.size, portMAX_DELAY);
                ESP_LOGI(TAG, "[DATA EVT]:");
                uart_write_bytes(EX_UART_NUM, (const char*) dtmp, event.size);
                break;
            case UART_FIFO_OVF:
                ESP_LOGI(TAG, "hw fifo overflow");
                uart_flush_input(EX_UART_NUM);
                xQueueReset(uart0_queue);
                break;
            case UART_BUFFER_FULL:
                ESP_LOGI(TAG, "ring buffer full");
                uart_flush_input(EX_UART_NUM);
                xQueueReset(uart0_queue);
                break;
            case UART_BREAK:
                ESP_LOGI(TAG, "uart rx break");
                break;
            case UART_PARITY_ERR:
                ESP_LOGI(TAG, "uart parity error");
                break;
            case UART_FRAME_ERR:
                ESP_LOGI(TAG, "uart frame error");
                break;
            case UART_PATTERN_DET:
                uart_get_buffered_data_len(EX_UART_NUM, &buffered_size);
                int pos = uart_pattern_pop_pos(EX_UART_NUM);
                ESP_LOGI(TAG, "[UART PATTERN DETECTED] pos: %d, buffered size: %d", pos, buffered_size);
                if (pos == -1) {
                    uart_flush_input(EX_UART_NUM);
                } else {
                    uart_read_bytes(EX_UART_NUM, dtmp, pos, 100 / portTICK_PERIOD_MS);
                    uint8_t pat[PATTERN_CHR_NUM + 1];
                    memset(pat, 0, sizeof(pat));
                    uart_read_bytes(EX_UART_NUM, pat, PATTERN_CHR_NUM, 100 / portTICK_PERIOD_MS);
                    ESP_LOGI(TAG, "read data: %s", dtmp);
                    ESP_LOGI(TAG, "read pat : %s", pat);
                }
                break;
            default:
                ESP_LOGI(TAG, "uart event type: %d", event.type);
                break;
            }
        }
    }
    free(dtmp);
    dtmp = NULL;
    vTaskDelete(NULL);
}

void app_main(void)
{
    esp_log_level_set(TAG, ESP_LOG_INFO);

    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };
    uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0);
    uart_param_config(EX_UART_NUM, &uart_config);

    uart_set_pin(EX_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    uart_enable_pattern_det_baud_intr(EX_UART_NUM, '+', PATTERN_CHR_NUM, 9, 0, 0);
    uart_pattern_queue_reset(EX_UART_NUM, 20);

    xTaskCreate(uart_event_task, "uart_event_task", 3072, NULL, 12, NULL);
}

代码逻辑梳理

1. 头文件引入:加载串口、FreeRTOS、日志等所需工具; 2. 常量定义:配置UART端口、缓冲区大小、模式检测参数; 3. uart_event_task:循环接收UART事件,根据事件类型处理(数据回显、缓冲区清空等); 4. app_main:配置UART参数、安装驱动、启动事件处理任务,完成串口初始化和通信准备。

三、ADC外设API

(一)ADC单次转换API

1. 核心函数及说明

  • adc_oneshot_new_unit:创建ADC单次转换单元(绑定ADC1或ADC2),初始化硬件资源,返回单元句柄。

  • adc_oneshot_config_channel:配置指定ADC通道的衰减(量程)和分辨率,需传入单元句柄。

  • adc_cali_create_scheme_curve_fitting:创建曲线拟合校准句柄(利用内置eFuse数据,精度最高)。

  • adc_cali_create_scheme_line_fitting:备选校准方式,线性拟合,精度略低,兼容性更广。

  • adc_oneshot_read:触发一次ADC转换,读取指定通道的原始值(未校准)。

  • adc_cali_raw_to_voltage:将ADC原始值转换为校准后的电压值(单位:mV)。

  • adc_oneshot_del_unit:销毁ADC单次转换单元,释放资源。

  • adc_cali_delete:销毁校准句柄,释放校准相关资源。

2. 关键注意事项

参数需一致(unit_id、atten、bitwidth在各函数中保持统一);需处理错误码,便于调试;程序退出前需销毁句柄,避免内存泄漏;ESP32-S3推荐使用ADC1,抗干扰性更强。

3. 完整示例代码

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "test_uart.h"

// 宏定义配置
#define ADC_UNIT ADC_UNIT_1
#define ADC_CHANNEL ADC_CHANNEL_3
#define ADC_ATTEN ADC_ATTEN_DB_12
#define ADC_BITWIDTH ADC_BITWIDTH_12
#define QUEUELENGTH 10
#define QUEUESIZE sizeof(uint32_t)

// 全局变量
QueueHandle_t queuedata_handle = NULL;
adc_oneshot_unit_handle_t adc_oneshot_handle = NULL;
adc_cali_handle_t adc_cali_handle = NULL;
TaskHandle_t adc_read_handle = NULL;
bool calibrated_enable = false;
const char *TAG = "ADC_TASK";
int conversion_temp = 0;
int voltage = 0;

// ADC单次转换初始化
void adc_config_init()
{
    adc_oneshot_unit_init_cfg_t init_cfg = {
        .unit_id = ADC_UNIT,
        .ulp_mode = ADC_ULP_MODE_DISABLE,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_cfg, &adc_oneshot_handle));

    adc_oneshot_chan_cfg_t chan_cfg = {
        .atten = ADC_ATTEN,
        .bitwidth = ADC_BITWIDTH,
    };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_oneshot_handle, ADC_CHANNEL, &chan_cfg));
}

// ADC校准初始化(曲线拟合)
bool adc_calibration_init(adc_atten_t atten, adc_bitwidth_t bitwidth, adc_channel_t chan, adc_unit_t unit_id)
{
    esp_err_t ret;
    bool calibrated = false;

    adc_cali_curve_fitting_config_t config_init = {
        .atten = atten,
        .bitwidth = bitwidth,
        .chan = chan,
        .unit_id = unit_id,
    };

    ret = adc_cali_create_scheme_curve_fitting(&config_init, &adc_cali_handle);
    if (ret == ESP_OK)
    {
        calibrated = true;
        ESP_LOGI(TAG, "曲线拟合成功");
    }
    else
    {
        ESP_LOGI(TAG, "错误原因看错误码:0x%x", ret);
    }

    return calibrated;
}

// ADC读取任务
void ADC_read_Task(void *paramete)
{
    esp_err_t ret;
    while (1)
    {
        ret = adc_oneshot_read(adc_oneshot_handle, ADC_CHANNEL, &conversion_temp);
        if (ret == ESP_OK)
        {
            ESP_LOGI(TAG, "原始值:%d(范围:0~%d)", conversion_temp, (1 << ADC_BITWIDTH) - 1);
            if (calibrated_enable)
            {
                ret = adc_cali_raw_to_voltage(adc_cali_handle, conversion_temp, &voltage);
                if (ret == ESP_OK)
                {
                    ESP_LOGI(TAG, "校准后电压:%d mV", voltage);
                    xQueueSend(queuedata_handle, &voltage, portMAX_DELAY);
                }
                else
                {
                    ESP_LOGE(TAG, "电压转换失败(错误码:0x%x)", ret);
                }
            }
        }
        else
        {
            ESP_LOGE(TAG, "采样失败(错误码:0x%x)", ret);
        }
        vTaskDelay(pdMS_TO_TICKS(500));
    }
    vTaskDelete(NULL);
}

// UART发送任务
void uart_send_task(void *parame)
{
    int len = 0;
    char str[20] = "error";
    int voltage_temp;
    while (1)
    {
        if (xQueueReceive(queuedata_handle, &voltage_temp, 0) == pdPASS)
        {
            sprintf(str, "ADC的电压是:%d", voltage_temp);
            len = strlen(str);
            uart_write_bytes(UART_NUM, str, len);
        }
        else
        {
            uart_write_bytes(UART_NUM, str, strlen(str));
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 程序入口
void app_main(void)
{
    // 创建消息队列
    queuedata_handle = xQueueCreate(QUEUELENGTH, QUEUESIZE);
    if (queuedata_handle == NULL)
    {
        ESP_LOGI(TAG, "队列创建失败");
        return;
    }

    // 初始化ADC和UART
    adc_config_init();
    uart_init();

    // 初始化校准
    calibrated_enable = adc_calibration_init(ADC_ATTEN, ADC_BITWIDTH, ADC_CHANNEL, ADC_UNIT);

    // 创建任务
    xTaskCreatePinnedToCore(ADC_read_Task, "ADC_read_Task", 4096, NULL, 5, &adc_read_handle, 1);
    xTaskCreatePinnedToCore(uart_send_task, "uart_send_task", 4096, NULL, 4, NULL, 1);
}

(二)ADC连续转换API

1. 核心开发步骤

1. 配置ADC单元参数:创建连续转换单元,指定单元、分辨率、采样频率; 2. 配置采集通道:指定启用的通道、衰减值,绑定到ADC单元; 3. 启动连续转换:调用adc_continuous_start启动采集; 4. 循环读取结果:通过adc_continuous_read读取缓冲区数据,解析通道原始值并转换为电压; 5. 停止转换并释放资源:程序退出前调用adc_continuous_stop和adc_continuous_del_unit。

2. 关键注意事项

  • 通道与引脚对应:参考ESP32-S3数据手册,确认ADC通道对应的GPIO(如ADC1_CH0对应GPIO4)。

  • 衰减与量程:ADC_ATTEN_DB_11对应0~3.3V(最常用),需配合校准提升精度。

  • ADC2冲突:使用Wi-Fi时,优先使用ADC1,避免ADC2被Wi-Fi占用。

  • 采样频率:最高约200kHz,需根据需求调整,避免数据溢出。

3. 完整示例代码

#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "esp_adc/adc_continuous.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "freertos/FreeRTOS.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#define ADC_UNIT ADC_UNIT_1
#define ADC_CONV_MODE ADC_CONV_SINGLE_UNIT_1
#define ADC_FORMAT ADC_DIGI_OUTPUT_FORMAT_TYPE2
#define ADC_SAMPLE_FREQ_HZ 5000
#define ADC_PATTERN_NUM 2
#define ADC_BIT_WIDTH ADC_BITWIDTH_12
#define ADC_ATTEN ADC_ATTEN_DB_12
#define READ_BUFFER_SIZE 256

static adc_cali_handle_t adc1_cali_chan0 = NULL;
static adc_cali_handle_t adc1_cali_chan1 = NULL;
adc_continuous_handle_t adc_handle = NULL;
SemaphoreHandle_t semaphore_handle = NULL;

// ADC校准初始化
static bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_cali_handle_t *out_handle)
{
    adc_cali_handle_t handle = NULL;
    esp_err_t ret = ESP_FAIL;
    bool cali_enable = false;

    adc_cali_line_fitting_config_t cali_config = {
        .unit_id = unit,
        .channel = channel,
        .atten = ADC_ATTEN,
        .bitwidth = ADC_BIT_WIDTH,
    };

    ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
    if (ret == ESP_OK)
    {
        cali_enable = true;
        *out_handle = handle;
    }
    return cali_enable;
}

// ADC连续转换初始化
static void adc_continuous_init(void)
{
    adc_continuous_unit_config_t unit_config = {
        .unit_id = ADC_UNIT,
        .clk_src = ADC_CLK_SRC_DEFAULT,
        .resolution = ADC_BIT_WIDTH,
        .sample_freq_hz = ADC_SAMPLE_FREQ_HZ,
    };
    ESP_ERROR_CHECK(adc_continuous_new_unit(&unit_config, &adc_handle));

    adc_continuous_chan_cfg_t chan_config = {
        .bitmask = BIT(ADC_CHANNEL_0) | BIT(ADC_CHANNEL_1),
        .atten = ADC_ATTEN,
    };
    ESP_ERROR_CHECK(adc_continuous_config_channel(adc_handle, &chan_config));

    // 初始化校准
    adc_calibration_init(ADC_UNIT, ADC_CHANNEL_0, &adc1_cali_chan0);
    adc_calibration_init(ADC_UNIT, ADC_CHANNEL_1, &adc1_cali_chan1);
}

// ADC读取任务
static void adc_continuous_read_task(void *param)
{
    adc_continuous_read_result_t read_result;
    while (1)
    {
        esp_err_t ret = adc_continuous_read(adc_handle, &read_result, 100);
        if (ret == ESP_OK)
        {
            for (int i = 0; i < read_result.num_samples; i++)
            {
                uint8_t chan = ADC_CON_RESULT_GET_CHAN(read_result.data[i]);
                uint16_t raw = ADC_CON_RESULT_GET_VALUE(read_result.data[i]);
                int voltage = 0;
                if (chan == ADC_CHANNEL_0 && adc1_cali_chan0 != NULL)
                {
                    adc_cali_raw_to_voltage(adc1_cali_chan0, raw, &voltage);
                }
                else if (chan == ADC_CHANNEL_1 && adc1_cali_chan1 != NULL)
                {
                    adc_cali_raw_to_voltage(adc1_cali_chan1, raw, &voltage);
                }
                ESP_LOGI("ADC_CONT", "Channel: %d, Raw: %d, Voltage: %d mV", chan, raw, voltage);
            }
        }
        else if (ret == ESP_ERR_TIMEOUT)
        {
            continue;
        }
        else
        {
            ESP_LOGE("ADC_CONT", "Read error: %s", esp_err_to_name(ret));
            break;
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void app_main(void)
{
    adc_continuous_init();
    ESP_ERROR_CHECK(adc_continuous_start(adc_handle));
    xTaskCreate(adc_continuous_read_task, "adc_cont_read_task", 4096, NULL, 5, NULL);
}

四、定时器(GPTimer)API

(一)核心模式及回调函数

1. 三种核心模式

  • 单次触发模式:触发后自动停止,适合一次性定时任务。

  • 自动重载模式:固定周期重复触发,适合周期性任务。

  • 动态调整周期模式:每次触发后修改下一次周期,适合灵活定时场景。

2. 核心回调函数

// 回调函数版本1:单次触发模式(触发后停止)
static bool IRAM_ATTR timer_callback_v1(gptimer_handle_t timer,
                                     const gptimer_alarm_event_data_t *edata,
                                     void *user_data) {
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_data;
    timer_event_t event = { .event_count = edata->count_value };
    xQueueSendFromISR(queue, &event, &high_task_awoken);
    gptimer_stop(timer); // 触发后停止定时器
    return (high_task_awoken == pdTRUE);
}

// 回调函数版本2:自动重载模式(固定周期)
static bool IRAM_ATTR timer_callback_v2(gptimer_handle_t timer,
                                     const gptimer_alarm_event_data_t *edata,
                                     void *user_data) {
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_data;
    timer_event_t event = { .event_count = edata->count_value };
    xQueueSendFromISR(queue, &event, &high_task_awoken);
    return (high_task_awoken == pdTRUE);
}

// 回调函数版本3:动态调整周期模式(每次周期+1秒)
static bool IRAM_ATTR timer_callback_v3(gptimer_handle_t timer,
                                     const gptimer_alarm_event_data_t *edata,
                                     void *user_data) {
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_data;
    timer_event_t event = { .event_count = edata->count_value };
    xQueueSendFromISR(queue, &event, &high_task_awoken);
    // 动态修改下一次触发周期
    gptimer_alarm_config_t new_alarm = {
        .alarm_count = edata->alarm_value + 1000000 // 1秒 = 1MHz * 1s
    };
    gptimer_set_alarm_action(timer, &new_alarm);
    return (high_task_awoken == pdTRUE);
}

(二)完整示例代码

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "driver/gptimer.h"

static const char *TAG = "gptimer_example";

// 定时器事件结构体
typedef struct {
    uint64_t event_count;
} timer_event_t;

// 回调函数声明(三种模式)
static bool IRAM_ATTR timer_callback_v1(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data);
static bool IRAM_ATTR timer_callback_v2(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data);
static bool IRAM_ATTR timer_callback_v3(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data);

// 回调函数实现(单次触发)
static bool IRAM_ATTR timer_callback_v1(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) {
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_data;
    timer_event_t event = { .event_count = edata->count_value };
    xQueueSendFromISR(queue, &event, &high_task_awoken);
    gptimer_stop(timer);
    return (high_task_awoken == pdTRUE);
}

// 回调函数实现(自动重载)
static bool IRAM_ATTR timer_callback_v2(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) {
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_data;
    timer_event_t event = { .event_count = edata->count_value };
    xQueueSendFromISR(queue, &event, &high_task_awoken);
    return (high_task_awoken == pdTRUE);
}

// 回调函数实现(动态调整周期)
static bool IRAM_ATTR timer_callback_v3(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) {
    BaseType_t high_task_awoken = pdFALSE;
    QueueHandle_t queue = (QueueHandle_t)user_data;
    timer_event_t event = { .event_count = edata->count_value };
    xQueueSendFromISR(queue, &event, &high_task_awoken);
    gptimer_alarm_config_t new_alarm = {
        .alarm_count = edata->alarm_value + 1000000
    };
    gptimer_set_alarm_action(timer, &new_alarm);
    return (high_task_awoken == pdTRUE);
}

void app_main(void)
{
    // 1. 创建消息队列,接收定时器中断数据
    QueueHandle_t timer_queue = xQueueCreate(10, sizeof(timer_event_t));
    if (!timer_queue) {
        ESP_LOGE(TAG, "队列创建失败!内存不足");
        return;
    }

    // 2. 初始化定时器基本配置
    gptimer_handle_t timer = NULL;
    gptimer_config_t timer_cfg = {
        .clk_src = GPTIMER_CLK_SRC_DEFAULT,
        .direction = GPTIMER_COUNT_UP,
        .resolution_hz = 1000000 // 1MHz,1计数单位=1微秒
    };
    ESP_ERROR_CHECK(gptimer_new_timer(&timer_cfg, &timer));

    // 模式1:单次触发模式
    ESP_LOGI(TAG, "\n===== 测试模式1:单次触发 =====");
    gptimer_event_callbacks_t cbs = { .on_alarm = timer_callback_v1 };
    ESP_ERROR_CHECK(gptimer_register_event_callbacks(timer, &cbs, timer_queue));
    ESP_ERROR_CHECK(gptimer_enable(timer));
    gptimer_alarm_config_t alarm_cfg1 = { .alarm_count = 1000000 }; // 1秒触发
    ESP_ERROR_CHECK(gptimer_set_alarm_action(timer, &alarm_cfg1));
    ESP_ERROR_CHECK(gptimer_start(timer));
    ESP_LOGI(TAG, "定时器已启动,1秒后触发一次并停止");
    timer_event_t event;
    if (xQueueReceive(timer_queue, &event, pdMS_TO_TICKS(2000))) {
        ESP_LOGI(TAG, "模式1触发成功,计数值:%llu", event.event_count);
    } else {
        ESP_LOGW(TAG, "模式1超时未触发!");
    }

    // 模式2:自动重载模式
    ESP_LOGI(TAG, "\n===== 测试模式2:自动重载 =====");
    ESP_ERROR_CHECK(gptimer_disable(timer));
    cbs.on_alarm = timer_callback_v2;
    ESP_ERROR_CHECK(gptimer_register_event_callbacks(timer, &cbs, timer_queue));
    ESP_ERROR_CHECK(gptimer_enable(timer));
    gptimer_alarm_config_t alarm_cfg2 = {
        .reload_count = 0,
        .alarm_count = 1000000,
        .flags.auto_reload_on_alarm = true
    };
    ESP_ERROR_CHECK(gptimer_set_alarm_action(timer, &alarm_cfg2));
    ESP_ERROR_CHECK(gptimer_start(timer));
    ESP_LOGI(TAG, "定时器已启动,将以1秒周期触发4次");
    int trigger_count = 4;
    while (trigger_count > 0) {
        if (xQueueReceive(timer_queue, &event, pdMS_TO_TICKS(2000))) {
            ESP_LOGI(TAG, "模式2第%d次触发,计数值:%llu", 5 - trigger_count, event.event_count);
            trigger_count--;
        } else {
            ESP_LOGW(TAG, "模式2第%d次触发超时!", 5 - trigger_count);
            break;
        }
    }
    ESP_ERROR_CHECK(gptimer_stop(timer));

    // 模式3:动态调整周期模式
    ESP_LOGI(TAG, "\n===== 测试模式3:动态调整周期 =====");
    ESP_ERROR_CHECK(gptimer_disable(timer));
    cbs.on_alarm = timer_callback_v3;
    ESP_ERROR_CHECK(gptimer_register_event_callbacks(timer, &cbs, timer_queue));
    ESP_ERROR_CHECK(gptimer_enable(timer));
    gptimer_alarm_config_t alarm_cfg3 = { .alarm_count = 1000000 }; // 初始1秒
    ESP_ERROR_CHECK(gptimer_set_alarm_action(timer, &alarm_cfg3));
    ESP_ERROR_CHECK(gptimer_start(timer));
    ESP_LOGI(TAG, "定时器已启动,每次触发周期增加1秒(共4次)");
    trigger_count = 4;
    while (trigger_count > 0) {
        if (xQueueReceive(timer_queue, &event, pdMS_TO_TICKS(5000))) {
            ESP_LOGI(TAG, "模式3第%d次触发,计数值:%llu", 5 - trigger_count, event.event_count);
            trigger_count--;
        } else {
            ESP_LOGW(TAG, "模式3第%d次触发超时!", 5 - trigger_count);
            break;
        }
    }

    // 清理资源
    ESP_ERROR_CHECK(gptimer_stop(timer));
    ESP_ERROR_CHECK(gptimer_disable(timer));
    ESP_ERROR_CHECK(gptimer_del_timer(timer));
    vQueueDelete(timer_queue);
    ESP_LOGI(TAG, "\n所有测试完成,资源已清理");
}

五、BLE SERVER端上传数据(SHT31温湿度示例)

(一)核心功能

实现BLE Server端,通过I2C读取SHT31温湿度传感器数据,创建GATT服务和特征,将温湿度数据广播并响应客户端读取、指示请求。

(二)完整示例代码

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "driver/i2c_master.h"

#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gatt_common_api.h"

// 宏定义配置
#define PROFILE_NUM 1
#define ENV_PROFILE_APP_ID 0
#define ENV_SVC_UUID 0x181A
#define TEMP_CHAR_UUID 0x2A6E
#define HUMI_CHAR_UUID 0x2A6F
#define ENV_NUM_HANDLE 6
#define ADV_CONFIG_FLAG (1 << 0)
#define SCAN_RSP_CONFIG_FLAG (1 << 1)
#define I2C_MASTER_NUM I2C_NUM_0
#define I2C_MASTER_SCL_IO 15
#define I2C_MASTER_SDA_IO 16
#define SHT31_ADDR 0x44

// GATT服务实例结构体
struct gatts_profile_inst
{
    esp_gatts_cb_t gatts_cb;
    uint16_t gatts_if;
    uint16_t app_id;
    uint16_t conn_id;
    uint16_t service_handle;
    esp_gatt_srvc_id_t service_id;
    uint16_t temp_char_handle;
    esp_bt_uuid_t temp_char_uuid;
    uint16_t humi_char_handle;
    esp_bt_uuid_t humi_char_uuid;
    esp_gatt_perm_t perm;
    esp_gatt_char_prop_t property;
    uint16_t temp_descr_handle;
    uint16_t humi_descr_handle;
    esp_bt_uuid_t descr_uuid;
};

// 函数声明
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static void env_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static void example_write_event_env(esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);

// 全局变量
static const char *GATTS_TAG = "GATTS_SHT31";
static esp_gatt_char_prop_t env_property = 0;
static uint8_t temp_val[2] = {0};
static uint8_t humi_val[2] = {0};
static bool temp_indicate_enabled = false;
static bool humi_indicate_enabled = false;
static bool env_create_cmpl = false;
static uint8_t adv_config_done = 0;

static esp_attr_value_t temp_attr = {
    .attr_max_len = 2,
    .attr_len = sizeof(temp_val),
    .attr_value = temp_val,
};
static esp_attr_value_t humi_attr = {
    .attr_max_len = 2,
    .attr_len = sizeof(humi_val),
    .attr_value = humi_val,
};

static esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,
    .include_name = true,
    .include_txpower = false,
    .min_interval = 0x0006,
    .max_interval = 0x0010,
    .appearance = 0x00,
    .manufacturer_len = 0,
    .p_manufacturer_data = NULL,
    .service_data_len = 0,
    .p_service_data = NULL,
    .service_uuid_len = 0,
    .p_service_uuid = NULL,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

static esp_ble_adv_params_t adv_params = {
    .adv_int_min = 0x20,
    .adv_int_max = 0x40,
    .adv_type = ADV_TYPE_IND,
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    .channel_map = ADV_CHNL_ALL,
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};

static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
    [ENV_PROFILE_APP_ID] = {
        .gatts_cb = env_gatts_profile_event_handler,
        .gatts_if = ESP_GATT_IF_NONE,
    },
};

static i2c_master_bus_handle_t i2c_bus_handle;
static i2c_master_dev_handle_t sht31_dev_handle;

// SHT31初始化
static esp_err_t sht31_init(void)
{
    i2c_master_bus_config_t bus_cfg = {
        .clk_source = I2C_CLK_SRC_DEFAULT,
        .i2c_port = I2C_MASTER_NUM,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .glitch_ignore_cnt = 7,
        .flags.enable_internal_pullup = true,
    };

    esp_err_t ret = i2c_new_master_bus(&bus_cfg, &i2c_bus_handle);
    if (ret != ESP_OK)
    {
        ESP_LOGE(GATTS_TAG, "I2C总线初始化失败: %s", esp_err_to_name(ret));
        return ret;
    }

    i2c_device_config_t dev_cfg = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = SHT31_ADDR,
        .scl_speed_hz = 100000,
    };

    ret = i2c_master_bus_add_device(i2c_bus_handle, &dev_cfg, &sht31_dev_handle);
    if (ret != ESP_OK)
    {
        ESP_LOGE(GATTS_TAG, "SHT31设备添加失败: %s", esp_err_to_name(ret));
        i2c_del_master_bus(i2c_bus_handle);
        return ret;
    }

    return ESP_OK;
}

// 读取SHT31温湿度数据
static esp_err_t sht31_read_data(uint16_t *temp, uint16_t *humi)
{
    uint8_t cmd[2] = {0x2C, 0x06};
    uint8_t data[6] = {0};

    esp_err_t ret = i2c_master_transmit(sht31_dev_handle, cmd, sizeof(cmd), pdMS_TO_TICKS(100));
    if (ret != ESP_OK)
    {
        ESP_LOGE(GATTS_TAG, "发送测量命令失败: %s", esp_err_to_name(ret));
        return ret;
    }

    vTaskDelay(pdMS_TO_TICKS(50));

    ret = i2c_master_receive(sht31_dev_handle, data, sizeof(data), pdMS_TO_TICKS(100));
    if (ret != ESP_OK)
    {
        ESP_LOGE(GATTS_TAG, "读取测量数据失败: %s", esp_err_to_name(ret));
        return ret;
    }

    uint16_t raw_temp = (data[0] << 8) | data[1];
    uint16_t raw_humi = (data[3] << 8) | data[4];

    *temp = (uint16_t)(-4500 + 17500 * raw_temp / 65535.0);
    *humi = (uint16_t)(10000 * raw_humi / 65535.0);

    return ESP_OK;
}

// 温湿度数据更新任务
static void env_data_task(void *param)
{
    ESP_LOGI(GATTS_TAG, "SHT31数据更新任务启动");

    while (1)
    {
        if (env_create_cmpl)
        {
            uint16_t temperature, humidity;
            if (sht31_read_data(&temperature, &humidity) == ESP_OK)
            {
                temp_val[0] = temperature & 0xFF;
                temp_val[1] = (temperature >> 8) & 0xFF;
                humi_val[0] = humidity & 0xFF;
                humi_val[1] = (humidity >> 8) & 0xFF;

                esp_ble_gatts_set_attr_value(
                    gl_profile_tab[ENV_PROFILE_APP_ID].temp_char_handle,
                    sizeof(temp_val),
                    temp_val);

                esp_ble_gatts_set_attr_value(
                    gl_profile_tab[ENV_PROFILE_APP_ID].humi_char_handle,
                    sizeof(humi_val),
                    humi_val);

                ESP_LOGI(GATTS_TAG, "温度: %d.%d℃, 湿度: %d.%d%%",
                         temperature / 100, temperature % 100,
                         humidity / 100, humidity % 100);
            }
            else
            {
                ESP_LOGE(GATTS_TAG, "SHT31数据读取失败,重试中...");
            }
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

// GAP事件处理函数
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    switch (event)
    {
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
        ESP_LOGI(GATTS_TAG, "广播数据配置完成,状态 %d", param->adv_data_cmpl.status);
        adv_config_done &= (~ADV_CONFIG_FLAG);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
    case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
        ESP_LOGI(GATTS_TAG, "扫描响应数据配置完成,状态 %d", param->scan_rsp_data_cmpl.status);
        adv_config_done &= (~SCAN_RSP_CONFIG_FLAG

六、WiFi STA模式(ESP32连接网络)

(一)配置方法

1. 准备环境(类似手机插卡、开网络)

  • esp_netif_init():初始化网络“基带”,让ESP32能处理网络数据(类似手机插卡并打开移动网络)。

  • esp_event_loop_create_default():创建“事件通知栏”,让系统能发送通知(如“WiFi连上了”“获取到IP了”)。

  • esp_netif_create_default_wifi_sta():创建“WiFi客户端”接口(类似手机里的“WiFi开关”,专门用于连接路由器)。

2. 配置WiFi(填写WiFi名称、密码)

  • esp_wifi_init():打开WiFi芯片(类似手机点击“打开WiFi”按钮)。

  • esp_event_handler_instance_register():订阅“WiFi事件通知”(类似手机开启“WiFi连接提醒”,连上/断开都会触发通知)。

  • wifi_config_t:结构体,用于填写要连接的WiFi名称、密码(类似手机输入“WiFi名称+密码”)。

  • esp_wifi_set_mode():设置为STA模式(WIFI_MODE_STA),即“连接他人WiFi”的客户端模式。

  • esp_wifi_set_config():将填写好的WiFi信息应用到STA接口(类似手机点击“保存”按钮)。

3. 启动连接(类似手机点击“连接”按钮)

  • esp_wifi_start():开始扫描并连接WiFi(类似手机点击“连接”,主动寻找路由器并尝试登录)。

事件驱动(连接状态通知)

连接过程中,系统通过event_handler反馈状态,关键事件如下:

  • WIFI_EVENT_STA_START:开始扫描WiFi(类似手机显示“正在扫描...”)。

  • WIFI_EVENT_STA_CONNECTED:已连上路由器,但未获取IP(类似手机显示“已连接,但无互联网”)。

  • IP_EVENT_STA_GOT_IP:成功获取IP,可正常上网(类似手机显示“已连接,可上网”)。

  • WIFI_EVENT_STA_DISCONNECTED:连接失败或断开(类似手机显示“WiFi断开,正在重试”)。

4. 重试机制(自动重连)

通过全局变量s_retry_num实现自动重试功能,当连接失败时,会自动重试直至达到最大重试次数(类似手机“连不上时自动重连”功能)。

(二)完整示例代码

#include <string.h>
// FreeRTOS 基础头文件:提供任务、事件组等功能
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
// ESP32 系统相关头文件
#include "esp_system.h"
// WiFi 功能头文件
#include "esp_wifi.h"
// 事件驱动头文件(处理 WiFi 连接、获取 IP 等事件)
#include "esp_event.h"
// 日志头文件(打印调试信息)
#include "esp_log.h"
// 非易失性存储(保存 WiFi 配置等信息)
#include "nvs_flash.h"

// LwIP 协议栈相关头文件(处理网络数据)
#include "lwip/err.h"
#include "lwip/sys.h"

// 从 menuconfig 中获取 WiFi 名称、密码、最大重试次数
#define EXAMPLE_ESP_WIFI_SSID      CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS      CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY  CONFIG_ESP_MAXIMUM_RETRY

// 日志标签(区分不同模块的日志,这里代表 WiFi Station)
static const char *TAG = "wifi station";
// 事件处理实例句柄:用于注销事件(WiFi 所有事件、获取 IP 事件)
esp_event_handler_instance_t instance_any_id = NULL;
esp_event_handler_instance_t instance_got_ip = NULL;
// 连接重试次数
int s_retry_num = 0;

/**
 * @brief WiFi 和 IP 事件的“总管家”
 *        当 WiFi 连接状态变化(如启动、断开、连接成功)或获取到 IP 时,会触发这里
 * @param arg:用户自定义参数(这里没用,传 NULL)
 * @param event_base:事件类型(是 WiFi 相关事件,还是 IP 相关事件)
 * @param event_id:具体事件(比如 WiFi 启动、WiFi 断开、获取到 IP 等)
 * @param event_data:事件附带的数据(比如 IP 地址信息)
 */
static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    // 情况 1:WiFi Station 刚启动,需要开始连接路由器
    if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        ESP_LOGI(TAG, "sta启动事件");
        // 调用 ESP-IDF 提供的 API,发起 WiFi 连接
        ESP_ERROR_CHECK(esp_wifi_connect());    
    }
    // 情况 2:WiFi 连接断开了(可能是信号差、密码错等)
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        ESP_LOGI(TAG, "sta断开事件");
        // 如果重试次数还没到最大限制,就再试一次
        if(s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
        {
            ESP_LOGI(TAG, "重试连接");
            // 重新发起连接
            ESP_ERROR_CHECK(esp_wifi_connect());
            // 重试次数 +1
            s_retry_num++;
        }
    }
    // 情况 3:WiFi 成功连接到路由器(但还没获取到 IP)
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED)
    {
        ESP_LOGI(TAG, "WIFI连接成功");
    }
    // 情况 4:成功获取到 IP 地址(可以上网了!)
    else if(event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        // 把事件数据转换成 IP 信息结构体(里面存着获取到的 IP 地址)
        ip_event_got_ip_t *ip_event = (ip_event_got_ip_t*)event_data;
        // 打印获取到的 IP 地址(IPSTR 是格式化宏,IP2STR 用来解析地址)
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&ip_event->ip_info.ip));
    }
    
}

/**
 * @brief 初始化并配置 WiFi Station 模式的核心函数
 *        流程:初始化网络环境 → 配置 WiFi 参数 → 启动 WiFi → 注册事件监听
 */
void wifi_init_sta(void)
{
    // 1. 初始化 TCP/IP 协议栈(让 ESP32 能处理网络数据)
    ESP_ERROR_CHECK(esp_netif_init());

    // 2. 创建默认事件循环(让 ESP32 能接收和处理 WiFi、IP 等事件)
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 3. 创建默认的 WiFi Station 网络接口(把 WiFi 驱动和网络协议栈绑定)
    esp_netif_create_default_wifi_sta();

    // 4. 初始化 WiFi 驱动(设置默认配置,比如时钟、缓存等)
    wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));

    // 5. 注册事件处理函数(让 event_handler 能收到 WiFi 和 IP 事件)
    // 5.1 监听所有 WiFi 事件(比如启动、断开、连接成功)
    ESP_ERROR_CHECK(esp_event_handler_instance_register(
        WIFI_EVENT,          // 事件类型:WiFi 相关事件
        ESP_EVENT_ANY_ID,    // 监听 WiFi 所有事件
        event_handler,       // 事件发生时,调用这个函数处理
        NULL,                // 传给 event_handler 的参数(这里没用)
        &instance_any_id     // 保存事件实例,后续可用来注销
    ));
    // 5.2 监听“获取到 IP”事件(属于 IP 相关事件)
    ESP_ERROR_CHECK(esp_event_handler_instance_register(
        IP_EVENT,                            // 事件类型:IP 相关事件
        IP_EVENT_STA_GOT_IP,                 // 具体事件:Station 获取到 IP
        event_handler,                       // 事件发生时,调用这个函数处理
        NULL,                                // 传给 event_handler 的参数(这里没用)
        &instance_got_ip                     // 保存事件实例,后续可用来注销
    ));

    // 6. 配置要连接的 WiFi 信息(名称、密码、加密方式等)
    wifi_config_t cfg = {
        .sta = {  // Station 模式配置
            .ssid = EXAMPLE_ESP_WIFI_SSID,     // 要连接的 WiFi 名称(从配置获取)
            .password = EXAMPLE_ESP_WIFI_PASS, // 要连接的 WiFi 密码(从配置获取)
            .sae_pwe_h2e = WPA3_SAE_PWE_BOTH,  // WPA3 加密相关(同时支持两种模式,适配新路由器)
            .sae_h2e_identifier = "",          // WPA3 加密的标识符(这里不用填)
            .threshold.authmode = WIFI_AUTH_WPA2_PSK, // 认证模式:用 WPA2 密码认证(兼容大多数路由器)
        }
    };

    // 7. 设置 WiFi 工作模式为 Station(即 ESP32 作为客户端连路由器)
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

    // 8. 把上面配置的 WiFi 信息(名称、密码等)应用到 Station 接口
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &cfg));

    // 9. 启动 WiFi 驱动(开始工作,扫描、连接路由器)
    ESP_ERROR_CHECK(esp_wifi_start());
}

/**
 * @brief 程序入口(ESP32 启动后第一个运行的函数)
 *        流程:初始化 NVS(保存配置) → 初始化并启动 WiFi Station
 */
void app_main(void)
{
    // 1. 初始化 NVS(非易失性存储,用来保存 WiFi 配置、连接记录等)
    esp_err_t ret = nvs_flash_init();
    // 如果 NVS 满了或者版本不兼容,先擦除再重试
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){
        nvs_flash_erase();
        ret = nvs_flash_init();
    }
    // 检查初始化结果,如果失败会报错停止
    ESP_ERROR_CHECK(ret);

    // 2. 初始化并启动 WiFi Station(调用上面的配置函数)
    wifi_init_sta();
}

更多推荐