ESP32通关学习【全网详细版】
解决方法1:在任务A进行任务通知之前,先Delay,让这个任务A先阻塞一下,以至于任务B可以运行,才可以成功创建这个taskB_handle句柄。我们创建完任务A之后,任务A就直接运行了,任务A会直接向任务B发送任务通知,由于taskB_handle是一个无效值,所以导致程序崩溃了。解决方法2:在app_main()函数中颠倒任务A 和 任务B 的创建顺序,使得taskB_handle 这个任务B
ESP32通关学习
一、ESP32架构和组件依赖

1、组件

2、组件依赖

二、ESP32任务创建


为了处理ESP32这个双核的情况 - 该函数为乐鑫自己实现的函数

1、新建文件
①在终端命令中新建文件
cd ~/ESP/v5.5/esp-idf
. ./export.sh

②我们需要添加ESP-IDF路径到工程中 在vscode中选择

这样就可以把ESP-IDF中里面的一些头文件包含路径包含到我们的工程中了,此时可以编写代码了


因为这个函数它的参数不是毫秒,而是系统节拍,那么多少系统节拍是500ms呢?那么这里FreeRTOS提供一个宏
pdMS_TO_TICKS() 里面的系数填多少,就可以把相应的时间转换为对应的系统节拍数

2、编译烧录命令
一般的命令操作 这几条命令可以让环境直接先运行get_idf - 其实就是修改这个路径问题 echo 'export IDF_PATH=~/ESP_IDF/v5.5.1/esp-idf' >> ~/.bashrc echo 'export IDF_TOOLS_PATH=~/ESP_IDF/Tools' >> ~/.bashrc echo 'source ~/ESP_IDF/v5.5.1/esp-idf/export.sh' >> ~/.bashrc source ~/.bashrc # 立即生效 cd ~/ESP/v5.5/esp-idf . ./export.sh cd ~/ESP_Code/test_task idf.py build -- 如果不行的话 替换为下面的一条命令 idf.py fullclean 【以上可以不用】 设置工具 - 修改环境变量 export IDF_TOOLS_PATH="$HOME/required_idf_tools_path" vim ~/.bashrc alias get_idf='. $HOME/esp/esp-idf/export.sh' 之后按;然后写入wq保存并退出 source ~/.bashrc 【环境变量设置】 get_idf idf.py build idf.py flash monitor - 烧录 【以后三条命令可以直接运行】
三、ESP32任务间同步
1、队列
队列在Freertos中主要是用于不同任务间的数据传输,一般来说有个负责写入数据的任务【比如:负责采集传感器数据的任务】,然后有一个不断从队列中接收数据的任务

【xQueueCreate创建一个队列,创建成功后,它会返回这个队列的句柄,后面所有的api都是基于这个句柄进行操作的】
【第一个参数为队列的最大容量、第二个参数为每个队列项所占的内存大小-单位为字节】


2、队列实验
队列代码
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
//新建一个队列句柄 - 本质上是一个指针
QueueHandle_t queue_handle = NULL;
//新建一个结构体 - 是存放在队列里面的数据单元
typedef struct
{
int value;
}queue_data_t;
void taskA(void* param)
{
//任务A:从队列里面接收数据,并打印
queue_data_t data;
while(1)
{
//参数 句柄 缓存 等待时间
if(pdTRUE == xQueueReceive(queue_handle,&data,100))
{
ESP_LOGI("queue","receive queue value:%d",data.value);
}
}
}
void taskB(void* param)
{
//任务B:每隔1s向队列里面发数据
queue_data_t data;
memset(&data,0,sizeof(queue_data_t));
while(1)
{
xQueueSend(queue_handle,&data,100);
vTaskDelay(pdMS_TO_TICKS(1000));
data.value++;
}
}
void app_main(void)
{
//创建一个队列 - 参数 队列最大容量 队列单元的占用内存字节数
queue_handle = xQueueCreate(10,sizeof(queue_data_t));
//创建以下两个任务A B
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1);
}
实验效果如下:

四、ESP32信号量
1、互斥锁

互斥锁实现了优先级继承机制 - 常常用于临界区资源的互斥访问
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "esp_log.h"
//定义一个信号量句柄
SemaphoreHandle_t bin_sem;
void taskA(void* param)
{
//释放信号量
while(1)
{
xSemaphoreGive(bin_sem);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskB(void* param)
{
//等待信号量成功后,打印提示
while(1)
{
if(pdTRUE == xSemaphoreTake(bin_sem,portMAX_DELAY))
{
ESP_LOGI("bin","task B take binsem success");
}
}
}
void app_main(void)
{
bin_sem = xSemaphoreCreateBinary();
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,4,NULL,1);
}

互斥锁使用的原则就是 - 谁拿了谁释放
2、移植dht11

最开始先设置信号线为输出模式,如上图所示: 为了主机开始发信号脉冲

信号线设置为输入模式 - 准备接收数据
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <nvs_flash.h>
#include <soc/rmt_reg.h>
#include "esp_log.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp32s3/rom/ets_sys.h"
#include "driver/rmt_types.h"
#include "driver/rmt_rx.h"
#include "driver/rmt_tx.h"
#define TAG "DHT11"
static uint8_t DHT11_PIN = -1;
// RMT 接收通道句柄
static rmt_channel_handle_t rx_chan_handle = NULL;
// 数据接收队列
static QueueHandle_t rx_receive_queue = NULL;
// 互斥锁,防止多任务同时访问
static SemaphoreHandle_t dht11_mutex = NULL;
// forward declaration
static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10);
// 接收完成回调函数
static bool IRAM_ATTR example_rmt_rx_done_callback(rmt_channel_handle_t channel,
const rmt_rx_done_event_data_t *edata,
void *user_data)
{
BaseType_t high_task_wakeup = pdFALSE;
QueueHandle_t rx_receive_queue = (QueueHandle_t)user_data;
xQueueSendFromISR(rx_receive_queue, edata, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
/** DHT11 初始化 */
void DHT11_Init(uint8_t dht11_pin)
{
DHT11_PIN = dht11_pin;
rmt_rx_channel_config_t rx_chan_config = {
.clk_src = RMT_CLK_SRC_APB,
.resolution_hz = 1000 * 1000, // 1 tick = 1us
.mem_block_symbols = 64,
.gpio_num = dht11_pin,
.flags.invert_in = false,
.flags.with_dma = false,
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan_handle));
rx_receive_queue = xQueueCreate(20, sizeof(rmt_rx_done_event_data_t));
assert(rx_receive_queue);
ESP_LOGI(TAG, "register RX done callback");
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = example_rmt_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_chan_handle, &cbs, rx_receive_queue));
ESP_ERROR_CHECK(rmt_enable(rx_chan_handle));
// 初始化互斥锁
if (dht11_mutex == NULL) {
dht11_mutex = xSemaphoreCreateMutex();
assert(dht11_mutex);
}
}
// RMT 脉冲解析函数(不变)
static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10)
{
int i = 0;
unsigned int rh = 0, temp = 0, checksum = 0;
if (item_num < 41) {
return 0;
}
if (item_num > 41)
item++;
for (i = 0; i < 16; i++, item++) {
uint16_t duration = item->level0 ? item->duration0 : item->duration1;
rh = (rh << 1) + (duration < 35 ? 0 : 1);
}
for (i = 0; i < 16; i++, item++) {
uint16_t duration = item->level0 ? item->duration0 : item->duration1;
temp = (temp << 1) + (duration < 35 ? 0 : 1);
}
for (i = 0; i < 8; i++, item++) {
uint16_t duration = item->level0 ? item->duration0 : item->duration1;
checksum = (checksum << 1) + (duration < 35 ? 0 : 1);
}
if ((((temp >> 8) + temp + (rh >> 8) + rh) & 0xFF) != checksum) {
ESP_LOGI(TAG, "Checksum failure %4X %4X %2X\n", temp, rh, checksum);
return 0;
}
rh = rh >> 8;
temp = (temp >> 8) * 10 + (temp & 0xFF);
if (rh <= 100) *humidity = rh;
if (temp <= 600) *temp_x10 = temp;
return 1;
}
/** 获取 DHT11 数据(加了互斥锁) */
int DHT11_StartGet(int *temp_x10, int *humidity)
{
int result = 0;
if (xSemaphoreTake(dht11_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
// 发送开始信号
gpio_set_direction(DHT11_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(DHT11_PIN, 1);
ets_delay_us(1000);
gpio_set_level(DHT11_PIN, 0);
ets_delay_us(20000);
gpio_set_level(DHT11_PIN, 1);
ets_delay_us(20);
gpio_set_direction(DHT11_PIN, GPIO_MODE_INPUT);
gpio_set_pull_mode(DHT11_PIN, GPIO_PULLUP_ONLY);
// 启动 RMT 接收器
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 100,
.signal_range_max_ns = 1000 * 1000,
};
static rmt_symbol_word_t raw_symbols[128];
static rmt_rx_done_event_data_t rx_data;
ESP_ERROR_CHECK(rmt_receive(rx_chan_handle, raw_symbols, sizeof(raw_symbols), &receive_config));
if (xQueueReceive(rx_receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdTRUE) {
result = parse_items(rx_data.received_symbols, rx_data.num_symbols, humidity, temp_x10);
}
xSemaphoreGive(dht11_mutex); // 释放互斥锁
} else {
ESP_LOGW(TAG, "DHT11 busy, another task is reading");
}
return result;
}
#ifndef _DHT11_H_ #define _DHT11_H_ #include <stdint.h> /** DHT11初始化 * @param dht11_pin GPIO引脚 * @return 无 */ void DHT11_Init(uint8_t dht11_pin); /** 获取DHT11数据 * @param temp_x10 温度值X10 * @param humidity 湿度值 * @return 无 */ int DHT11_StartGet(int *temp_x10, int *humidity); #endif
在main目录下: CMakeLists.txt idf_component_register(SRCS "test_sem.c" "dht11.c" INCLUDE_DIRS ".")
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "dht11.h"
SemaphoreHandle_t dht11_mutex;
void taskA(void* param)
{
int temp,humidity;
while(1)
{
xSemaphoreTake(dht11_mutex,portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(500));
if(DHT11_StartGet(&temp,&humidity))
{
ESP_LOGI("dht11","taskA-->temp:%d,humidity:%d%%",temp / 10,humidity);
}
xSemaphoreGive(dht11_mutex);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskB(void* param)
{
int temp,humidity;
while(1)
{
xSemaphoreTake(dht11_mutex,portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(500));
if(DHT11_StartGet(&temp,&humidity))
{
ESP_LOGI("dht11","taskB-->temp:%d,humidity:%d%%",temp / 10,humidity);
}
xSemaphoreGive(dht11_mutex);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
dht11_mutex = xSemaphoreCreateMutex();
DHT11_Init(GPIO_NUM_20);
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1);
}
3、RMT硬件外设
RMT 是 ESP32 系列上一个硬件外设,用来生成或采样高精度脉冲序列(常用于红外、遥控、单线协议采样等)。 优点:硬件定时,精度高,CPU 参与少;可以当作高速脉冲捕获器来读 DHT11 这类通过脉冲长度表示 0/1 的器件。 RMT 的基本概念: resolution_hz:分辨率(你设 1 MHz,即 1 tick = 1 µs)。 mem_block_symbols:每个内存块能记录多少个 symbol(符号)。一个 symbol 通常包含一对(level,duration)信息(level0/duration0, level1/duration1)。 rmt_symbol_word_t:RMT 的“符号”结构(duration0/duration1 与 level0/level1)。 驱动回调会把 rmt_rx_done_event_data_t(含 received_symbols 指针 和 num_symbols)交给用户。
五、事件组|直达任务通知【同步】
1、事件组

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
//定义两个事件位
#define NUM0_BIT BIT0
#define NUM1_BIT BIT1
//定义一个事件组的句柄
static EventGroupHandle_t test_event;
void taskA(void* param)
{
//定时设置不同的事件位
while(1)
{
xEventGroupSetBits(test_event,NUM0_BIT);
vTaskDelay(pdMS_TO_TICKS(1000));
xEventGroupSetBits(test_event,NUM1_BIT);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
//任务B 等待上面的两个事件位
void taskB(void* param)
{
//等待事件位
EventBits_t ev;
while(1)
{
ev = xEventGroupWaitBits(test_event,NUM0_BIT|NUM1_BIT,pdTRUE,pdFALSE,pdMS_TO_TICKS(50000));
if(ev & NUM0_BIT)
{
ESP_LOGI("ev","get BIT0 event");
}
if(ev & NUM1_BIT)
{
ESP_LOGI("ev","get BIT1 event");
}
}
}
void app_main(void)
{
test_event = xEventGroupCreate();
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1);
}

2、直达任务通知

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
static TaskHandle_t taskA_handle;
static TaskHandle_t taskB_handle;
void taskA(void* param)
{
//定时发送一个任务通知值
uint32_t value = 0;
//vTaskDelay(pdMS_TO_TICKS(200));
while(1)
{
xTaskNotify(taskB_handle,value,eSetValueWithOverwrite);
vTaskDelay(pdMS_TO_TICKS(1000));
value++;
}
}
void taskB(void* param)
{
//接收任务通知值并打印
uint32_t value;
while(1)
{
xTaskNotifyWait(0x00,ULONG_MAX,&value,portMAX_DELAY);
ESP_LOGI("ev","notify wait value:%d",value);
}
}
void app_main(void)
{
//xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,&taskA_handle,1);
xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,&taskB_handle,1);
xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,&taskA_handle,1);
}
注意:如果我们在app_main()中先创建任务A,此时taskB_handle是一个无效值
我们创建完任务A之后,任务A就直接运行了,任务A会直接向任务B发送任务通知,由于taskB_handle是一个无效值,所以导致程序崩溃了
解决方法1:在任务A进行任务通知之前,先Delay,让这个任务A先阻塞一下,以至于任务B可以运行,才可以成功创建这个taskB_handle句柄
解决方法2:在app_main()函数中颠倒任务A 和 任务B 的创建顺序,使得taskB_handle 这个任务B的句柄成功创建。
六、ESP_IDF的FreeRTOS操作系统

注意:ESP_IDF版本自动创建 5 个任务 ①空闲任务 - 在每一个核上都会有1个 优先级为0 ②FreeRTOS定时器任务 - 它的优先级任务是1 ③app_main任务 - 优先级是1 【应用入口】 ④IPC任务 - 每一个核创建一个【用于处理多核协调】 优先级是24,为最高优先级 ⑤ESP定时器任务 - 负责ESP定时器的回调 - 优先级为22
/* 注意: ①ESP_IDF FreeRTOS版本步使用原生FreeRTOS的内存管理,他实现了自己的堆 ②如果任务中使用到了浮点运算,则创建任务的时候必须指定具体运行在哪个核上,不能由系统自动安排 ③通常,负责处理无线网络的任务【WIFI 蓝牙】 将被固定到CPU0上,而处理应用程序其余部分的任务将被固定在CPU1上 */
七、ESP32外设
1、GPIO

/* 在ESP_IDF中,我们经常会看到一种代码 ①先定义一个结构体 ②我们往结构体里面填充东西 - 填充东西之后成为一个配置 ③我们将这个配置通过一个api函数 - 设置到底层去 */
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define LED_GPIO GPIO_NUM_4
void led_task(void* param)
{
int gpio_level = 0;
while(1)
{
gpio_level = gpio_level ? 0 : 1;
gpio_set_level(LED_GPIO,gpio_level);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void app_main(void)
{
//先定义一个结构体 - 初始化配置
gpio_config_t led_cfg = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1 << LED_GPIO),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
//通过一个api函数 - 设置到底层去
gpio_config(&led_cfg);
//新建一个任务
xTaskCreatePinnedToCore(led_task,"led_task",2048,NULL,3,NULL,1);
}
2、脉冲宽度调制PWM

如上图所示: 如果我们加了IRAM_ATTR这个宏,就说明这个函数是放在内存中执行的,而不是flash中,那么他的效率也就比较高
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_intr_alloc.h" // for ESP_INTR_FLAG_IRAM optional
#define LED_GPIO GPIO_NUM_4
#define FULL_EV_BIT BIT0
#define EMPTY_EV_BIT BIT1
static EventGroupHandle_t ledc_event_handle = NULL;
/* ISR 回调(fade 完成时被调用)
* 注意:必须把局部变量初始化为 pdFALSE,并返回 (taskWoken == pdTRUE)
*/
bool IRAM_ATTR ledc_finish_cb(const ledc_cb_param_t *param, void *user_arg)
{
BaseType_t taskWoken = pdFALSE; // 一定要初始化
if (param->duty) {
xEventGroupSetBitsFromISR(ledc_event_handle, FULL_EV_BIT, &taskWoken);
} else {
xEventGroupSetBitsFromISR(ledc_event_handle, EMPTY_EV_BIT, &taskWoken);
}
return (taskWoken == pdTRUE); // 告诉内核是否需要做上下文切换
}
void led_task(void* param)
{
EventBits_t ev;
while (1) {
ev = xEventGroupWaitBits(ledc_event_handle,
FULL_EV_BIT | EMPTY_EV_BIT,
pdTRUE, // 取到后自动清除这些位
pdFALSE, // 不需要全部位同时置位
portMAX_DELAY);
if (ev & FULL_EV_BIT) {
// 从 full -> fade to 0
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0, 2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT);
}
if (ev & EMPTY_EV_BIT) {
// 从 empty -> fade to max
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1<<13)-1, 2000); // 13-bit max = 8191
ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT);
}
// 循环继续等待事件
}
}
void app_main(void)
{
// 先创建事件组,确保回调不会在 event group 未创建时触发
ledc_event_handle = xEventGroupCreate();
// LEDC 定时器配置
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
ledc_timer_config(&ledc_timer);
// LEDC 通道配置(会设置引脚)
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.intr_type = LEDC_INTR_DISABLE, // 与 fade callback 无关(fade cb 通过 ledc_cb_register 注册)
.gpio_num = LED_GPIO,
.duty = 0,
.hpoint = 0
};
ledc_channel_config(&ledc_channel);
// 安装 fade 功能,参数为中断分配标志
// 如果你的回调需要在 IRAM 执行并且被要求中断分配在 IRAM, 可传 ESP_INTR_FLAG_IRAM
ledc_fade_func_install(0);
// 注册回调(一次即可)
ledc_cbs_t cbs = {
.fade_cb = ledc_finish_cb,
};
ledc_cb_register(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, &cbs, NULL);
// 启动第一个渐变(从 0 -> max)
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1<<13)-1, 2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT);
// 创建工作任务(别忘记检查返回值和调整栈/优先级)
xTaskCreatePinnedToCore(led_task, "led_task", 2048, NULL, 3, NULL, 1);
}
3、RMT外设
WS2812时序 - 0码 1码 - 高低电平的占比

4、串口UART
①GPIO交换矩阵提供了高度的引脚配置灵活性,允许用户将芯片的GPIO引脚重新配置为各种外设的输入或输出
②注意:ESP32中的GPIO交换矩阵不是完全没有限制的,如GPIO34、35、36、39,这几个引脚只能作为输入
八、ESP32的IOT
1、WIFI
ESP32的WIFI有三种工作方式: ①STA模式:【ESP32最常用的模式】,ESP32可以连接到已经存在的WIFI网路,从而允许ESP32与网络上的其他设备进行通信,类似于一台普通的WIFI客户端设备 ②AP模式:ESP32可以创建自己的WIFI网络,称为一个小型WIFI路由器,他可以接收其他WIFI终端设备连接,这种模式多用于【设备配网】 ③STA+AP模式:ESP32同时工作在STA和AP两种模式下,可以连接到已有的WIFI网络,也可以提供WIFI热点,从当一个【网桥/中继器】的功能
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_err.h"
#include <string.h>
#define TEST_SSID "kuku"
#define TEST_PASSWORD "12345678"
void wifi_event_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
//先判断事件类型
if(event_base == WIFI_EVENT)//WIFI事件
{
switch (event_id)
{
case WIFI_EVENT_STA_START://这个事件->已经启动STA的工作模式了
esp_wifi_connect();
break;
case WIFI_EVENT_STA_CONNECTED://ESP32已经连上了路由器
ESP_LOGI("sta","esp32 connet to ap!");
break;
case WIFI_EVENT_STA_DISCONNECTED://WIFI断连 -> 直接连接
esp_wifi_connect();
ESP_LOGI("sta","esp32 connet the ap faild retry");
break;
default:
break;
}
}
else if(event_base == IP_EVENT)
{
switch (event_id)
{
//只有获取到了路由器分配的IP - ESP32真正的连上了路由器
case IP_EVENT_STA_GOT_IP:
ESP_LOGI("sta","esp32 get ip address");
break;
}
}
}
//定义一个事件回调函数
void app_main(void)
{
//初始化nvs
ESP_ERROR_CHECK(nvs_flash_init());//用这个宏来检查这个初始化是否有问题
//初始化TCP/IP协议栈 ESP32中使用的是lwip
ESP_ERROR_CHECK(esp_netif_init());
//初始化 创建系统循环【WIFI连接过程中会出现各种事件】
ESP_ERROR_CHECK(esp_event_loop_create_default());
//初始化 创建STA
esp_netif_create_default_wifi_sta();
//初始化WIFI
//定义一个WIFI初始化配置结构体,设置到这个WIFI初始化函数去
//这个步骤会设置WIFI的缓冲区数量等 ESP_ERROR_CHECK这个函数是设置为默认功能
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));
//注册WIFI事件响应
esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,wifi_event_handle,NULL);
//注册网络事件响应
esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,wifi_event_handle,NULL);
wifi_config_t wifi_config = {
.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK, //加密模式
.sta.pmf_cfg.capable = true, //使用保护管理帧
.sta.pmf_cfg.required = false, //是否只和有保护管理帧功能的设备通行
};
//设置SSID
memset(wifi_config.sta.ssid,0,sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.ssid,TEST_SSID,strlen(TEST_SSID));
//设置密码
memset(wifi_config.sta.password,0,sizeof(wifi_config.sta.password));
memcpy(wifi_config.sta.password,TEST_PASSWORD,strlen(TEST_PASSWORD));
//设置WIFI当前的模式 STA
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
//配置WIFI的STA模式下的网卡
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA,&wifi_config));
//启动wifi工作
ESP_ERROR_CHECK(esp_wifi_start());
return;
}
2、SmartConfig配网
配网定义

SmartConfig配网原理:
①首先要让WIFI芯片处于混杂模式下,监听网络中的所有报文,手机APP将SSID和密码编码到UDP报文中,通过广播报或组播报文的形式发送;
②智能硬件接收到UDP报文后,解码得到正确的SSID和密码,然后主动连接到指定的路由,完成连接
#include <stdio.h>
#include <string.h>
#include "esp_smartconfig.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#define TEST_SSID "kuku"
#define TEST_PASSWORD "12345678"
void wifi_event_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
//先判断事件类型
if(event_base == WIFI_EVENT)//WIFI事件
{
switch (event_id)
{
case WIFI_EVENT_STA_START://这个事件->已经启动STA的工作模式了
esp_wifi_connect();
break;
case WIFI_EVENT_STA_CONNECTED://ESP32已经连上了路由器
ESP_LOGI("sta","esp32 connet to ap!");
break;
case WIFI_EVENT_STA_DISCONNECTED://WIFI断连 -> 直接连接
esp_wifi_connect();
ESP_LOGI("sta","esp32 connet the ap faild retry");
break;
default:
break;
}
}
else if(event_base == IP_EVENT)
{
switch (event_id)
{
//只有获取到了路由器分配的IP - ESP32真正的连上了路由器
case IP_EVENT_STA_GOT_IP:
ESP_LOGI("sta","esp32 get ip address");
break;
}
}
else if(event_base == SC_EVENT)
{
switch(event_id)
{
case SC_EVENT_SCAN_DONE:
ESP_LOGI("sc","sc scan done");
break;
case SC_EVENT_GOT_SSID_PSWD://提取SSID 和 PSWD
{
smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t*)event_data;
wifi_config_t wifi_config = {0};
memset(&wifi_config,0,sizeof(wifi_config));
snprintf((char*)wifi_config.sta.ssid,sizeof(wifi_config.sta.ssid),"%s",(char*)evt->ssid);
snprintf((char*)wifi_config.sta.password,sizeof(wifi_config.sta.password),"%s",(char*)evt->password);
//设置热点的MAC地址
wifi_config.sta.bssid_set = evt->bssid_set;
if(wifi_config.sta.bssid_set)
{
memcpy((char*)wifi_config.sta.bssid_set,evt->bssid,6);
}
//如果有WIFI连接,先断开连接
esp_wifi_disconnect();
//设置这个结构体进去
esp_wifi_set_config(WIFI_IF_STA,&wifi_config);
esp_wifi_connect();//发起连接
break;
}
case SC_EVENT_SEND_ACK_DONE://设备告诉
{
esp_smartconfig_stop();
break;
}
default:
break;
}
}
}
void app_main(void)
{
//初始化nvs
ESP_ERROR_CHECK(nvs_flash_init());//用这个宏来检查这个初始化是否有问题
//初始化TCP/IP协议栈 ESP32中使用的是lwip
ESP_ERROR_CHECK(esp_netif_init());
//初始化 创建系统循环【WIFI连接过程中会出现各种事件】
ESP_ERROR_CHECK(esp_event_loop_create_default());
//初始化 创建STA
esp_netif_create_default_wifi_sta();
//初始化WIFI
//定义一个WIFI初始化配置结构体,设置到这个WIFI初始化函数去
//这个步骤会设置WIFI的缓冲区数量等 ESP_ERROR_CHECK这个函数是设置为默认功能
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));
//注册WIFI事件响应
esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,wifi_event_handle,NULL);
//注册网络事件响应
esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,wifi_event_handle,NULL);
//注册SmartConfig事件响应
esp_event_handler_register(SC_EVENT,ESP_EVENT_ANY_ID,wifi_event_handle,NULL);
//设置WIFI当前的模式 STA
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
//启动wifi工作
ESP_ERROR_CHECK(esp_wifi_start());
//开启Smartconfig
esp_smartconfig_set_type(SC_TYPE_ESPTOUCH);
smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
esp_smartconfig_start(&cfg);
return ;
}
3、MQTT协议


MQTT协议是基于主题来进行消息流向的,从本质上看,topic是一串字符串
#include <stdio.h>
#include "mqtt_client.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_err.h"
#include "freertos/semphr.h"
#define MQTT_ADDRESS "mqtt://broker-cn.emqx.io"
#define MQTT_CLIENTID "mqttx_esp3220250914"
#define MQTT_USERNAME "esp32name"
#define MQTT_PASSWORD "esp32password"
#define TAG "mqtt"
//定义两个主题
#define MQTT_TOPIC1 "/topic/esp32" //ESP32往这个主题推送消息
#define MQTT_TOPIC2 "/topic/mqtt" //mqtt往这个主题推送消息
#define TEST_SSID "kuku"
#define TEST_PASSWORD "12345678"
//定义一个句柄 - 后面的MQTT的发布与订阅的操作需要用到这个句柄
static esp_mqtt_client_handle_t mqtt_handle = NULL;
static SemaphoreHandle_t wifi_connect_sem = NULL;
void wifi_event_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
//先判断事件类型
if(event_base == WIFI_EVENT)//WIFI事件
{
switch (event_id)
{
case WIFI_EVENT_STA_START://这个事件->已经启动STA的工作模式了
esp_wifi_connect();
break;
case WIFI_EVENT_STA_CONNECTED://ESP32已经连上了路由器
ESP_LOGI("sta","esp32 connet to ap!");
break;
case WIFI_EVENT_STA_DISCONNECTED://WIFI断连 -> 直接连接
esp_wifi_connect();
ESP_LOGI("sta","esp32 connet the ap faild retry");
break;
default:
break;
}
}
else if(event_base == IP_EVENT)
{
switch (event_id)
{
//只有获取到了路由器分配的IP - ESP32真正的连上了路由器[网]
case IP_EVENT_STA_GOT_IP:
ESP_LOGI("sta","esp32 get ip address");
xSemaphoreGive(wifi_connect_sem);//释放信号量
break;
}
}
}
//修改回调函数
void mqtt_event_callback(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
esp_mqtt_event_handle_t data = (esp_mqtt_event_handle_t)event_data;
switch(event_id)
{
case MQTT_EVENT_CONNECTED://成功连接上服务器
ESP_LOGI(TAG,"mqtt conneted");
//订阅函数
esp_mqtt_client_subscribe_single(mqtt_handle,MQTT_TOPIC2,1);
break;
case MQTT_EVENT_DISCONNECTED://断开服务器
ESP_LOGI(TAG,"mqtt disconneted");
break;
case MQTT_EVENT_PUBLISHED://发布 - ESP32向服务器发送一条消息
ESP_LOGI(TAG,"mqtt published");
break;
case MQTT_EVENT_SUBSCRIBED://订阅 - 向服务器订阅一个主题
ESP_LOGI(TAG,"mqtt subscribed");
break;
case MQTT_EVENT_DATA://其他终端向服务器推送消息 - 服务器把该消息发给ESP32 - 提取数据
ESP_LOGI(TAG,"topic:%s",data->topic);//打印主题
ESP_LOGI(TAG,"payload:%s",data->data);//打印数据
break;
default:
break;
}
}
//向服务器发起连接
void mqtt_start(void)
{
esp_mqtt_client_config_t mqtt_cfg = {0};
//配置mqtt的地址
mqtt_cfg.broker.address.uri = MQTT_ADDRESS;
//配置MQTT端口
mqtt_cfg.broker.address.port = 1883;
mqtt_cfg.credentials.client_id = MQTT_CLIENTID;
mqtt_cfg.credentials.username = MQTT_USERNAME;
mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD;
//将初始化配置写入
mqtt_handle = esp_mqtt_client_init(&mqtt_cfg);
//注册一个事件响应回调函数 对中间过程【连接成功、发布、订阅】
esp_mqtt_client_register_event(mqtt_handle,MQTT_EVENT_ANY,mqtt_event_callback,NULL);
esp_mqtt_client_start(mqtt_handle);
}
void app_main(void)
{
//初始化nvs
ESP_ERROR_CHECK(nvs_flash_init());//用这个宏来检查这个初始化是否有问题
//初始化TCP/IP协议栈 ESP32中使用的是lwip
ESP_ERROR_CHECK(esp_netif_init());
//初始化 创建系统循环【WIFI连接过程中会出现各种事件】
ESP_ERROR_CHECK(esp_event_loop_create_default());
//初始化 创建STA
esp_netif_create_default_wifi_sta();
//初始化WIFI
//定义一个WIFI初始化配置结构体,设置到这个WIFI初始化函数去
//这个步骤会设置WIFI的缓冲区数量等 ESP_ERROR_CHECK这个函数是设置为默认功能
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));
wifi_connect_sem = xSemaphoreCreateBinary();
//注册WIFI事件响应
esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,wifi_event_handle,NULL);
//注册网络事件响应
esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,wifi_event_handle,NULL);
wifi_config_t wifi_config = {
.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK, //加密模式
.sta.pmf_cfg.capable = true, //使用保护管理帧
.sta.pmf_cfg.required = false, //是否只和有保护管理帧功能的设备通信
};
//设置SSID
memset(wifi_config.sta.ssid,0,sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.ssid,TEST_SSID,strlen(TEST_SSID));
//设置密码
memset(wifi_config.sta.password,0,sizeof(wifi_config.sta.password));
memcpy(wifi_config.sta.password,TEST_PASSWORD,strlen(TEST_PASSWORD));
//设置WIFI当前的模式 STA
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
//配置WIFI的STA模式下的网卡
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA,&wifi_config));
//启动wifi工作
ESP_ERROR_CHECK(esp_wifi_start());
xSemaphoreTake(wifi_connect_sem,portMAX_DELAY);//等待获取信号量
mqtt_start();
int count = 0;
//在这个循环里面,定时的向这个主题发布一条消息
while(1)
{
char count_str[32];
snprintf(count_str,sizeof(count_str),"{\"count\":%d}",count);
esp_mqtt_client_publish(mqtt_handle,MQTT_TOPIC1,count_str,strlen(count_str),1,0);
count++;
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
更多推荐
所有评论(0)