ESP32S3-IDF版本学习笔记(带API和代码示例)
一、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();
}
更多推荐
所有评论(0)