ESP32通关学习

一、ESP32架构和组件依赖

image-20250909153227434

1、组件

image-20250909152423071

2、组件依赖

image-20250909152610292

二、ESP32任务创建

image-20250909153716654

image-20250909153852012

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

image-20250909154511984

1、新建文件

①在终端命令中新建文件

cd ~/ESP/v5.5/esp-idf

. ./export.sh

image-20250909162610546

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

image-20250909163540348

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

image-20250909164208746

image-20250909164602843

因为这个函数它的参数不是毫秒,而是系统节拍,那么多少系统节拍是500ms呢?那么这里FreeRTOS提供一个宏

pdMS_TO_TICKS() 里面的系数填多少,就可以把相应的时间转换为对应的系统节拍数

image-20250909173415992

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中主要是用于不同任务间的数据传输,一般来说有个负责写入数据的任务【比如:负责采集传感器数据的任务】,然后有一个不断从队列中接收数据的任务

image-20250909181802381

【xQueueCreate创建一个队列,创建成功后,它会返回这个队列的句柄,后面所有的api都是基于这个句柄进行操作的】

【第一个参数为队列的最大容量、第二个参数为每个队列项所占的内存大小-单位为字节】

image-20250910100400685

image-20250910100518461

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);
 }
 实验效果如下:

image-20250910104807234

四、ESP32信号量

1、互斥锁

image-20250910105455239

互斥锁实现了优先级继承机制 - 常常用于临界区资源的互斥访问

 #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);
 }
 ​

image-20250910164114012

互斥锁使用的原则就是 - 谁拿了谁释放

2、移植dht11

image-20250911212201097

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

image-20250911212258699

信号线设置为输入模式 - 准备接收数据

 #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、事件组

image-20250910202928932

 #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);
 }
 ​

image-20250910213706555

2、直达任务通知

image-20250910203738791

 #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操作系统

image-20250911163831756

 注意: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

image-20250911171003287

 /*
 在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

image-20250911185328506

 如上图所示:
 如果我们加了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码 - 高低电平的占比

image-20250911201447872

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐